` 内 ``
+- 上传后必须通过 admin API PUT `/api/db/book` 触发 `cache.InvalidateChapterContent()`
diff --git a/.cursor/archive/agents-legacy-soul/小程序开发工程师/evolution/2026-04-11-阅读付费墙三态与营销句闭环.md b/.cursor/archive/agents-legacy-soul/小程序开发工程师/evolution/2026-04-11-阅读付费墙三态与营销句闭环.md
new file mode 100644
index 0000000..a8c8b1b
--- /dev/null
+++ b/.cursor/archive/agents-legacy-soul/小程序开发工程师/evolution/2026-04-11-阅读付费墙三态与营销句闭环.md
@@ -0,0 +1,15 @@
+# 阅读页付费墙三态与营销句闭环(2026-04-11)
+
+## 问题 / 背景
+
+- 登录前/后、朋友圈单页付费墙样式与「购买本章」框不一致;未登录长文案过多;底部营销句与已解锁分享区兜底不一致。
+
+## 处理要点
+
+- `read.wxml`:`paywall-marketing-box` 统一 90% 主标题;`readUi.shareTipLine` 作唯一底部营销句;已解锁区 `share-tip-text` 仅绑 `readUi.shareTipLine`,去掉旧兜底。
+- `read.js`:`readBeforeLoginHint` 默认置空(长说明改走配置或不再展示)。
+- 需求文档 `内容管理20260411.plan.md` 补充闭环与验收说明。
+
+## 可复用规则
+
+- 阅读页展示文案优先 `read_preview_ui`(章节接口合并);兜底在 `READ_UI_DEFAULTS` 与 `chapter_preview.go` 保持同步;管理端 ContentPage 模板同键。
diff --git a/.cursor/archive/agents-legacy-soul/小程序开发工程师/evolution/2026-04-13.md b/.cursor/archive/agents-legacy-soul/小程序开发工程师/evolution/2026-04-13.md
new file mode 100644
index 0000000..ef186f9
--- /dev/null
+++ b/.cursor/archive/agents-legacy-soul/小程序开发工程师/evolution/2026-04-13.md
@@ -0,0 +1,6 @@
+# 小程序开发工程师 经验记录 - 2026-04-13
+
+## 会议:按功能同步开发文档
+
+- **要点**:页面级记录 `/api/miniprogram/*`、scene、支付/登录、分享 singlePage 等行为变更;纯 UI 无接口变更时也注明,便于测试裁剪回归。
+- **待办**:主路径未记入文档的改动补索引一行。
diff --git a/.cursor/archive/agents-legacy-soul/小程序开发工程师/evolution/2026-04-14.md b/.cursor/archive/agents-legacy-soul/小程序开发工程师/evolution/2026-04-14.md
new file mode 100644
index 0000000..f37535b
--- /dev/null
+++ b/.cursor/archive/agents-legacy-soul/小程序开发工程师/evolution/2026-04-14.md
@@ -0,0 +1,6 @@
+# 小程序开发工程师 经验记录 - 2026-04-14
+
+## 吸收沉淀:闭环与硬编码自检
+
+- 新功能自检:**页面 → 数据从哪来**;若仅为写死展示,在索引注明「无接口/无配置」以免误判完成度。
+- 涉及 **scene、支付、登录、分享 singlePage** 的改动必须在 `需求汇总` 或项目索引可追溯,并评估对 **我的/阅读/代付** 等邻页的副作用。
diff --git a/.cursor/archive/agents-legacy-soul/小程序开发工程师/evolution/索引.md b/.cursor/archive/agents-legacy-soul/小程序开发工程师/evolution/索引.md
new file mode 100644
index 0000000..2a145f6
--- /dev/null
+++ b/.cursor/archive/agents-legacy-soul/小程序开发工程师/evolution/索引.md
@@ -0,0 +1,19 @@
+# 小程序开发工程师 经验索引
+
+| 日期 | 摘要 | 文件 |
+|------|------|------|
+| 2025-03-14 | 联网吸收:基础库 3.14、Skyline、隐私按需授权、新 API | [2025-03-14-联网吸收小程序最新开发规则与API.md](./2025-03-14-联网吸收小程序最新开发规则与API.md) |
+| 2025-03-14 | 阅读页文本长按选中复制:text 组件 user-select | [2025-03-14-文本长按复制.md](./2025-03-14-文本长按复制.md) |
+| 2026-02-28 | input 边距口诀、match 资源对接弹窗修正 | [2026-02-28.md](./2026-02-28.md) |
+| 2026-03-03 | 我的页面卡片区边距优化,16rpx 推荐值 | [2026-03-03.md](./2026-03-03.md) |
+| 2026-03-05 | 分支合并后核心流程自测;app.json 拆行;orders 接口确认 | [2026-03-05.md](./2026-03-05.md) |
+| 2026-03-05 | 文章详情@某人高亮与一键加好友(解析@、调添加好友接口) | [2026-03-05.md](./2026-03-05.md) |
+| 2026-03-10 | 管理端迁移 Mycontent-temp:关注内容产物格式与阅读页解析兼容回归 | [2026-03-10.md](./2026-03-10.md) |
+| 2026-03-12 | 链接标签 mpKey、@ 人物 token 兑换:contentParser、onLinkTagTap、onMentionTap | [2026-03-12.md](./2026-03-12.md) |
+| 2026-03-14 | 我的页设置入口隐藏;资料修改引导场景梳理(登录后、@某人、找伙伴、链接卡若) | [2026-03-14.md](./2026-03-14.md) |
+| 2026-03-16 | 编辑资料页分享名片:转发/朋友圈特殊处理,Canvas 绘制封面,标题「昵称+为您分享名片」 | [2026-03-16.md](./2026-03-16.md) |
+| 2026-03-17 | 代付美团式:读页→代付页→分享;详情页双态(发起人/好友);目录 loading、最新新增 5 条折叠 | [2026-03-17.md](./2026-03-17.md) |
+| 2026-03-30 | 阅读页 `navigateToMiniProgram` 时 path 追加 `phone`(已登录且已绑定) | [2026-03-30.md](./2026-03-30.md) |
+| 2026-04-11 | 阅读付费墙三态框体统一、`shareTipLine` 与文末分享区一致、需求文档闭环 | [2026-04-11-阅读付费墙三态与营销句闭环.md](./2026-04-11-阅读付费墙三态与营销句闭环.md) |
+| 2026-04-13 | 按功能同步开发文档:页面级接口与分享行为、无接口变更亦标注 | [2026-04-13.md](./2026-04-13.md) |
+| 2026-04-14 | 吸收沉淀:闭环与硬编码自检、邻页副作用 | [2026-04-14.md](./2026-04-14.md) |
diff --git a/.cursor/archive/agents-legacy-soul/管理端开发工程师/evolution/.gitkeep b/.cursor/archive/agents-legacy-soul/管理端开发工程师/evolution/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/.cursor/archive/agents-legacy-soul/管理端开发工程师/evolution/2026-02-26.md b/.cursor/archive/agents-legacy-soul/管理端开发工程师/evolution/2026-02-26.md
new file mode 100644
index 0000000..90eb313
--- /dev/null
+++ b/.cursor/archive/agents-legacy-soul/管理端开发工程师/evolution/2026-02-26.md
@@ -0,0 +1,5 @@
+# 2026-02-26 | 管理端经验
+
+> 本日经验条目,格式:类型 | 摘要 | 升级 Skill
+
+---
diff --git a/.cursor/archive/agents-legacy-soul/管理端开发工程师/evolution/2026-02-28.md b/.cursor/archive/agents-legacy-soul/管理端开发工程师/evolution/2026-02-28.md
new file mode 100644
index 0000000..c8abafd
--- /dev/null
+++ b/.cursor/archive/agents-legacy-soul/管理端开发工程师/evolution/2026-02-28.md
@@ -0,0 +1,16 @@
+# 管理端开发工程师 经验记录 - 2026-02-28
+
+## stitch_soul 需求评审会议
+
+- **待支撑能力**:章节管理(增删改、排序、免费/付费/NEW)、导师管理(审核、标签、价格、展示)、会员配置(权益、价格、有效期)、预约管理(列表、状态)。
+- **接口依赖**:`/api/admin/*` 与 `/api/db/*`;字段需与 miniprogram 端统一。
+- **时机**:待后端方案确定后规划管理端页面与接口对接。
+
+## 个人资料页实现评估会议
+
+- **无新增任务**:个人资料展示/编辑为 C 端能力,管理端沿用现有能力即可。
+
+## 文章类型(普通版/增值版)需求分析会议
+
+- **书籍版本配置**:支持选择普通版/增值版;配置「后 N 章」的 N。
+- **章节标注**:章节列表需标注是否为增值章节;单价取自 chapters.price。
diff --git a/.cursor/archive/agents-legacy-soul/管理端开发工程师/evolution/2026-03-05.md b/.cursor/archive/agents-legacy-soul/管理端开发工程师/evolution/2026-03-05.md
new file mode 100644
index 0000000..8b7d2f6
--- /dev/null
+++ b/.cursor/archive/agents-legacy-soul/管理端开发工程师/evolution/2026-03-05.md
@@ -0,0 +1,12 @@
+# 管理端开发工程师 经验记录 - 2026-03-05
+
+## 分支冲突后功能完整性分析会议
+
+- **路由与页面结构完整时**:主要风险在接口可用性。全功能自测可快速暴露 404 或异常接口。
+- **重点确认**:`DistributionPage` 调用的 `GET /api/db/distribution` 是否已实现;`OrdersPage` 的订单接口路径是否与 soul-api 一致。
+- **建议**:分支合并后做一次管理端全流程自测(登录→订单→提现→分销→VIP 角色→导师→配置),记录异常接口反馈后端。
+
+## 文章详情 @某人 高亮与一键加好友方案讨论
+
+- **编辑侧**:文章/章节编辑页增加「插入 @用户」,选择用户后插入到光标位置,保存时写入约定格式 content(如 `@[昵称](userId)`);仅调 `/api/admin/*`、`/api/db/*`。
+- **一致性**:与小程序、后端共用同一 content 格式,避免多套标记;列表/预览可简单高亮或原样显示。
diff --git a/.cursor/archive/agents-legacy-soul/管理端开发工程师/evolution/2026-03-10.md b/.cursor/archive/agents-legacy-soul/管理端开发工程师/evolution/2026-03-10.md
new file mode 100644
index 0000000..b292829
--- /dev/null
+++ b/.cursor/archive/agents-legacy-soul/管理端开发工程师/evolution/2026-03-10.md
@@ -0,0 +1,56 @@
+# 管理端开发工程师 经验记录 - 2026-03-10
+
+## 会议结论:管理端迁移 Mycontent-temp 菜单/布局
+
+- **目标态基线**:以 `Mycontent-temp/soul-admin` 为“新规范基线”,旧 `soul-admin` 若继续演进则对齐其实现,避免两套后台信息架构发散。
+- **菜单信息架构**:
+ - 侧栏主菜单固定 5 项:数据概览 / 内容管理 / 用户管理 / 找伙伴 / 推广中心
+ - 系统设置固定在侧栏底部
+ - 取消「更多」折叠入口
+- **路由与入口策略**:
+ - `author-settings`、`admin-users` 不再作为独立菜单/页面入口,统一并入 `/settings?tab=author|admin`
+ - 订单/提现/推广设置/VIP角色/导师等页面**保留路由可达**,但不进入侧栏主菜单;入口通过概览卡片/页面内跳转承载
+- **实现抓手**:
+ - `AdminLayout.tsx`:用 `primaryMenuItems` 平铺主菜单;Settings 单独固定
+ - `App.tsx`:对旧路径用 `Navigate` 做兼容跳转(减少断链风险)
+
+> 详见会议纪要:`.cursor/meeting/2026-03-10_管理端迁移Mycontent-temp菜单布局讨论.md`
+
+---
+
+## Toast 通知系统全局落地
+
+### 背景
+
+管理端全部操作反馈使用原生 `alert()`,体验差、阻断操作流程、需点 OK 才能继续。
+
+### 解决方案
+
+创建 `soul-admin/src/utils/toast.ts`(**纯原生 DOM 实现**,无第三方依赖):
+
+```typescript
+// 用法
+import toast from '@/utils/toast'
+toast.success('已保存:标题') // 绿色,3s 消失
+toast.error('保存失败: ...') // 红色,3s 消失
+toast.info('暂无数据') // 蓝色,3s 消失
+```
+
+### 全系统替换
+
+使用 PowerShell 批量脚本处理 **18 个文件、约 90 处 alert**,替换规则:
+- 含"失败/错误/请填/不一致/必填" → `toast.error()`
+- 含"成功/已保存/已删除/已创建" → `toast.success()`
+- 其余 → `toast.info()`
+
+替换后人工复查 `toast.info()` 调用,修正 5 处语义误判(验证提示类应为 error)。
+
+### 规则沉淀
+
+1. **管理端禁用 `alert()`**,统一使用 `@/utils/toast`
+2. 新增页面/组件时,操作反馈一律用 toast
+3. 批量脚本替换后,**必须**人工复查 `toast.info()` 是否有应为 `toast.error()` 的验证提示
+4. toast 自动消失(3s),不阻断流程;若需用户确认,仍使用 `confirm()`
+
+> 详见会议纪要:`.cursor/meeting/2026-03-10_Toast通知系统全局落地.md`
+
diff --git a/.cursor/archive/agents-legacy-soul/管理端开发工程师/evolution/2026-03-11.md b/.cursor/archive/agents-legacy-soul/管理端开发工程师/evolution/2026-03-11.md
new file mode 100644
index 0000000..408954d
--- /dev/null
+++ b/.cursor/archive/agents-legacy-soul/管理端开发工程师/evolution/2026-03-11.md
@@ -0,0 +1,7 @@
+# 管理端开发工程师 经验记录 - 2026-03-11
+
+## 以界面定需求:管理端界面清单作为验收基准
+
+- 《以界面定需求》已纳入管理端全部路由(dashboard、content、users、find-partner、distribution、orders、withdrawals、settings、vip-roles、mentors、mentor-consultations、payment、site、qrcodes、match、match-records、api-doc)及每页功能要点与主要接口(/api/admin/*、/api/db/*、/api/orders 等)。
+- 开发与联调以该清单为准;业务规则(用户/VIP 资料展示、三端 API 边界等)以《以界面定需求》第四节为准。
+- 详见团队共享:`agent/团队/evolution/2026-03-11.md`。
diff --git a/.cursor/archive/agents-legacy-soul/管理端开发工程师/evolution/2026-03-12.md b/.cursor/archive/agents-legacy-soul/管理端开发工程师/evolution/2026-03-12.md
new file mode 100644
index 0000000..7736add
--- /dev/null
+++ b/.cursor/archive/agents-legacy-soul/管理端开发工程师/evolution/2026-03-12.md
@@ -0,0 +1,43 @@
+# 管理端开发工程师 经验记录 - 2026-03-12
+
+## ContentPage TypeScript 严格类型修复
+
+### 问题
+
+`soul-admin` 构建时 `ContentPage.tsx` 出现多处 TS2322 类型错误:
+
+1. **可选字段赋给必填字段**:`LinkTagItem` 的 `appId`、`pagePath` 为可选(`string | undefined`),`setNewLinkTag` 期望 `string`
+2. **接口缺字段**:`SectionListItem` 无 `isPinned`,但 ranking API 返回该字段
+3. **API 映射类型**:`loadPersons` 中 `p.token`、`p.label`、`p.ckbApiKey` 可能为 `undefined`,映射后 `PersonItem.id` 等需为 `string`
+4. **可选参数传 setState**:`setEditingPersonKey(p.personId)` 中 `personId` 可选,setter 期望 `string | null`
+
+### 解决方案
+
+| 场景 | 写法 |
+|------|------|
+| 可选字段 → 必填 string | `t.appId ?? ''`、`t.pagePath ?? ''` |
+| 接口补字段 | 在 `SectionListItem` 添加 `isPinned?: boolean` |
+| API 映射兜底 | `id: p.token ?? p.personId ?? ''`,`label: p.label ?? ''`,`ckbApiKey: p.ckbApiKey ?? ''` |
+| 可选 → setState(string\|null) | `setEditingPersonKey(p.personId ?? null)` |
+
+### 规则提炼
+
+- 从可选类型(`T | undefined`)赋给必填类型(`T`)时,用 `?? defaultValue` 兜底
+- 接口类型需与 API 返回字段对齐,缺字段时补 `field?: Type`
+- `useState` 的 setter 传参时,`undefined` 需显式转为 `null`
+
+---
+
+## 关联小程序与 @ 人物:密钥/token 设计
+
+### 关联小程序
+
+- 添加时生成 32 位 key,链接标签选择小程序时存 key(非 appId)
+- 列表展示:名称、密钥、AppID、路径;编辑/删除用 key
+- 链接标签下拉:选项显示 name + key,选中后 `appId` 字段存 key
+
+### @ 人物
+
+- 添加时生成 32 位 token,PersonItem.id = token(RichEditor 插入用)
+- 列表展示 token;编辑/删除用 personId(API 仍用 personId)
+- 文章 @ 时 data-id 存 token
diff --git a/.cursor/archive/agents-legacy-soul/管理端开发工程师/evolution/2026-03-14.md b/.cursor/archive/agents-legacy-soul/管理端开发工程师/evolution/2026-03-14.md
new file mode 100644
index 0000000..49672f0
--- /dev/null
+++ b/.cursor/archive/agents-legacy-soul/管理端开发工程师/evolution/2026-03-14.md
@@ -0,0 +1,17 @@
+# 2026-03-14 - 内容排行权重保存后刷新
+
+## 问题 / 场景
+
+- 管理端「排名算法」弹窗修改权重并保存后,内容排行榜列表和排序不更新。
+- 原因:`handleSaveRankingWeights` 成功时只调用了 `loadList()`(章节树),未调用 `loadRanking()`(内容排行榜)。
+
+## 解决方案
+
+- 保存成功后:
+ 1. `setShowRankingAlgorithmModal(false)` 关闭弹窗
+ 2. `loadList()` 刷新章节树(含 hotScore)
+ 3. `loadRanking()` 刷新内容排行榜
+
+## 代码位置
+
+- `soul-admin/src/pages/content/ContentPage.tsx`:`handleSaveRankingWeights`
diff --git a/.cursor/archive/agents-legacy-soul/管理端开发工程师/evolution/2026-03-16.md b/.cursor/archive/agents-legacy-soul/管理端开发工程师/evolution/2026-03-16.md
new file mode 100644
index 0000000..4e79453
--- /dev/null
+++ b/.cursor/archive/agents-legacy-soul/管理端开发工程师/evolution/2026-03-16.md
@@ -0,0 +1,16 @@
+# 管理端开发工程师 经验记录 - 2026-03-16
+
+## 链接人与事列表优化
+
+- **table 布局**:列表改用 `` 替代 flex,列头与数据对齐
+- **新增列**:planId、apiKey
+- **apiKey 复制**:列前增加复制图标,点击复制到剪贴板并 toast 提示
+- **删除确认**:用 Dialog 弹窗替代 confirm(),二次确认文案清晰
+
+## 删除弹窗尺寸
+
+- max-w-md、p-4、gap-3,避免弹窗过大
+
+## new-soul 派对AI 与管理端(会议:new-soul 新需求与当前项目差异分析)
+
+- 派对AI 不新增管理端需求,管理端以当前项目为准
diff --git a/.cursor/archive/agents-legacy-soul/管理端开发工程师/evolution/2026-03-17.md b/.cursor/archive/agents-legacy-soul/管理端开发工程师/evolution/2026-03-17.md
new file mode 100644
index 0000000..e34314e
--- /dev/null
+++ b/.cursor/archive/agents-legacy-soul/管理端开发工程师/evolution/2026-03-17.md
@@ -0,0 +1,19 @@
+# 管理端开发工程师 经验记录 - 2026-03-17
+
+## 稳定版源码质量优化(会议:2026-03-17)
+
+- **UserDetailModal**:改为调用 `/api/admin/user/track`(后端新增后同步改)
+- **RichEditor**:@mention 弹窗对 `item.name`、`item.label` 做 HTML 转义,防 XSS,不改 @mention 行为
+
+---
+
+## 会议收尾(2026-03-17)
+
+- 源码优化已落地;开发环境测试通过
+
+---
+
+## 性能优化会议(2026-03-17)
+
+- **OSS 上传**:系统设置保存 OSS 配置后,上传接口自动优先 OSS;失败回退本地
+- 无需前端改动,后端 upload handler 已支持
diff --git a/.cursor/archive/agents-legacy-soul/管理端开发工程师/evolution/2026-03-18.md b/.cursor/archive/agents-legacy-soul/管理端开发工程师/evolution/2026-03-18.md
new file mode 100644
index 0000000..a3817fd
--- /dev/null
+++ b/.cursor/archive/agents-legacy-soul/管理端开发工程师/evolution/2026-03-18.md
@@ -0,0 +1,29 @@
+# 管理端开发工程师 经验记录 - 2026-03-18
+
+## 功能需求整理(以界面定需求 → 管理端任务清单)
+
+### 需求基准
+- 管理端界面与接口以 `开发文档/1、需求/以界面定需求.md` 第三节为准。
+- 允许路径:`/api/admin/*`、`/api/db/*`、`/api/orders` 等;禁止调用 `/api/miniprogram/*`。
+
+### 主要功能域(稳定版基线)
+- **登录与鉴权**:JWT Bearer;鉴权失败应清 token 并回登录(避免假登录态)。
+- **数据概览**:用户/订单/收入、趋势与最近列表。
+- **内容管理**:章节树/内容编辑/API 文档入口(以稳定版为主,迁移新版时不覆盖核心逻辑)。
+- **用户管理**:列表/搜索/详情;VIP 设置(到期必填);用户余额与行为轨迹。
+- **订单管理**:支付方式(微信/余额/代付);筛选/退款;用户/推荐人信息展示。
+- **提现管理**:审核/打款/状态流转;测试接口在 release 环境不可用。
+- **系统设置**:免费章节、推广设置、站点与 OSS 配置(如有 region 等字段需与后端契约一致)。
+
+### 新版差异(迁移待办口径)
+- 迁移与否以 `开发文档/迁移完成度与待办清单.md` 为准:优先补齐“运行时配置/审核模式/auditMode”相关的界面隐藏与配置读取一致性。
+
+### 分享链路验收提醒(管理端侧)
+- 虽然 singlePage 属于小程序端场景,但管理端涉及“配置/开关/文案”时,需要给小程序提供可配置的引导文案或开关(若业务要求),否则前端只能硬编码。
+
+## 超级个体开通后自动创建@人(链接人与事)
+
+### 管理端影响面
+- 自动创建的 Person 记录会出现在「链接人与事」列表中;如后端新增 `persons.user_id`,管理端可选增加一列“绑定用户/来源”,便于运营排查与避免重名困扰。
+- mention 展示依赖 TipTap 的 `data-label`:只要后端/数据层保证 `data-label=昵称`,管理端预览与编辑侧显示即可稳定。
+
diff --git a/.cursor/archive/agents-legacy-soul/管理端开发工程师/evolution/2026-03-24.md b/.cursor/archive/agents-legacy-soul/管理端开发工程师/evolution/2026-03-24.md
new file mode 100644
index 0000000..fb42ca0
--- /dev/null
+++ b/.cursor/archive/agents-legacy-soul/管理端开发工程师/evolution/2026-03-24.md
@@ -0,0 +1,15 @@
+# 管理端开发工程师 经验记录 - 2026-03-24
+
+## 开发进度同步会议
+
+### 提现相关(2026-03-20 已落地,与文档一致)
+- 提现审核:备注列(fail_reason/error_message)、自动审批开关。
+- 推广设置:提现手续费、自动提现开关。
+
+### 待办
+- DistributionPage Order.description 类型错误待修(与本次迁移无关,有空即修)。
+
+## 需求与进度及三端闭环评审
+
+- 管理端路由域与小程序 Tab/子业务整体配套(审核、配置、导师、匹配、存客宝相关)。
+- C 端绕过接口的风险主要靠后端 + 小程序请求层闭环,非管理端菜单能单独解决。
diff --git a/.cursor/archive/agents-legacy-soul/管理端开发工程师/evolution/2026-03-31.md b/.cursor/archive/agents-legacy-soul/管理端开发工程师/evolution/2026-03-31.md
new file mode 100644
index 0000000..f8f0984
--- /dev/null
+++ b/.cursor/archive/agents-legacy-soul/管理端开发工程师/evolution/2026-03-31.md
@@ -0,0 +1,6 @@
+# 管理端开发工程师 经验记录 - 2026-03-31
+
+## 会议:超级个体列表与 @ 列表融合
+
+- UsersPage「超级个体列表」与 ContentPage 人物/@ 区短期以 **数据同源 + 互链** 为主:表格增加 @ 绑定状态列、跳转内容管理;避免两处各写排序逻辑。
+- 二期再评估「超级个体中心」单页,复用现有表格与人物卡片模式。
diff --git a/.cursor/archive/agents-legacy-soul/管理端开发工程师/evolution/2026-04-02.md b/.cursor/archive/agents-legacy-soul/管理端开发工程师/evolution/2026-04-02.md
new file mode 100644
index 0000000..9f70a4c
--- /dev/null
+++ b/.cursor/archive/agents-legacy-soul/管理端开发工程师/evolution/2026-04-02.md
@@ -0,0 +1,6 @@
+# 管理端开发工程师 经验记录 - 2026-04-02
+
+## 会议:工作进度与需求同步会
+
+- **场景**:UsersPage @ 状态与跳转内容页依赖后端 vip-members 扩展。
+- **要点**:先锁定聚合接口字段契约再改表格列与跳转;deploy 文档若涉及管理端访问基址,与后端 README 对齐。
diff --git a/.cursor/archive/agents-legacy-soul/管理端开发工程师/evolution/2026-04-13.md b/.cursor/archive/agents-legacy-soul/管理端开发工程师/evolution/2026-04-13.md
new file mode 100644
index 0000000..eacfec1
--- /dev/null
+++ b/.cursor/archive/agents-legacy-soul/管理端开发工程师/evolution/2026-04-13.md
@@ -0,0 +1,6 @@
+# 管理端开发工程师 经验记录 - 2026-04-13
+
+## 会议:按功能同步开发文档
+
+- **要点**:以页面/菜单为粒度同步至运营与变更或项目索引;标注所用 `/api/admin/*`、`/api/db/*`;与小程序共用字段写明来源。
+- **待办**:DistributionPage 类型债是否纳入排期,在文档或索引中闭环。
diff --git a/.cursor/archive/agents-legacy-soul/管理端开发工程师/evolution/2026-04-14.md b/.cursor/archive/agents-legacy-soul/管理端开发工程师/evolution/2026-04-14.md
new file mode 100644
index 0000000..52cbb78
--- /dev/null
+++ b/.cursor/archive/agents-legacy-soul/管理端开发工程师/evolution/2026-04-14.md
@@ -0,0 +1,6 @@
+# 管理端开发工程师 经验记录 - 2026-04-14
+
+## 吸收沉淀:配置入口与需求表
+
+- 以 **页面/菜单** 为粒度在 `运营与变更.md` 或项目索引记录所用 `/api/admin/*`、`/api/db/*`;与小程序对齐的字段写明来源。
+- **MAINT-ADMIN-TS**:`DistributionPage` 等类型债已进 `需求汇总.md`,排期修复后更新状态为已完成。
diff --git a/.cursor/archive/agents-legacy-soul/管理端开发工程师/evolution/索引.md b/.cursor/archive/agents-legacy-soul/管理端开发工程师/evolution/索引.md
new file mode 100644
index 0000000..3a5b7fb
--- /dev/null
+++ b/.cursor/archive/agents-legacy-soul/管理端开发工程师/evolution/索引.md
@@ -0,0 +1,12 @@
+# 管理端开发工程师 经验索引
+
+| 日期 | 摘要 | 文件 |
+|------|------|------|
+| 2026-03-12 | ContentPage TypeScript 严格类型修复;关联小程序 key、@ 人物 token 设计 | [2026-03-12.md](./2026-03-12.md) |
+| 2026-03-14 | 排名算法权重保存后 loadRanking 刷新、关闭弹窗 | [2026-03-14.md](./2026-03-14.md) |
+| 2026-03-05 | 分支合并后全功能自测,404/异常接口记录 | [2026-03-05.md](./2026-03-05.md) |
+| 2026-03-05 | 文章详情@某人:编辑页插入 @用户、保存约定 content 格式 | [2026-03-05.md](./2026-03-05.md) |
+| 2026-03-10 | 管理端迁移 Mycontent-temp 菜单/布局:主导航收敛、Settings Tab 承载 author/admin | [2026-03-10.md](./2026-03-10.md) |
+| 2026-03-16 | 链接人与事:table 布局、planId/apiKey 列、复制图标、删除 Dialog 弹窗 | [2026-03-16.md](./2026-03-16.md) |
+| 2026-04-13 | 按功能同步开发文档:页面粒度 admin/db 与字段对齐说明 | [2026-04-13.md](./2026-04-13.md) |
+| 2026-04-14 | 吸收沉淀:配置入口与 MAINT-ADMIN-TS 需求行 | [2026-04-14.md](./2026-04-14.md) |
diff --git a/.cursor/archive/agents-legacy-soul/软件测试/evolution/.gitkeep b/.cursor/archive/agents-legacy-soul/软件测试/evolution/.gitkeep
new file mode 100644
index 0000000..e69de29
diff --git a/.cursor/archive/agents-legacy-soul/软件测试/evolution/2026-02-27.md b/.cursor/archive/agents-legacy-soul/软件测试/evolution/2026-02-27.md
new file mode 100644
index 0000000..446cebb
--- /dev/null
+++ b/.cursor/archive/agents-legacy-soul/软件测试/evolution/2026-02-27.md
@@ -0,0 +1,65 @@
+# 测试 - 经验记录 - 2026-02-27
+
+## 小程序静态审查报告
+
+**测试时间**:2026-02-27
+**测试方式**:代码级静态分析
+**测试范围**:miniprogram 全目录(不含 backup 文件)
+
+---
+
+### 一、API 路径隔离检查 ✅ 通过
+
+| 检查项 | 结果 | 说明 |
+|--------|------|------|
+| 是否只调 /api/miniprogram/* | ✅ | 所有 app.request 均使用 miniprogram 路径 |
+| 是否误调 /api/admin/*、/api/db/* | ✅ | 无违规调用 |
+| wx.uploadFile URL | ✅ | 使用 baseUrl + /api/miniprogram/upload |
+
+**说明**:`read.js.backup` 中曾有 `/api/db/config`,该文件为备份,非线上代码。
+
+---
+
+### 二、主要页面与接口映射
+
+| 页面/模块 | 主要接口 | 状态 |
+|-----------|----------|------|
+| 首页 index | vip/members、users、book/all-chapters | ✅ |
+| 目录 chapters | book/all-chapters | ✅ |
+| 阅读 read | book/chapter、purchase-status、pay、qrcode | ✅ |
+| 我的 my | config、withdraw/*、earnings、user/update、vip/status | ✅ |
+| VIP vip | vip/status、vip/profile、upload、pay | ✅ |
+| 推广 referral | referral/data、qrcode、withdraw | ✅ |
+| 匹配 match | match/config、match/users、ckb/join、ckb/match、pay | ✅ |
+| 设置 settings | user/update、user/profile、phone、upload | ✅ |
+| 地址 addresses | user/addresses | ✅ |
+| 提现记录 | withdraw/records、confirm-info | ✅ |
+| 购买记录 purchases | orders | ✅ |
+| 会员详情 member-detail | vip/members、users | ✅ |
+| 搜索 search | book/hot、book/search | ✅ |
+
+---
+
+### 三、建议手工验证场景
+
+| 场景 | 验证点 | 优先级 |
+|------|--------|--------|
+| 登录 | 微信登录、token 持久化、401 跳转 | 高 |
+| VIP 购买 | 下单、支付、开通成功、资料填写、头像上传 | 高 |
+| 推荐码 | 分享带 ref、扫码绑定、分润展示 | 高 |
+| 超级个体 | 加载骨架、头像展示、点击进详情 | 中 |
+| 朋友圈分享 | 所有页面右上角「分享到朋友圈」可点 | 中 |
+| 提现 | 申请、记录、确认收款 | 中 |
+| 地址管理 | 增删改查、默认地址 | 低 |
+
+---
+
+### 四、发现与建议
+
+1. **输入框规范**:VIP 等表单已按「view 包裹 input」规范实现,符合 SKILL-小程序开发 §6。
+2. **朋友圈分享**:各页面已启用 wx.showShareMenu + onShareTimeline,推荐码会随 query 传递。
+3. **超级个体**:已有 4 圆形骨架加载动画,加载完成后再展示内容或空态。
+
+---
+
+**结论**:小程序代码在 API 路径隔离、规范遵从方面**通过静态审查**。建议在真机/模拟器中按上表手工验证核心流程。
diff --git a/.cursor/archive/agents-legacy-soul/软件测试/evolution/2026-02-28.md b/.cursor/archive/agents-legacy-soul/软件测试/evolution/2026-02-28.md
new file mode 100644
index 0000000..f75f452
--- /dev/null
+++ b/.cursor/archive/agents-legacy-soul/软件测试/evolution/2026-02-28.md
@@ -0,0 +1,16 @@
+# 测试人员 经验记录 - 2026-02-28
+
+## stitch_soul 需求评审会议
+
+- **关键联调场景**:阅读进度、免费/付费解锁、会员权益、导师预约与支付、资料完善与提现限制。
+- **三端**:miniprogram ↔ soul-api、soul-admin ↔ soul-api;变更后需回归支付、登录、提现等现有流程。
+- **待办**:需求确定后补充三端联调用例与回归清单。
+
+## 个人资料页实现评估会议
+
+- **验证点**:profile-show 与 profile-edit 字段一一对应;保存后两页及「我的」数据一致;手机/微信号脱敏与复制;头像上传、昵称、MBTI 选择。
+
+## 文章类型(普通版/增值版)需求分析会议
+
+- **用例**:普通版 9.9 买断全书;增值版基础价+逐章购买、价格累加正确。
+- **边界**:N=0、N=全书、章节无单价时的降级逻辑。
diff --git a/.cursor/archive/agents-legacy-soul/软件测试/evolution/2026-03-05.md b/.cursor/archive/agents-legacy-soul/软件测试/evolution/2026-03-05.md
new file mode 100644
index 0000000..2fc43be
--- /dev/null
+++ b/.cursor/archive/agents-legacy-soul/软件测试/evolution/2026-03-05.md
@@ -0,0 +1,13 @@
+# 软件测试 经验记录 - 2026-03-05
+
+## 分支冲突后功能完整性分析会议
+
+- **分支合并后应制定「回归清单」**:覆盖三端联调关键路径(登录、VIP、阅读、分销、提现、找伙伴、个人资料、导师、购买记录)。
+- **soul-api 不在仓库时**:需与后端协作确认接口契约,无法在仓库内直接检查。
+- **多分支合并**:`完整的`、`soul-content`、`yongpxu-soul` 等分支合并结果需确认,各端自测 + 测试抽检。
+
+## 文章详情 @某人 高亮与一键加好友方案讨论
+
+- **用例**:无 @ 行为不变;有 @ 高亮且点击调起添加好友并提示;重复点击、未登录、无权限等边界;管理端插入 @ 后保存再编辑不错位。
+- **联调**:小程序↔章节接口(content 含 @)、小程序↔添加好友接口;管理端↔内容保存与用户列表。
+- **回归**:阅读页进度、购买、分享等不受 @ 功能影响。
diff --git a/.cursor/archive/agents-legacy-soul/软件测试/evolution/2026-03-10.md b/.cursor/archive/agents-legacy-soul/软件测试/evolution/2026-03-10.md
new file mode 100644
index 0000000..2fba8b5
--- /dev/null
+++ b/.cursor/archive/agents-legacy-soul/软件测试/evolution/2026-03-10.md
@@ -0,0 +1,12 @@
+# 软件测试 经验记录 - 2026-03-10
+
+## 管理端迁移 Mycontent-temp:测试与回归关注点
+
+- **菜单一致性**:侧栏必须是 5 个主入口 + 底部系统设置;不再出现“更多”折叠。
+- **路由可达性回归**:菜单入口减少不等于功能减少,需要覆盖“隐藏路由仍可访问”的用例:
+ - 订单、提现、推广设置、VIP角色、导师、导师预约、二维码、站点、支付、API 文档、匹配记录等。
+- **鉴权回归**:任意页面刷新都必须先 `GET /api/admin` 校验;失效则跳登录并清 token。
+- **导航兼容**:旧路径(如 `/author-settings`、`/admin-users`)应跳转到 `/settings?tab=author|admin`。
+
+> 详见会议纪要:`.cursor/meeting/2026-03-10_管理端迁移Mycontent-temp菜单布局讨论.md`
+
diff --git a/.cursor/archive/agents-legacy-soul/软件测试/evolution/2026-03-15-全站深度测试42问题.md b/.cursor/archive/agents-legacy-soul/软件测试/evolution/2026-03-15-全站深度测试42问题.md
new file mode 100644
index 0000000..7ef9499
--- /dev/null
+++ b/.cursor/archive/agents-legacy-soul/软件测试/evolution/2026-03-15-全站深度测试42问题.md
@@ -0,0 +1,44 @@
+# 2026-03-15 Soul 创业派对全站深度测试
+
+## 问题背景
+
+对 Soul 创业派对项目进行首次全站深度测试(只检测不修改),覆盖管理端 20+ 页面、35 个 API 端点、25 个小程序页面、25 张数据库表。
+
+## 解决过程
+
+### 测试方法
+1. 环境检查:确认后端/前端/数据库运行状态
+2. 管理端浏览器测试:逐页逐按钮截图检查
+3. API 端点测试:curl 逐个测试 35 个端点,含安全边界测试
+4. 小程序代码审查:25 个页面 + 8 个工具文件全量代码阅读
+5. 数据库一致性检查:交叉验证各 API 数据
+
+### 关键发现(42 个问题)
+- 严重 11 个:OSS 密钥泄露、登录守卫缺失、小程序模块混用、废弃 API
+- 高 13 个:硬编码、API 失败伪装成功、分页缺失
+- 中 12 个:调试日志残留、模拟数据未清理
+- 低 6 个:版本号未设置等
+
+## 可提炼规则
+
+### 安全测试
+1. **密钥脱敏是硬性规则**:任何返回配置的 API,密钥类字段必须脱敏
+2. **SPA 路由守卫必查**:直接访问后台路径测试
+3. **上传接口安全测试**:非图片文件、超大文件、空文件
+
+### API 测试
+4. **响应字段名不能假设**:先 print keys() 再解析,不同端点可能用 data/results/orders/records
+5. **分页必须翻页验证**:测第一页也测 page=2
+6. **交叉验证**:stats 的总数 vs list API 的实际条数
+
+### 小程序测试
+7. **废弃 API 年检制度**:每年核对微信基础库废弃列表
+8. **模块语法统一检查**:grep -rn "export default" 快速排查
+9. **死代码扫描**:utils/ 每个文件是否被 pages/ 引用
+
+### 数据库测试
+10. **空表不代表无问题**:空表可能是同步逻辑失效
+
+## 适用角色
+
+- target_roles: ["软件测试", "团队"]
diff --git a/.cursor/archive/agents-legacy-soul/软件测试/evolution/2026-03-16-scripts目录与测试关联.md b/.cursor/archive/agents-legacy-soul/软件测试/evolution/2026-03-16-scripts目录与测试关联.md
new file mode 100644
index 0000000..02e1551
--- /dev/null
+++ b/.cursor/archive/agents-legacy-soul/软件测试/evolution/2026-03-16-scripts目录与测试关联.md
@@ -0,0 +1,103 @@
+# 软件测试 经验记录 - 2026-03-16
+
+## scripts 目录与测试关联
+
+测试工程师需了解项目根目录 `scripts/` 下的辅助脚本,以便在联调、回归、环境准备时正确使用。
+
+---
+
+### 1. 本地启动脚本(联调必备)
+
+| 脚本 | 用途 | 测试关联 |
+|------|------|----------|
+| `本地启动.sh` | 一键启动 soul-api(8080)+ soul-admin(5174) | **三端联调前**:先执行此脚本,确保后端与管理端在本地运行;小程序需配置本地 API 地址 |
+
+**用法**:`./scripts/本地启动.sh` 或 `bash scripts/本地启动.sh`
+**前置**:Mac/Linux 环境;soul-api 需能连接数据库;首次会编译 `soul-api-mac`
+**验证**:访问 http://localhost:5174,默认账号 admin / admin123
+
+---
+
+### 2. 飞书相关脚本(非核心业务,可了解)
+
+| 脚本/目录 | 用途 | 测试关联 |
+|-----------|------|----------|
+| `feishu_export/` | 书稿导出 md,供飞书知识库同步 | 与 Soul 三端业务无直接关系,回归可不覆盖 |
+| `sync_book_to_feishu_export.py` | 从书稿目录导出 md 到 feishu_export | 同上 |
+| `feishu_wiki_upload.py` | 上传全书到飞书知识库 | 同上 |
+| `send_chapter_poster_to_feishu.py` | 生成章节海报并推送到飞书群 | 若海报含小程序码,可顺带验证二维码可访问性 |
+
+---
+
+### 3. Git 推送脚本
+
+| 脚本 | 用途 | 测试关联 |
+|------|------|----------|
+| `gitea_push_once.sh` | 首次推送到 Gitea 仓库 | 与功能测试无关,部署/发布流程用 |
+
+---
+
+### 4. 测试工程师使用建议
+
+- **联调前**:优先使用 `本地启动.sh` 启动后端与管理端,再测小程序、管理端功能
+- **回归范围**:scripts 内飞书、Gitea 脚本不纳入三端功能回归清单
+- **环境依赖**:`本地启动.sh` 依赖 Go 编译、pnpm、数据库可连;测试环境需提前确认
+
+---
+
+### 5. 测试用例目录 `scripts/test/`(测试工程师主战场)
+
+| 子目录 | 用途 | 对应端 |
+|--------|------|--------|
+| **miniapp/** | 小程序接口测试 | miniprogram,API:/api/miniprogram/* |
+| **web/** | 管理端测试 | soul-admin,API:/api/admin/*、/api/db/* |
+| **process/** | 流程测试 | 跨端,多接口串联 |
+
+**约定**:测试工程师在此编写与维护测试用例,miniapp 放小程序接口、web 放管理端、process 放跨端业务流程。
+
+**环境配置**:必须明确指定测试环境(SOUL_TEST_ENV=local|souldev|soulapi 或 SOUL_API_BASE),运行前会打印「测试环境: xxx」横幅,避免误测正式库。配置可来自 soul-api/.env* 或 scripts/test/.env.test。
+
+---
+
+### 6. pytest + requests 架构与配置约定
+
+| 文件 | 说明 |
+|------|------|
+| config.py | 从项目 soul-api/.env* 或 .env.test 读取;SOUL_TEST_ENV / SOUL_API_BASE |
+| conftest.py | base_url、admin_token、miniapp_token;pytest_report_header 显示环境横幅 |
+| util.py | admin_headers、miniapp_headers |
+| requirements-test.txt | pytest、requests |
+
+**配置优先级**:SOUL_TEST_ENV > SOUL_API_BASE > .env.test > soul-api/.env* > 默认 local。
+
+**运行前必看**:pytest 报告头部会显示「测试环境: 本地/测试/正式 (URL)」,确认无误后再执行。
+
+---
+
+### 7. 测试用例归档与复用规则
+
+| 场景 | 归档目录 | 示例 |
+|------|----------|------|
+| 管理端 + 后端混合 | process/ | 文章 @某人 自动创建 Person + 存客宝 |
+| 仅小程序接口 | miniapp/ | 登录、VIP、阅读 |
+| 仅管理端/后端 | web/ | 鉴权、CRUD |
+
+**需求变更**:用例随需求更新;无变更时直接复用。
+
+---
+
+### 8. 与 soul-api/scripts 的区别
+
+| 位置 | 内容 | 测试关联 |
+|------|------|----------|
+| `scripts/`(项目根) | 本地启动、飞书同步、Gitea 推送、**test/** | 见上文 |
+| `scripts/test/` | **测试用例**:miniapp、web、process;pytest 架构 | 测试工程师在此写用例 |
+| `soul-api/scripts/` | SQL 迁移、Python 脚本等 | 数据库迁移、后端运维;测试时若涉及表结构变更,需关注对应 SQL |
+
+---
+
+### 9. 示例:文章 @某人 自动创建(2026-03-16)
+
+- **用例**:`scripts/test/process/test_article_mention_ckb_flow.py`
+- **报告**:`scripts/test/process/2026-03-16-文章@某人自动创建-测试报告.md`
+- **结论**:后端逻辑正确,会调用存客宝创建计划;存客宝 API 返回 400 导致失败,需排查 CKB 配置或 deviceGroups 空值
diff --git a/.cursor/archive/agents-legacy-soul/软件测试/evolution/2026-03-16.md b/.cursor/archive/agents-legacy-soul/软件测试/evolution/2026-03-16.md
new file mode 100644
index 0000000..5a41707
--- /dev/null
+++ b/.cursor/archive/agents-legacy-soul/软件测试/evolution/2026-03-16.md
@@ -0,0 +1,7 @@
+# 软件测试 经验记录 - 2026-03-16
+
+## new-soul 派对AI 与测试关注点(会议:new-soul 新需求与当前项目差异分析)
+
+- 引入派对AI 流程时需回归:文章上传、飞书推送、小程序展示
+- 关注 content_upload.py 与 soul-api 数据流一致性,避免双写冲突
+- 环境差异:派对AI 为 Mac 路径,当前为 Windows,需确认是否同一代码库不同环境
diff --git a/.cursor/archive/agents-legacy-soul/软件测试/evolution/2026-03-17.md b/.cursor/archive/agents-legacy-soul/软件测试/evolution/2026-03-17.md
new file mode 100644
index 0000000..5a03088
--- /dev/null
+++ b/.cursor/archive/agents-legacy-soul/软件测试/evolution/2026-03-17.md
@@ -0,0 +1,31 @@
+# 软件测试 经验记录 - 2026-03-17
+
+## 新版管理端迁移验收(会议:实施方案确认)
+
+- **验收清单**:按《新版管理端迁移到稳定版-需求评估》§七
+- **回归范围**:提现、分销、找伙伴、导师、设置等
+- **风险**:合并时避免误覆盖稳定版独有逻辑,建议 diff 逐模块核对
+- **三端联调**:管理端 ↔ soul-api 重点验证;用户规则、订单、余额展示
+
+---
+
+## 稳定版源码质量优化(会议:2026-03-17)
+
+- **回归重点**:支付流程、管理端用户详情行为轨迹、我的页找伙伴/推广/搜索、首页目录搜索
+- **策略**:每项优化完成后小回归,全部完成后完整三端联调
+
+---
+
+## 会议收尾(2026-03-17)
+
+- **功能测试流程定稿**:`scripts/test/功能测试流程.md` — 成功 ☑️、失败列问题、最终报告
+- **测试报告模板**:`scripts/test/测试报告-环境与用例清单.md` — 环境、用例、结果记录
+- **开发环境测试**:10 通过、2 跳过、0 失败
+
+---
+
+## 性能优化会议(2026-03-17)
+
+- **test_upload.py**:6 个用例(上传成功、鉴权、校验、删除)
+- **/health**:可验证 database、redis 连接状态
+- **部署后回归**:parts、hot、config、章节阅读等缓存接口
diff --git a/.cursor/archive/agents-legacy-soul/软件测试/evolution/2026-03-18.md b/.cursor/archive/agents-legacy-soul/软件测试/evolution/2026-03-18.md
new file mode 100644
index 0000000..2e4a22e
--- /dev/null
+++ b/.cursor/archive/agents-legacy-soul/软件测试/evolution/2026-03-18.md
@@ -0,0 +1,34 @@
+# 软件测试 经验记录 - 2026-03-18
+
+## 文档归档后测试口径统一(按“界面→接口→规则”验收)
+
+### 验收基准(先看文档再测)
+- 《以界面定需求》:`开发文档/1、需求/以界面定需求.md`
+- 《需求清单》:`开发文档/1、需求/需求汇总.md`
+- 《变更与决议》:`开发文档/10、项目管理/运营与变更.md`
+- 《测试流程》:`scripts/test/功能测试流程.md`
+- 《里程碑推进》:`开发文档/10、项目管理/项目落地推进表.md`
+
+### 分享场景新增必测点(高优先级)
+- **好友分享**:进入页面能力完整,支付/登录/领取均可用。
+- **朋友圈分享(singlePage)**:
+ - 进入后页面能力可能不完整
+ - 关键按钮点击应 **不执行强动作**(支付/自动领取/自动登录等)
+ - 必须出现明确引导:点击底部 **「前往小程序」** 进入完整版
+
+### 回归覆盖建议(与分享强相关的链路)
+- 代付分享:发起人支付→分享→好友打开→自动/手动领取→解锁正文→重复进入幂等
+- 支付与回调:微信/余额两条路,状态一致,失败/取消提示一致
+- 路径隔离:小程序只调 `/api/miniprogram/*`;管理端不调 miniprogram
+
+## 新增用例:超级个体开通后自动创建@人与支付前资料引导
+
+### 资料引导拦截
+- 默认资料(昵称/头像)→ 点击支付超级个体 → 必须跳转 `avatar-nickname` 引导页并阻断支付
+- 引导页保存成功 → 返回原流程继续支付(不丢失上下文)
+- 已完善资料 → 不拦截支付
+
+### Person 自动创建(幂等)
+- 支付成功回调重复触发/用户重复进入成功页 → Person 记录不应重复创建
+- 昵称变更后同步策略回归(若实现“跟随昵称”)
+
diff --git a/.cursor/archive/agents-legacy-soul/软件测试/evolution/2026-03-24.md b/.cursor/archive/agents-legacy-soul/软件测试/evolution/2026-03-24.md
new file mode 100644
index 0000000..8086734
--- /dev/null
+++ b/.cursor/archive/agents-legacy-soul/软件测试/evolution/2026-03-24.md
@@ -0,0 +1,13 @@
+# 软件测试 经验记录 - 2026-03-24
+
+## 开发进度同步会议
+
+### 用例补充待办
+- 提现审批、手机号登录、我的收益、推广设置、提现审核等新增功能需补充/更新用例。
+- 边界必测:singlePage 分享进入、getPhoneNumber 隐私协议同意流程。
+- 下次回归前完成用例更新。
+
+## 需求与进度及三端闭环评审
+
+- 补齐「仅调 API、绕过前端」负例:提现、CKBJoin、MatchUsers。
+- 后端与 err.response 契约落地后,做双入口提现(我的 / 推广)回归。
diff --git a/.cursor/archive/agents-legacy-soul/软件测试/evolution/2026-03-31.md b/.cursor/archive/agents-legacy-soul/软件测试/evolution/2026-03-31.md
new file mode 100644
index 0000000..06ad864
--- /dev/null
+++ b/.cursor/archive/agents-legacy-soul/软件测试/evolution/2026-03-31.md
@@ -0,0 +1,6 @@
+# 软件测试 经验记录 - 2026-03-31
+
+## 会议:超级个体列表与 @ 列表融合
+
+- 回归清单:VIP 排序↔首页、@ 解析与跳转、Person 与 VIP 绑定/解绑、获客/Webhook。
+- 边界:无 Person 的 VIP、VIP 过期仍有 Person 等交叉场景需与产品确认预期。
diff --git a/.cursor/archive/agents-legacy-soul/软件测试/evolution/2026-04-02.md b/.cursor/archive/agents-legacy-soul/软件测试/evolution/2026-04-02.md
new file mode 100644
index 0000000..531e989
--- /dev/null
+++ b/.cursor/archive/agents-legacy-soul/软件测试/evolution/2026-04-02.md
@@ -0,0 +1,6 @@
+# 软件测试 经验记录 - 2026-04-02
+
+## 会议:工作进度与需求同步会
+
+- **场景**:融合方案回归与部署类变更验证策略。
+- **要点**:3/31 清单(排序、@、绑定、Webhook)待联调就绪后执行;含 `deploy/` 的 PR 建议增加 compose 起服务、`/health`、核心阅读/下单 smoke。
diff --git a/.cursor/archive/agents-legacy-soul/软件测试/evolution/2026-04-13.md b/.cursor/archive/agents-legacy-soul/软件测试/evolution/2026-04-13.md
new file mode 100644
index 0000000..6ce537b
--- /dev/null
+++ b/.cursor/archive/agents-legacy-soul/软件测试/evolution/2026-04-13.md
@@ -0,0 +1,6 @@
+# 软件测试 经验记录 - 2026-04-13
+
+## 会议:按功能同步开发文档
+
+- **要点**:以更新后的开发文档与各角色项目索引为真源维护回归与联调清单;契约未入文档可标阻塞。
+- **待办**:依据各端索引更新融合与主路径用例;deploy/health/smoke 与项目管理文档交叉引用。
diff --git a/.cursor/archive/agents-legacy-soul/软件测试/evolution/2026-04-14.md b/.cursor/archive/agents-legacy-soul/软件测试/evolution/2026-04-14.md
new file mode 100644
index 0000000..b446f66
--- /dev/null
+++ b/.cursor/archive/agents-legacy-soul/软件测试/evolution/2026-04-14.md
@@ -0,0 +1,6 @@
+# 软件测试 经验记录 - 2026-04-14
+
+## 吸收沉淀:以文档为真源的回归
+
+- **LOOP-20260414**:按「小程序优先逆推」清单编写/裁剪用例;文档与索引未更新的契约项可标 **阻塞—待文档** 并指向纪要问题区。
+- 主路径冒烟与 deploy/health/smoke 要求见 `运营与变更.md` 与历史纪要交叉引用。
diff --git a/.cursor/archive/agents-legacy-soul/软件测试/evolution/索引.md b/.cursor/archive/agents-legacy-soul/软件测试/evolution/索引.md
new file mode 100644
index 0000000..ebbf00d
--- /dev/null
+++ b/.cursor/archive/agents-legacy-soul/软件测试/evolution/索引.md
@@ -0,0 +1,14 @@
+# 软件测试 经验索引
+
+| 日期 | 摘要 | 文件 |
+|------|------|------|
+| 2026-03-05 | 分支合并后回归清单制定;三端联调验证 | [2026-03-05.md](./2026-03-05.md) |
+| 2026-03-05 | 文章详情@某人:@ 展示与添加好友用例、联调与回归点 | [2026-03-05.md](./2026-03-05.md) |
+| 2026-03-10 | 管理端迁移 Mycontent-temp:菜单一致性、隐藏路由可达性、鉴权与跳转回归 | [2026-03-10.md](./2026-03-10.md) |
+| 2026-03-16 | scripts 目录与测试关联:本地启动、飞书脚本、联调前环境准备 | [2026-03-16-scripts目录与测试关联.md](./2026-03-16-scripts目录与测试关联.md) |
+| 2026-03-16 | scripts/test 测试用例目录:miniapp 小程序接口、web 管理端 | [2026-03-16-scripts目录与测试关联.md](./2026-03-16-scripts目录与测试关联.md) |
+| 2026-03-16 | scripts/test/process 流程测试:跨端多接口串联 | [2026-03-16-scripts目录与测试关联.md](./2026-03-16-scripts目录与测试关联.md) |
+| 2026-03-16 | pytest 架构、配置从项目读取、运行前显示测试环境 | [2026-03-16-scripts目录与测试关联.md](./2026-03-16-scripts目录与测试关联.md) |
+| 2026-03-16 | 测试用例归档规则:混合→process、纯端→miniapp/web;需求变更时更新 | [2026-03-16-scripts目录与测试关联.md](./2026-03-16-scripts目录与测试关联.md) |
+| 2026-04-13 | 按功能同步开发文档:以文档与索引为真源、契约缺失可阻塞 | [2026-04-13.md](./2026-04-13.md) |
+| 2026-04-14 | 吸收沉淀:LOOP 需求行与逆推清单回归 | [2026-04-14.md](./2026-04-14.md) |
diff --git a/.cursor/archive/skills-karuo-party/README.md b/.cursor/archive/skills-karuo-party/README.md
new file mode 100644
index 0000000..48eaba9
--- /dev/null
+++ b/.cursor/archive/skills-karuo-party/README.md
@@ -0,0 +1,3 @@
+# 已归档:karuo-party 技能包
+
+原路径:`.cursor/skills/karuo-party/`。与玩值(Wanzhi)三端迁移无强关联,已整体移至此处。若仍做 Soul/卡若运营,可手动将本目录移回 `skills/` 或从本 README 相对路径 Read `karuo-party/SKILL.md`。
diff --git a/.cursor/archive/skills-karuo-party/karuo-party/README.md b/.cursor/archive/skills-karuo-party/karuo-party/README.md
new file mode 100644
index 0000000..9b4968f
--- /dev/null
+++ b/.cursor/archive/skills-karuo-party/karuo-party/README.md
@@ -0,0 +1,159 @@
+# 卡若创业派对运营技能包
+
+> **打包日期**:2026-03-20
+> **项目**:一场soul的创业实验-永平
+> **技能包路径**:`.cursor/skills/karuo-party/`
+
+---
+
+## 📦 打包内容
+
+本技能包整合了卡若创业派对的 4 大核心运营技能:
+
+1. **运营报表**:Soul派对运营数据全自动写入飞书表格
+2. **飞书视频文字下载**:从飞书妙记下载视频和文字
+3. **视频切片**:视频转录、高光识别、批量切片、成片输出
+4. **多平台分发**:一键分发到抖音/B站/视频号/小红书/快手
+
+---
+
+## 🔐 凭证管理
+
+所有凭证统一存储在 `credentials/` 目录:
+
+### 飞书凭证
+
+- **`.feishu_tokens.json`**:飞书访问令牌(自动刷新)
+
+### 视频平台 Cookies
+
+- **视频号**:`cookies/视频号_cookies.json`(有效期 ~24-48h)
+- **B站**:`cookies/B站_cookies.json`(有效期 ~6个月)
+- **小红书**:`cookies/小红书_cookies.json`(有效期 ~1-3天)
+- **快手**:`cookies/快手_cookies.json`(有效期 ~7-30天)
+- **抖音**:`cookies/抖音_cookies.json`(账号封禁中)
+
+---
+
+## 📁 目录结构
+
+```
+.cursor/skills/karuo-party/
+├── SKILL.md # 主技能入口
+├── README.md # 本文件
+├── credentials/ # 凭证目录
+│ ├── .feishu_tokens.json # 飞书Token
+│ └── cookies/ # 平台Cookies
+│ ├── 视频号_cookies.json
+│ ├── B站_cookies.json
+│ ├── 小红书_cookies.json
+│ ├── 快手_cookies.json
+│ └── 抖音_cookies.json
+└── skills/ # 子技能文档
+ ├── 运营报表_SKILL.md
+ ├── 飞书视频文字下载_SKILL.md
+ ├── 视频切片_SKILL.md
+ └── 多平台分发_SKILL.md
+```
+
+---
+
+## 🚀 快速开始
+
+### 1. 运营报表
+
+```bash
+FEISHU_SCRIPT="/Users/karuo/Documents/个人/卡若AI/02_卡人(水)/水桥_平台对接/飞书管理/脚本"
+cd "$FEISHU_SCRIPT"
+python3 soul_party_to_feishu_sheet.py 115
+```
+
+### 2. 飞书视频文字下载
+
+```bash
+JIYAO_SCRIPT="/Users/karuo/Documents/个人/卡若AI/02_卡人(水)/水桥_平台对接/智能纪要/脚本"
+python3 "$JIYAO_SCRIPT/feishu_minutes_export_github.py" "<妙记链接>"
+python3 "$JIYAO_SCRIPT/feishu_minutes_download_video.py" "<妙记链接>"
+```
+
+### 3. 视频切片
+
+```bash
+VIDEO_SCRIPT="/Users/karuo/Documents/个人/卡若AI/03_卡木(木)/木叶_视频内容/视频切片/脚本"
+conda activate mlx-whisper
+python3 "$VIDEO_SCRIPT/soul_slice_pipeline.py" --video "<原视频.mp4>" --clips 8
+```
+
+### 4. 多平台分发
+
+```bash
+DIST_SCRIPT="/Users/karuo/Documents/个人/卡若AI/03_卡木(木)/木叶_视频内容/多平台分发/脚本"
+python3 "$DIST_SCRIPT/distribute_all.py" --video-dir "<成片目录>"
+```
+
+---
+
+## 📝 使用说明
+
+### 激活方式
+
+当用户提到以下触发词时,自动激活本技能包:
+
+- **运营报表**、**派对填表**、**派对截图**
+- **飞书视频下载**、**妙记下载**、**飞书妙记**
+- **视频剪辑**、**切片发布**、**视频切片**
+- **多平台分发**、**一键分发**、**全平台发布**
+- **卡若创业派对**、**派对运营**
+
+### 执行流程
+
+1. **读取对应子技能**:根据用户需求,读取 `skills/` 目录下对应的 SKILL.md
+2. **检查凭证**:确认 `credentials/` 目录下相关凭证文件存在且有效
+3. **执行命令**:按子技能文档中的命令执行(路径指向卡若AI原始脚本目录)
+4. **结果反馈**:执行完成后反馈结果
+
+---
+
+## ⚠️ 注意事项
+
+1. **脚本路径**:所有脚本仍在卡若AI原始目录,本技能包仅提供技能文档和凭证管理
+2. **凭证同步**:凭证更新后需手动复制到本技能包的 `credentials/` 目录
+3. **跨平台兼容**:macOS 用 `python3`,Windows 用 `python`
+
+---
+
+## 🔄 凭证更新
+
+### 飞书 Token
+
+```bash
+cd "/Users/karuo/Documents/个人/卡若AI/02_卡人(水)/水桥_平台对接/飞书管理/脚本"
+python3 auto_log.py
+# 更新后,将 .feishu_tokens.json 复制到本技能包的 credentials/ 目录
+```
+
+### 平台 Cookies
+
+各平台 Cookie 文件位于 `credentials/cookies/` 目录。更新方式:
+
+- **视频号**:浏览器登录后,使用 `cookie_manager.py` 提取
+- **B站**:使用 `bilibili-api-python` 自动获取
+- **小红书/快手**:Playwright 自动化登录后提取
+
+---
+
+## 📚 相关文档
+
+- **主技能入口**:`SKILL.md`
+- **卡若创业派对项目**:`/Users/karuo/Documents/个人/卡若AI/02_卡人(水)/水岸_项目管理/卡若创业派对/README.md`
+- **运营报表脚本**:`/Users/karuo/Documents/个人/卡若AI/02_卡人(水)/水桥_平台对接/飞书管理/脚本/`
+- **视频切片脚本**:`/Users/karuo/Documents/个人/卡若AI/03_卡木(木)/木叶_视频内容/视频切片/脚本/`
+- **多平台分发脚本**:`/Users/karuo/Documents/个人/卡若AI/03_卡木(木)/木叶_视频内容/多平台分发/脚本/`
+
+---
+
+## 📋 版本记录
+
+| 版本 | 日期 | 说明 |
+|:---|:---|:---|
+| 1.0 | 2026-03-20 | 初版:整合运营报表、视频切片、多平台分发、飞书视频文字下载 4 大技能,统一凭证管理 |
diff --git a/.cursor/archive/skills-karuo-party/karuo-party/SKILL.md b/.cursor/archive/skills-karuo-party/karuo-party/SKILL.md
new file mode 100644
index 0000000..b0eabfe
--- /dev/null
+++ b/.cursor/archive/skills-karuo-party/karuo-party/SKILL.md
@@ -0,0 +1,304 @@
+---
+name: 卡若创业派对运营
+description: >
+ 卡若创业派对全链路运营技能包。包含运营报表、视频切片、多平台分发、飞书视频文字下载等核心能力。
+ 所有凭证(飞书TOKEN、各平台Cookies)统一管理在 credentials/ 目录。
+ 当用户提到 运营报表、视频切片、多平台分发、飞书视频下载、派对运营、卡若创业派对 时自动激活。
+triggers: 运营报表、视频切片、多平台分发、飞书视频下载、派对运营、卡若创业派对、派对填表、视频剪辑、一键分发、妙记下载
+owner: 水岸
+group: 运营
+version: "1.3"
+updated: "2026-03-24"
+---
+
+# 卡若创业派对运营 Skill 包
+
+> **项目定位**:Soul 创业派对全链路——从派对结束到内容变现
+> **技能包路径**:`.cursor/skills/karuo-party/`
+> **凭证目录**:`credentials/`(飞书TOKEN、各平台Cookies)
+
+---
+
+## 零、交互默认(派对 AI)
+
+- **直接操作**:报表、下载、切片、分发、脚本、凭证刷新等,**不向用户反问**「要不要跑」;按本文档路径**直接执行**终端命令(与全局 `karuo-ai.mdc`「默认零提问」一致)。
+- **缺凭证/Token**:先读 `credentials/` 与脚本内刷新逻辑;失败则**一句**说明缺哪份文件或环境变量,不展开选择题。
+- **收尾**:技术类任务仍可在会话末跟卡若复盘五块(多根工作区以 `soul-karuo-dialogue.mdc` 为准)。
+
+---
+
+## 一、技能包组成
+
+本技能包包含以下 8 个核心子技能:
+
+| # | 技能名 | 文件路径 | 触发词 | 用途 |
+|:--|:---|:---|:---|:---|
+| ① | Soul派对运营报表 | `skills/运营报表_SKILL.md` | 运营报表、派对填表、派对截图 | 截图→飞书表格→发群 |
+| ② | 飞书视频文字下载 | `skills/飞书视频文字下载_SKILL.md` | 妙记下载、飞书视频、飞书妙记 | 文字+视频→本地 |
+| ③ | 视频切片 | `skills/视频切片_SKILL.md` | 视频剪辑、切片发布 | 原视频→转录→高光→成片 |
+| ④ | 多平台分发 | `skills/多平台分发_SKILL.md` | 一键分发、全平台发布 | 成片→抖音/B站/视频号/小红书/快手 |
+| ⑤ | 视频号发布 | `skills/平台_视频号_SKILL.md` | 视频号发布、视频号重传 | 账号校验→清理→定时发布 |
+| ⑥ | B站发布 | `skills/平台_B站_SKILL.md` | B站发布、B站补发 | API优先→兜底→定时 |
+| ⑦ | 小红书发布 | `skills/平台_小红书_SKILL.md` | 小红书发布、小红书补发 | UI自动化定时发布 |
+| ⑧ | 抖音发布 | `skills/平台_抖音_SKILL.md` | 抖音发布、抖音补发 | API定时发布+失败重登 |
+
+---
+
+## 二、凭证管理
+
+所有凭证统一存储在 `credentials/` 目录:
+
+### 2.1 飞书凭证
+
+| 文件 | 说明 | 用途 |
+|:---|:---|:---|
+| `.feishu_tokens.json` | 飞书访问令牌 | 运营报表、智能纪要、素材库 |
+
+**Token 自动刷新**:所有脚本遇 401 自动用 refresh_token 刷新,无需手动。
+
+### 2.2 视频平台 Cookies
+
+| 平台 | Cookie 文件 | 有效期 | 状态 |
+|:---|:---|:---|:---|
+| 视频号 | `cookies/视频号_cookies.json` | ~24-48h | ✅ 可用 |
+| B站 | `cookies/B站_cookies.json` | ~6个月 | ✅ 可用 |
+| 小红书 | `cookies/小红书_cookies.json` | ~1-3天 | ✅ 可用 |
+| 快手 | `cookies/快手_cookies.json` | ~7-30天 | ⚠️ 需检查 |
+| 抖音 | `cookies/抖音_cookies.json` | ~2-4h | ❌ 账号封禁 |
+
+**Cookie 管理**:`cookie_manager.py` 统一管理,自动迁移、API 预检、防重复登录。
+
+---
+
+## 三、快速使用
+
+### 3.1 运营报表(派对结束后)
+
+```bash
+# 路径指向卡若AI原始脚本目录
+FEISHU_SCRIPT="/Users/karuo/Documents/个人/卡若AI/02_卡人(水)/水桥_平台对接/飞书管理/脚本"
+
+cd "$FEISHU_SCRIPT"
+python3 auto_log.py # 刷新Token(首次或过期时)
+python3 soul_party_to_feishu_sheet.py 115 # 填表+发群
+```
+
+**详细流程**:见 `skills/运营报表_SKILL.md`
+
+### 3.2 飞书视频文字下载
+
+```bash
+JIYAO_SCRIPT="/Users/karuo/Documents/个人/卡若AI/02_卡人(水)/水桥_平台对接/智能纪要/脚本"
+
+# 导出文字
+python3 "$JIYAO_SCRIPT/feishu_minutes_export_github.py" "<妙记链接>" -o "/Users/karuo/Documents/聊天记录/soul"
+
+# 下载视频
+python3 "$JIYAO_SCRIPT/feishu_minutes_download_video.py" "<妙记链接>" -o "/Users/karuo/Movies/soul视频/原视频"
+```
+
+**详细流程**:见 `skills/飞书视频文字下载_SKILL.md`
+
+### 3.3 视频切片
+
+```bash
+VIDEO_SCRIPT="/Users/karuo/Documents/个人/卡若AI/03_卡木(木)/木叶_视频内容/视频切片/脚本"
+
+eval "$(~/miniforge3/bin/conda shell.zsh hook)"
+conda activate mlx-whisper
+python3 "$VIDEO_SCRIPT/soul_slice_pipeline.py" --video "<原视频.mp4>" --clips 8 --two-folders
+```
+
+**详细流程**:见 `skills/视频切片_SKILL.md`
+
+### 3.4 多平台分发
+
+```bash
+DIST_SCRIPT="/Users/karuo/Documents/个人/卡若AI/03_卡木(木)/木叶_视频内容/多平台分发/脚本"
+
+# 默认智能错峰 + 静默(不自动弹窗扫视频号)
+python3 "$DIST_SCRIPT/distribute_all.py" --video-dir "<成片目录>"
+
+# 旧版随机间隔:加 --legacy-schedule;需要自动扫视频号:加 --auto-channels-login
+
+# 立即全部发布
+python3 "$DIST_SCRIPT/distribute_all.py" --now
+```
+
+**详细流程**:见 `skills/多平台分发_SKILL.md`
+
+#### 视频号发布前置(强制)
+
+在执行视频号发布前,固定做以下 3 步:
+
+1. **账号信息校验**:调用 `auth_data` 校验 `nickname` 与 `headImgUrl`,不一致先改到目标值再发。
+2. **线上失败/重复清理**:先查 `post_list`,删除失败条目;同标题仅保留最新一条(去重后再补发)。
+3. **仅定时发布**:禁止立即发布;若页面定时控件失效,使用 `post_create` 注入定时参数并拦截立即发布。
+
+#### 分发统一总规则(强制)
+
+1. **间隔规则**:按“每一条相邻发布时间”计算,必须在 **10 分钟到 120 分钟** 之间。
+2. **账号状态规则**:发布前做账号可用性检查;若出现封禁/登录失效/鉴权失败,先执行重登,再重试失败条。
+3. **平台分治规则**:先按平台触发对应子 Skill,再执行发布命令,不混用平台规则。
+4. **收敛规则**:每轮结束输出成功/失败清单 + 重登命令;失败条必须可继续重试直到收敛。
+
+---
+
+## 四、完整流程(派对结束后)
+
+### Phase 1:数据入库
+
+1. **运营报表**:提取效果数据 → 填表 → 发群
+2. **飞书妙记**:导出文字 → 下载视频
+
+### Phase 2:智能纪要
+
+1. 提炼纪要 JSON
+2. 生成纪要 HTML→PNG
+3. 纪要图入报表
+4. 纪要图发群
+
+### Phase 3:视频生产
+
+1. **视频切片**:转录 → 高光识别 → 批量切片 → 增强
+2. **多平台分发**:成片 → 5 平台发布(默认智能错峰定时)
+
+### Phase 4:文章内容
+
+1. 写第9章文章
+2. 上传小程序
+3. 推送飞书群
+
+**详细流程**:见卡若AI项目 `02_卡人(水)/水岸_项目管理/卡若创业派对/README.md`
+
+---
+
+## 五、凭证更新
+
+### 5.1 飞书 Token 更新
+
+```bash
+cd "/Users/karuo/Documents/个人/卡若AI/02_卡人(水)/水桥_平台对接/飞书管理/脚本"
+python3 auto_log.py
+```
+
+更新后,将 `.feishu_tokens.json` 复制到本技能包的 `credentials/` 目录。
+
+### 5.2 平台 Cookie 更新
+
+各平台 Cookie 文件位于 `credentials/cookies/` 目录。更新方式:
+
+1. **视频号**:`channels_login.py`(Cursor Simple Browser + 可选 CDP);详见 `skills/多平台分发_SKILL.md`
+2. **B站**:使用 `bilibili-api-python` 自动获取
+3. **小红书/快手**:Playwright 自动化登录后提取
+
+更新后,Cookie 文件会自动同步到本技能包。
+
+---
+
+## 六、目录结构
+
+```
+.cursor/skills/karuo-party/
+├── SKILL.md # 本文件(主入口)
+├── README.md # 说明文档
+├── credentials/ # 凭证目录
+│ ├── .feishu_tokens.json # 飞书Token
+│ └── cookies/ # 平台Cookies
+│ ├── 视频号_cookies.json
+│ ├── B站_cookies.json
+│ ├── 小红书_cookies.json
+│ ├── 快手_cookies.json
+│ └── 抖音_cookies.json
+└── skills/ # 子技能文件
+ ├── 运营报表_SKILL.md
+ ├── 飞书视频文字下载_SKILL.md
+ ├── 视频切片_SKILL.md
+ └── 多平台分发_SKILL.md
+```
+
+---
+
+## 七、使用说明
+
+### 7.1 激活方式
+
+当用户提到以下触发词时,自动激活本技能包:
+
+- **运营报表**、**派对填表**、**派对截图**
+- **飞书视频下载**、**妙记下载**、**飞书妙记**
+- **视频剪辑**、**切片发布**、**视频切片**
+- **多平台分发**、**一键分发**、**全平台发布**
+- **卡若创业派对**、**派对运营**
+
+### 7.2 执行流程
+
+1. **读取对应子技能**:根据用户需求,读取 `skills/` 目录下对应的 SKILL.md
+2. **检查凭证**:确认 `credentials/` 目录下相关凭证文件存在且有效
+3. **执行命令**:按子技能文档中的命令执行(路径指向卡若AI原始脚本目录)
+4. **结果反馈**:执行完成后反馈结果
+
+### 7.3 注意事项
+
+- **脚本路径**:所有脚本仍在卡若AI原始目录,本技能包仅提供技能文档和凭证管理
+- **凭证同步**:凭证更新后需手动复制到本技能包的 `credentials/` 目录
+- **跨平台兼容**:macOS 用 `python3`,Windows 用 `python`
+
+---
+
+## 八、相关文档
+
+- **卡若创业派对项目**:`/Users/karuo/Documents/个人/卡若AI/02_卡人(水)/水岸_项目管理/卡若创业派对/README.md`
+- **运营报表脚本**:`/Users/karuo/Documents/个人/卡若AI/02_卡人(水)/水桥_平台对接/飞书管理/脚本/`
+- **视频切片脚本**:`/Users/karuo/Documents/个人/卡若AI/03_卡木(木)/木叶_视频内容/视频切片/脚本/`
+- **多平台分发脚本**:`/Users/karuo/Documents/个人/卡若AI/03_卡木(木)/木叶_视频内容/多平台分发/脚本/`
+
+---
+
+## 九、闭环复盘发群(派对 AI · 强制)
+
+当本次对话完成**一个可交付的闭环**(需求→实现→自检,或运营链路单条跑通)时,**必须在回复末尾附带完整卡若复盘块**,并**推送到飞书群机器人**。
+
+### 9.1 复盘正文格式
+
+严格按卡若AI《卡若复盘格式_固定规则》:**`运营中枢/参考资料/卡若复盘格式_固定规则.md`**(五块 🎯📌💡📝▶,**复盘块内禁止表格**,标题带 **YYYY-MM-DD HH:mm**,**🎯 目标·结果·达成率** 整行 ≤30 字且含达成率百分比)。
+
+### 9.2 飞书推送(机器人 Webhook v2)
+
+- **凭证**:Webhook URL **不写死在仓库**;优先环境变量 **`FEISHU_PARTY_CLOSURE_WEBHOOK`**,或用户在对话中临时提供。
+- **请求体**(必须带 `msg_type`,否则返回 `params error, msg_type need`):
+
+```json
+{
+ "msg_type": "text",
+ "content": {
+ "text": "(与回复中复盘块一致的纯文本,可含换行)"
+ }
+}
+```
+
+- **执行方式**(任选其一,由 Agent 直接执行,不向用户索要确认):
+
+```bash
+# 将下方 URL 换为 FEISHU_PARTY_CLOSURE_WEBHOOK 或用户提供的 hook
+TEXT=$(python3 -c "import json,sys; print(json.dumps({'msg_type':'text','content':{'text':sys.argv[1]}}, ensure_ascii=False))" "$(cat /path/to/review.txt)")
+curl -sS -X POST -H "Content-Type: application/json" -d "$TEXT" "$FEISHU_PARTY_CLOSURE_WEBHOOK"
+```
+
+或使用单行 heredoc 时注意对引号转义;**禁止**只 POST 空 JSON 或缺 `msg_type` 的 body。
+
+### 9.3 触发边界
+
+- **要发群**:开发闭环(如 soul-admin/soul-api 联调交付)、运营闭环(报表填表+发群)、或用户明确要求「复盘发群」。
+- **可不发**:单句问答、未改代码的纯咨询(除非用户点名要发群)。
+
+---
+
+## 版本记录
+
+| 版本 | 日期 | 说明 |
+|:---|:---|:---|
+| 1.3 | 2026-03-24 | 升级为“总规则+平台子Skill”;统一相邻间隔 10~120 分钟;新增账号封禁/重登/重试收敛规则 |
+| 1.2 | 2026-03-23 | 新增视频号发布前置三步:头像昵称校验、失败/重复清理、强制定时发布(含请求注入兜底) |
+| 1.1 | 2026-03-21 | 新增 §九 闭环复盘发群:卡若五块复盘 + 飞书 Webhook v2(msg_type 必填) |
+| 1.0 | 2026-03-20 | 初版:整合运营报表、视频切片、多平台分发、飞书视频文字下载 4 大技能,统一凭证管理 |
diff --git a/.cursor/archive/skills-karuo-party/karuo-party/credentials/README.md b/.cursor/archive/skills-karuo-party/karuo-party/credentials/README.md
new file mode 100644
index 0000000..7dbda7b
--- /dev/null
+++ b/.cursor/archive/skills-karuo-party/karuo-party/credentials/README.md
@@ -0,0 +1,6 @@
+# 本机凭证目录
+
+将飞书 token、各平台 cookies 等**仅放在本机**,文件名与结构见上级 `karuo-party` 的 SKILL / README。
+
+- 本目录已在仓库根 `.gitignore` 中忽略,**不要**把真实密钥提交到 Git。
+- 若目录为空,按 `skills/karuo-party` 文档从模板复制并重命名即可。
diff --git a/.cursor/archive/skills-karuo-party/karuo-party/skills/多平台分发_SKILL.md b/.cursor/archive/skills-karuo-party/karuo-party/skills/多平台分发_SKILL.md
new file mode 100644
index 0000000..ef34db9
--- /dev/null
+++ b/.cursor/archive/skills-karuo-party/karuo-party/skills/多平台分发_SKILL.md
@@ -0,0 +1,226 @@
+---
+name: 多平台分发
+description: >
+ 一键将视频分发到 5 个平台(抖音、B站、视频号、小红书、快手)。
+ API 优先策略:视频号纯 API、B站 bilibili-api-python、抖音纯 API。
+ 支持定时排期(默认智能错峰;可选 legacy)、默认静默不弹窗登录、并行分发、去重、失败自动重试。
+triggers: 多平台分发、一键分发、全平台发布、批量分发、视频分发
+owner: 木叶
+group: 木
+version: "4.5"
+updated: "2026-03-24"
+---
+
+# 多平台分发 Skill(v4.5)
+
+> **核心原则**:API 发布为主,Playwright 为辅。确保确定性地分发到各平台。
+> **v4.5**:统一改为“按相邻条目”间隔控制,默认区间 **10~120 分钟**;并要求按平台子 Skill 执行对应规则后再发布。
+
+## 〇、执行原则(第一性原理)
+
+- **视频号两步**:先扫码落盘 Cookie 再上传;`video_channels_resume.py` **默认弹 Chromium 窗**扫码,扫完再继续传;`--silent-login` 无头。
+- **目标优先**:全平台分发 → 直接 `distribute_all.py`;视频号助手态需**事先**手动 `channels_login.py` 或显式 `--auto-channels-login`。
+- **Cookie 优先**:登录成功必须落盘;视频号双路径同步见 `cookie_manager.sync_channels_cookie_files`。
+- **默认静默**:无人值守跑命令时不弹浏览器。
+
+---
+
+## 一、平台与实现方式
+
+| 平台 | 实现方式 | 定时发布 | Cookie 有效期 | 120 场实测 |
+|------|----------|----------|---------------|------------|
+| **视频号** | **纯 API**(DFS 上传 + post_create) | API 原生支持 | ~24-48h | 12/12 成功 |
+| **B站** | **bilibili-api-python** API 优先 → Playwright 兜底 | API `dtime` | ~6 个月 | 12/12 成功 |
+| **小红书** | Playwright headless 自动化 | UI 定时(降级立即) | ~1-3 天 | 12/12 成功 |
+| **快手** | Playwright headless 自动化 | UI 定时 | ~7-30 天 | Cookie 过期 |
+| **抖音** | 纯 API(VOD + bd-ticket-guard) | API `timing_ts` | ~2-4h | 账号封禁中 |
+
+> **关于视频号官方 API 边界**:
+> 按《视频号与腾讯相关 API 整理》结论,微信官方目前**没有开放「短视频上传/发布」接口**;本 Skill 中的视频号发布能力,属于对 `https://channels.weixin.qq.com` 视频号助手网页协议的逆向封装(DFS 上传 + `post_create`),仅在你本机使用,需自行承担协议变更与合规风险。
+> 官方可控能力(直播记录、橱窗、留资、罗盘数据、本地生活等)的服务端 API 入口为:`https://developers.weixin.qq.com/doc/channels/api/`,如需做直播/橱窗/留资集成,可基于该文档在单独 Skill 中扩展。
+
+> **「视频号 API token」与成片上传**:公众号 **`access_token`** **不能**替代视频号助手网页态;`channels_api_publish` 依赖 **`channels_storage_state.json`**。127 场静默全平台:`python3 distribute_all.py --video-dir "/Users/karuo/Movies/soul视频/第127场_20260318_output/成片"`(须各平台 Cookie 已就绪)。
+
+---
+
+## 二、一键命令
+
+```bash
+cd /Users/karuo/Documents/个人/卡若AI/03_卡木(木)/木叶_视频内容/多平台分发/脚本
+
+# 默认智能错峰排期
+python3 distribute_all.py
+
+# 规则间隔(推荐,按相邻条目 10~120 分钟)
+python3 distribute_all.py --legacy-schedule --min-gap 10 --max-gap 120
+
+# 立即全部发布(仅在明确要求时)
+python3 distribute_all.py --now
+
+# 只发指定平台
+python3 distribute_all.py --platforms 视频号 B站
+
+# 自定义视频目录
+python3 distribute_all.py --video-dir "/path/to/videos/"
+
+# 检查 Cookie / 重试失败
+python3 distribute_all.py --check
+python3 distribute_all.py --retry
+
+# 需要脚本自动弹窗扫视频号(默认不弹)
+python3 distribute_all.py --platforms 视频号 --auto-channels-login --video-dir "/path/to/成片"
+# 独立 channels_api_publish 允许自动登录:CHANNELS_AUTO_LOGIN=1
+# 强制永不自动登录:NO_AUTO_CHANNELS_LOGIN=1
+
+# 平台站点上传 CLI(平台级,含账号检测+自动重登+重试)
+python3 site_upload_cli.py check --platforms 抖音 B站 小红书 快手
+python3 site_upload_cli.py publish --platforms 抖音 B站 小红书 --video-dir "/path/to/成片" --min-gap 10 --max-gap 120 --until-success
+```
+
+---
+
+## 三、定时排期(v4.2+)
+
+### 3.1 默认(`generate_smart_schedule`)
+- 第 1 条立即;间隔与总跨度随条数自适应;本地 0–7 点尽量挪到午间(`SCHEDULE_NO_NIGHT_REFINE=1` 关闭)
+- `--legacy-schedule` + `--min-gap` / `--max-gap` / `--max-hours` 为固定区间逻辑(建议 `10~120`)
+- 去重时排期与目录列表下标对齐
+
+### 3.2 独立 `channels_api_publish.py`:同上智能排期转 Unix
+
+### 3.3 各平台定时实现
+
+| 平台 | 定时方式 | 参数 |
+|------|----------|------|
+| B站 | API `meta.dtime` | Unix 时间戳(秒) |
+| 视频号 | API `postTimingInfo.postTime`(秒级 Unix);过近时间自动顺延,不允许立即发 | `channels_api_publish._scheduled_ts_for_channels` |
+| 抖音 | API `timing_ts` | Unix 时间戳 |
+| 快手 | Playwright UI | `schedule_helper.py` |
+| 小红书 | Playwright UI | `schedule_helper.py` |
+
+---
+
+## 四、元数据自动生成(v4.0 新增)
+
+`video_metadata.py` 根据文件名自动生成各平台差异化内容:
+
+```python
+from video_metadata import VideoMeta
+meta = VideoMeta.from_filename("AI最大的缺点是上下文太短.mp4")
+
+meta.title("B站") # 优化后的标题
+meta.description("B站") # 标题 + 标签 + 品牌标记
+meta.tags_str("B站") # AI工具,效率提升,Soul派对,...
+meta.bilibili_meta() # B站投稿完整 meta(含 tid/tag/desc)
+meta.title_short() # 小红书短标题(≤20字)
+meta.hashtags("视频号") # … + #小程序卡若创业派对 #公众号卡若-4点起床的男人
+```
+
+### 4.1 内容结构
+- **标题**:手工优化标题库优先,否则从文件名智能提取
+- **简介**:标题 + 换行 + 话题标签;**视频号**固定追加 `#小程序卡若创业派对` `#公众号卡若-4点起床的男人`(其它平台仍为 `#小程序 卡若创业派对`)
+- **标签**:基于关键词匹配(AI/创业/副业/Soul 等 12 类)+ 通用标签
+- **分区**:B站 tid=160(生活>日常)
+- **风控过滤**:`content_filter.py` 自动替换敏感词(70+ 映射,严格/宽松分级)
+
+---
+
+## 五、商品链接/小黄车(调研结果)
+
+| 平台 | 功能 | 实现方式 | 状态 |
+|------|------|----------|------|
+| B站 | 花火计划商品链接 | 需企业认证 + 品牌合作授权 | 需手动配置 |
+| 视频号 | 挂小程序 | 视频号主页 > 设置 > 服务菜单 > 小程序 | 需手动配置 |
+| 抖音 | 小黄车 | 需开通橱窗(粉丝 ≥1000) | 账号封禁 |
+| 快手 | 商品卡片 | 需开通快手小店 | 需手动配置 |
+| 小红书 | 商品笔记 | 需开通小红书店铺 | 需手动配置 |
+
+**当前做法**:在描述中统一添加 `#小程序 卡若创业派对` 引导用户搜索。
+
+---
+
+## 六、Cookie 管理
+
+`cookie_manager.py` 统一管理:
+- 中央存储:`多平台分发/cookies/{平台}_cookies.json`
+- 自动迁移:旧路径 → 中央存储(首次使用时)
+- **视频号双路径**:预检读中央、发布读 legacy;`sync_channels_cookie_files()` 按 **mtime 新者覆盖旧者**,避免两份不一致
+- **登录后必存**:`channels_login.py` 保存 `channels_storage_state.json` 后 **立即 copy** 到 `cookies/视频号_cookies.json`
+- **登录页只在 Cursor 内**:`channels_login.py` v7 用 `cursor://vscode.simple-browser/show?url=…` 打开 **Simple Browser**,不用系统默认浏览器;**无额外 Chromium** 时需 Cursor 带 `--remote-debugging-port=9223`(或 `CHANNELS_CDP_URL`),脚本 CDP 附着后导出会话;否则 **自动回退** 本机 Chromium 仅用于写 Cookie(`--playwright-only` 强制只走 Chromium)。
+- **视频号登录**:默认不自动执行 `channels_login.py`;需要时加 `--auto-channels-login`(或事先手动登录)
+- API 预检:各平台 auth API 校验有效性
+
+---
+
+## 六点五、视频号发布前置检查(强制)
+
+每次发布视频号前,必须先跑:
+
+1. `auth/auth_data`:校验 `nickname` 与 `headImgUrl`(不一致先改号资料,再执行发布)。
+2. `post/post_list`:筛查失败条目并删除。
+3. 同标题去重:若存在多条,仅保留最新 `objectId`,其余调用 `post/post_delete` 删除。
+4. 发布阶段若页面定时控件失败,改为 `post_create` 请求注入 `postTimingInfo`,继续定时发布;注入也失败则中止该条(防止误发立即)。
+
+---
+
+## 六点六、账号异常自动处理(强制)
+
+1. 发布前先执行 `--check`;若平台 Cookie 无效,先走对应登录脚本。
+2. 发布中若出现封禁/风控/鉴权失败,立即输出处理建议与重登命令。
+3. 重登完成后优先执行 `--retry`,只重试失败条,不重复成功条。
+4. 失败超过 2 轮仍未收敛时,输出平台级阻塞原因并停止盲目重试。
+
+---
+
+## 六点七、平台子Skill分流(强制)
+
+- 视频号:按 `平台_视频号_SKILL.md` 执行。
+- B站:按 `平台_B站_SKILL.md` 执行。
+- 小红书:按 `平台_小红书_SKILL.md` 执行。
+- 抖音:按 `平台_抖音_SKILL.md` 执行。
+
+多平台联发时,先加载各平台子Skill,再统一调度 `distribute_all.py`。
+
+---
+
+## 七、去重机制
+
+- 日志:`publish_log.json`(JSON Lines)
+- 去重键:`(平台名, 视频文件名)`
+- 双保险:调度器层 + 平台层
+- `--no-dedup` 跳过,`--retry` 重跑失败
+
+---
+
+## 八、目录结构
+
+```
+木叶_视频内容/
+├── 多平台分发/ ← 本 Skill(调度器 + 共享工具)
+│ ├── SKILL.md
+│ └── 脚本/
+│ ├── distribute_all.py # 主调度器 v4
+│ ├── video_metadata.py # 统一元数据生成器(v4 新增)
+│ ├── schedule_generator.py # 定时排期(v4: 第1条立即发)
+│ ├── schedule_helper.py # Playwright 定时 UI 辅助
+│ ├── publish_result.py # 统一 PublishResult + 去重
+│ ├── title_generator.py # 标题生成(被 video_metadata 取代)
+│ ├── content_filter.py # 敏感词过滤(70+ 映射)
+│ ├── cookie_manager.py # Cookie 统一管理(5 平台 API 预检)
+│ ├── video_utils.py # 视频处理(封面、元数据)
+│ └── publish_log.json # 发布日志
+├── 抖音发布/ ← 纯 API(账号封禁中)
+├── B站发布/ ← bilibili-api-python API
+├── 视频号发布/ ← 纯 API(DFS 协议,v5)
+├── 小红书发布/ ← Playwright headless
+└── 快手发布/ ← Playwright headless
+```
+
+---
+
+## 九、依赖
+
+- Python 3.10+
+- httpx, bilibili-api-python, playwright, Pillow
+- ffmpeg/ffprobe(系统已安装)
+- `playwright install chromium`
diff --git a/.cursor/archive/skills-karuo-party/karuo-party/skills/平台_B站_SKILL.md b/.cursor/archive/skills-karuo-party/karuo-party/skills/平台_B站_SKILL.md
new file mode 100644
index 0000000..1b377cf
--- /dev/null
+++ b/.cursor/archive/skills-karuo-party/karuo-party/skills/平台_B站_SKILL.md
@@ -0,0 +1,34 @@
+---
+name: 平台_B站发布
+description: B站发布专用规则。API优先,失败降级 Playwright,按相邻间隔 10~120 分钟定时发布。
+triggers: B站发布、B站补发、B站重试
+owner: 木叶
+group: 木
+version: "1.0"
+updated: "2026-03-24"
+---
+
+# 平台子Skill:B站发布
+
+## 一、强制规则
+
+1. 默认 API 投稿,失败才降级 Playwright。
+2. 发布间隔按相邻条目控制在 `10~120` 分钟。
+3. 出现 406/超时等异常时先重试失败条,不重复成功条。
+
+## 二、标准命令
+
+```bash
+python3 "/Users/karuo/Documents/个人/卡若AI/03_卡木(木)/木叶_视频内容/多平台分发/脚本/distribute_all.py" \
+ --platforms B站 \
+ --video-dir "<成片目录>" \
+ --legacy-schedule --min-gap 10 --max-gap 120
+```
+
+## 三、登录异常处理
+
+```bash
+python3 "/Users/karuo/Documents/个人/卡若AI/03_卡木(木)/木叶_视频内容/B站发布/脚本/bilibili_login.py"
+python3 "/Users/karuo/Documents/个人/卡若AI/03_卡木(木)/木叶_视频内容/多平台分发/脚本/distribute_all.py" --retry
+```
+
diff --git a/.cursor/archive/skills-karuo-party/karuo-party/skills/平台_小红书_SKILL.md b/.cursor/archive/skills-karuo-party/karuo-party/skills/平台_小红书_SKILL.md
new file mode 100644
index 0000000..88ba07b
--- /dev/null
+++ b/.cursor/archive/skills-karuo-party/karuo-party/skills/平台_小红书_SKILL.md
@@ -0,0 +1,34 @@
+---
+name: 平台_小红书发布
+description: 小红书发布专用规则。UI自动化定时发布,按相邻间隔 10~120 分钟执行。
+triggers: 小红书发布、小红书补发
+owner: 木叶
+group: 木
+version: "1.0"
+updated: "2026-03-24"
+---
+
+# 平台子Skill:小红书发布
+
+## 一、强制规则
+
+1. 逐条定时发布,禁止批量立即发布。
+2. 相邻发布时间间隔必须在 `10~120` 分钟。
+3. 若仅返回 likely_published,需在下一轮巡检确认状态。
+
+## 二、标准命令
+
+```bash
+python3 "/Users/karuo/Documents/个人/卡若AI/03_卡木(木)/木叶_视频内容/多平台分发/脚本/distribute_all.py" \
+ --platforms 小红书 \
+ --video-dir "<成片目录>" \
+ --legacy-schedule --min-gap 10 --max-gap 120
+```
+
+## 三、登录异常处理
+
+```bash
+python3 "/Users/karuo/Documents/个人/卡若AI/03_卡木(木)/木叶_视频内容/小红书发布/脚本/xiaohongshu_login.py"
+python3 "/Users/karuo/Documents/个人/卡若AI/03_卡木(木)/木叶_视频内容/多平台分发/脚本/distribute_all.py" --retry
+```
+
diff --git a/.cursor/archive/skills-karuo-party/karuo-party/skills/平台_抖音_SKILL.md b/.cursor/archive/skills-karuo-party/karuo-party/skills/平台_抖音_SKILL.md
new file mode 100644
index 0000000..316b102
--- /dev/null
+++ b/.cursor/archive/skills-karuo-party/karuo-party/skills/平台_抖音_SKILL.md
@@ -0,0 +1,34 @@
+---
+name: 平台_抖音发布
+description: 抖音发布专用规则。API定时发布,按相邻间隔 10~120 分钟;遇风控/封禁优先账号处理。
+triggers: 抖音发布、抖音补发、抖音重试
+owner: 木叶
+group: 木
+version: "1.0"
+updated: "2026-03-24"
+---
+
+# 平台子Skill:抖音发布
+
+## 一、强制规则
+
+1. 仅按定时发布,不做整批立即。
+2. 相邻发布时间间隔 `10~120` 分钟。
+3. 若出现封禁/风控提示,立刻停止盲目重试并提示账号处理。
+
+## 二、标准命令
+
+```bash
+python3 "/Users/karuo/Documents/个人/卡若AI/03_卡木(木)/木叶_视频内容/多平台分发/脚本/distribute_all.py" \
+ --platforms 抖音 \
+ --video-dir "<成片目录>" \
+ --legacy-schedule --min-gap 10 --max-gap 120
+```
+
+## 三、登录异常处理
+
+```bash
+python3 "/Users/karuo/Documents/个人/卡若AI/03_卡木(木)/木叶_视频内容/抖音发布/脚本/douyin_login.py"
+python3 "/Users/karuo/Documents/个人/卡若AI/03_卡木(木)/木叶_视频内容/多平台分发/脚本/distribute_all.py" --retry
+```
+
diff --git a/.cursor/archive/skills-karuo-party/karuo-party/skills/平台_视频号_SKILL.md b/.cursor/archive/skills-karuo-party/karuo-party/skills/平台_视频号_SKILL.md
new file mode 100644
index 0000000..520b5b4
--- /dev/null
+++ b/.cursor/archive/skills-karuo-party/karuo-party/skills/平台_视频号_SKILL.md
@@ -0,0 +1,38 @@
+---
+name: 平台_视频号发布
+description: 视频号发布专用规则。发布前校验账号,清理密集/失败条目,按相邻间隔 10~120 分钟定时发布。
+triggers: 视频号发布、视频号重传、视频号补发
+owner: 木叶
+group: 木
+version: "1.0"
+updated: "2026-03-24"
+---
+
+# 平台子Skill:视频号发布
+
+## 一、强制规则
+
+1. 发布前必须校验 `auth_data`(昵称、头像、登录态)。
+2. 发布前必须检查 `post_list`:失败条目删除、同标题去重。
+3. 仅允许定时发布,按相邻条目间隔 `10~120` 分钟。
+4. 定时控件失败时允许请求注入;注入失败则中止该条,禁止误发立即。
+
+## 二、标准命令
+
+```bash
+python3 "/Users/karuo/Documents/个人/卡若AI/03_卡木(木)/木叶_视频内容/视频号发布/脚本/channels_web_cli.py" \
+ publish-dir \
+ --video-dir "<成片目录>" \
+ --legacy-schedule --min-gap 10 --max-gap 120 \
+ --start-after-min 10
+```
+
+## 三、登录异常处理
+
+- 若出现 `300334` / `300002` / `finder_raw` 缺失:立即执行重登。
+- 重登命令:
+
+```bash
+python3 "/Users/karuo/Documents/个人/卡若AI/03_卡木(木)/木叶_视频内容/视频号发布/脚本/channels_login.py" --playwright-only
+```
+
diff --git a/.cursor/archive/skills-karuo-party/karuo-party/skills/视频切片_SKILL.md b/.cursor/archive/skills-karuo-party/karuo-party/skills/视频切片_SKILL.md
new file mode 100644
index 0000000..6d84b5b
--- /dev/null
+++ b/.cursor/archive/skills-karuo-party/karuo-party/skills/视频切片_SKILL.md
@@ -0,0 +1,583 @@
+---
+name: 视频切片
+description: Soul派对视频切片 + 快速混剪 + 切片动效包装(片头/片尾/程序化)+ 剪映思路借鉴(智能剪口播/镜头分割)。触发词含视频剪辑、切片发布、快速混剪、切片动效包装、程序化包装、片头片尾。
+group: 木
+triggers: 视频剪辑、切片发布、字幕烧录、**快速混剪、混剪预告、快剪串联、切片动效包装、程序化包装、片头片尾、批量封面、视频包装**、镜头切分、场景检测
+owner: 木叶
+version: "1.3"
+updated: "2026-03-03"
+---
+
+# 视频切片
+
+> **语言**:所有文档、字幕、封面文案统一使用**简体中文**。soul_enhance 自动繁转简。
+
+> **Soul 视频输出**:Soul 剪辑的成片统一导出到 `/Users/karuo/Movies/soul视频/最终版/`,原视频在 `原视频/`,中间产物在 `其他/`。
+
+> **联动规则**:每次执行视频切片时,自动检查是否需要「切片动效包装」或「快速混剪」。若用户提到片头/片尾/程序化包装/批量封面,则联动调用 `切片动效包装/10秒视频` 模板渲染,再与切片合成。若用户提到快速混剪/混剪预告/快剪串联,则在切片或成片生成后再调用 `脚本/quick_montage.py` 输出一条节奏版预告。
+
+## ⭐ Soul派对切片流程(默认)
+
+```
+原始视频 → MLX转录 → 字幕转简体 → 高光识别(API 优先/最佳模型,失败则 Ollama→规则) → 批量切片 → soul_enhance → 输出成片
+ ↑ ↓
+ 提取后立即繁转简+修正错误 封面+字幕(已简体)+加速10%+去语气词
+```
+
+**切片时长**:每段为**完整的一个片段**,时长 **30 秒~300 秒**,由该完整片段起止时间决定。**标题**用一句**刺激性观点**(见 `Soul竖屏切片_SKILL.md`)。
+
+**提问→回答 结构**:若片段内有人提问,前3秒优先展示**提问问题**,再播回答;高光识别填 `question` 且 `hook_3sec` 与之一致,成片整条去语助词。详见 `参考资料/视频结构_提问回答与高光.md`、`参考资料/高光识别提示词.md`。
+
+**Soul 竖屏专用**:抖音/首页用竖屏成片、完整参数与流程见 → **`Soul竖屏切片_SKILL.md`**(竖屏 498×1080、crop 参数、批量命令)。
+
+### 最新切片风格(当前默认)
+
+以后默认按这套风格出切片与成片:
+
+| 项 | 当前默认风格 |
+|------|------|
+| **封面** | **Soul 绿 + 半透明质感 + 深色渐变** |
+| **前3秒** | **优先提问→回答**,有提问时 Hook = `question` |
+| **标题** | **一句刺激性观点**,文件名 = 封面标题 = `highlights.title` |
+| **字幕** | 居中、白字黑描边、关键词亮金黄高亮 |
+| **节奏** | 去语助词 + 整体加速 10% |
+| **成片尺寸** | 竖屏 **498×1080** |
+
+这套风格与 `参考资料/高光识别提示词.md`、`参考资料/热点切片_标准流程.md`、`Soul竖屏切片_SKILL.md` 保持一致。
+
+### 一键命令(Soul派对专用)
+
+#### 一体化流水线(推荐)
+
+```bash
+cd 03_卡木(木)/木叶_视频内容/视频切片/脚本
+conda activate mlx-whisper
+python3 soul_slice_pipeline.py --video "/path/to/soul派对会议第57场.mp4" --clips 6
+
+# 仅重新烧录(字幕转简体后重跑增强)
+python3 soul_slice_pipeline.py -v "视频.mp4" -n 6 --skip-transcribe --skip-highlights --skip-clips
+
+# 切片+成片后,额外生成一条快速混剪
+python3 soul_slice_pipeline.py -v "视频.mp4" -n 8 --two-folders --quick-montage
+```
+
+流程:**转录 → 字幕转简体 → 高光识别 → 批量切片 → 增强**
+
+#### 分步命令
+
+```bash
+# 1. 转录(MLX Whisper,约3分钟/2.5小时视频)
+eval "$(~/miniforge3/bin/conda shell.zsh hook)"
+conda activate mlx-whisper
+mlx_whisper audio.wav --model mlx-community/whisper-small-mlx --language zh --output-format all
+
+# 2. 高光识别(API 优先,未配置则 Ollama → 规则;流水线会在读取 transcript 前自动转简体)
+python3 identify_highlights.py -t transcript.srt -o highlights.json -n 6
+# 需配置 OPENAI_API_KEY 或 OPENAI_API_BASES/KEYS/MODELS,默认模型 gpt-4o
+
+# 3. 切片
+python3 batch_clip.py -i 视频.mp4 -l highlights.json -o clips/ -p soul
+
+# 4. 增强处理(封面+字幕+加速,soul_enhance)
+python3 soul_enhance.py -c clips/ -l highlights.json -t transcript.srt -o clips_enhanced/
+```
+
+### 快速混剪(新增)
+
+适用场景:已经有 `切片/` 或 `成片/`,需要快速出一条 20~40 秒节奏版预告、招商预热视频、短视频串联版。
+
+**默认策略**:
+
+| 项 | 规则 |
+|------|------|
+| **顺序** | 优先按 `virality_score` / `rank` 排序;无分数时按序号 |
+| **取样** | 每条默认截取 **4 秒**高密度片段 |
+| **成片目录输入** | 自动跳过前 **2.6 秒**封面,避免混剪里全是封面 |
+| **输出** | 统一分辨率、统一节奏后拼成一条 `快速混剪.mp4` |
+
+```bash
+# 从成片目录生成快速混剪(推荐)
+python3 脚本/quick_montage.py \
+ -i "/path/to/成片" \
+ -o "/path/to/快速混剪.mp4" \
+ -l "/path/to/highlights.json" \
+ --source-kind final \
+ -n 8 \
+ -s 4
+
+# 一体化流水线里直接附带生成
+python3 脚本/soul_slice_pipeline.py \
+ -v "/path/to/原视频.mp4" \
+ --two-folders \
+ --quick-montage \
+ --montage-source finals \
+ --montage-max-clips 8 \
+ --montage-seconds 4
+```
+
+#### 按章节主题提取(推荐:第9章单场成片)
+
+以**章节 .md 正文**为来源提取核心主题,再在转录稿中匹配时间,不限于 5 分钟、片段数与章节结构一致。详见 `参考资料/主题片段提取规则.md`。
+
+```bash
+# 从章节生成 highlights,再走 batch_clip + soul_enhance
+python3 chapter_themes_to_highlights.py -c "第112场.md" -t transcript.srt -o highlights_from_chapter.json
+python3 batch_clip.py -i 视频.mp4 -l highlights_from_chapter.json -o clips/ -p soul112
+python3 soul_enhance.py -c clips/ -l highlights_from_chapter.json -t transcript.srt -o clips_enhanced/
+```
+
+- **主题来源**:章节 .md 按 `---` 分块,每块一个主题;文件名由 batch_clip 按 `前缀_序号_标题` 生成(标题仅保留中文与安全字符)。
+
+### Soul 竖屏成片(横版源 → 竖屏中段去白边)
+
+**约定**:以后剪辑 Soul 视频,成片统一做「竖屏中段」裁剪:横版 1920×1080 只保留中间竖条并去掉左右白边,输出 498×1080 竖屏。
+
+| 步骤 | 说明 |
+|------|------|
+| 源 | 横版 1920×1080(soul_enhance 输出) |
+| 1 | 取竖条 608×1080,起点 **x=483**(相对画面左) |
+| 2 | 裁掉左侧白边 60px、右侧白边 50px → 内容区宽 498 |
+| 输出 | **498×1080** 竖屏,仅内容窗口 |
+
+**FFmpeg 一条命令(固定参数):**
+
+```bash
+# 单文件。输入为 1920×1080 的 enhanced 成片
+ffmpeg -y -i "输入_enhanced.mp4" -vf "crop=608:1080:483:0,crop=498:1080:60:0" -c:a copy "输出_竖屏中段.mp4"
+```
+
+**批量对某目录下所有 \*_enhanced.mp4 做竖屏中段:**
+
+```bash
+# 脚本目录下执行,或直接调用
+python3 脚本/soul_vertical_crop.py --dir "/path/to/clips_enhanced" --suffix "_竖屏中段"
+```
+
+参数说明见:`参考资料/竖屏中段裁剪参数说明.md`。
+
+### 增强功能说明
+
+| 功能 | 说明 |
+|------|------|
+| **封面贴片** | 前2.5秒 Hook,苹方/思源黑体 |
+| **字幕烧录** | 关键词加粗加大亮金黄突出,去语助词+去空格 |
+| **加速10%** | 节奏更紧凑,适合短视频 |
+
+### 时间预估
+
+| 步骤 | 2.5小时视频 |
+|------|------------|
+| MLX转录 | 3分钟 |
+| 切片10个 | 2分钟 |
+| 增强处理 | 8分钟 |
+| **总计** | **约13分钟** |
+
+---
+
+## AI 生成与 LTX 可选集成
+
+在「已有录播 → 转录→高光→切片→成片」主流程外,可选用 **LTX**(GitHub: Lightricks/LTX-Video、LTX-2、LTX-Desktop-MPS)实现:
+
+| 能力 | 用途 |
+|------|------|
+| **Retake**(LTX-2 / LTX Desktop) | 对已有视频**某段时间**重生成,替换口误/补拍,再走成片流程 |
+| **Text/Image/Audio to video** | AI 生成口播替代、片头片尾、插播片段,生成 mp4 后进 `切片/` 或成片流程 |
+| **Video extension** | 片段前后自然延长,衔接切片 |
+| **自动 Prompt 增强** | 高光/标题文案 → 更易被生成模型理解,便于 I2V/Retake |
+
+**详细能力表与 API/本地/Desktop 接入**:见 `参考资料/LTX_能力与集成说明.md`。
+**Soul 竖屏场景**:见 `Soul竖屏切片_SKILL.md` 第九节「AI 生成与 LTX 可选集成」。
+**约定**:LTX 生成的片段统一经 soul_enhance(封面+字幕+竖屏)输出,与录播成片一致。
+
+---
+
+## 📹 通用视频处理
+
+一键处理视频:转录 → 字幕清洗 → 视频增强 → 烧录字幕 → **输出单个成片**
+
+---
+
+## ⚡ 一键命令
+
+```bash
+# 最简用法 - 输出: 视频名_带字幕.mp4
+python3 /Users/karuo/Documents/个人/卡若AI/04_效率工具/视频切片/scripts/one_video.py -i "视频.mp4"
+
+# 指定输出路径
+python3 scripts/one_video.py -i "视频.mp4" -o "成片.mp4"
+```
+
+### 处理流程
+
+```
+┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
+│ 提取音频 │──▶│MLX转录 │──▶│字幕清洗 │──▶│视频增强 │──▶│烧录字幕 │
+│ (5秒) │ │(1-3分钟)│ │繁转简 │ │降噪美颜 │ │ │
+└─────────┘ └─────────┘ └─────────┘ └─────────┘ └─────────┘
+ │
+ ▼
+ ┌────────────────┐
+ │ 单个带字幕成片 │
+ │ 可直接发布 │
+ └────────────────┘
+```
+
+### 时间预估
+
+| 视频时长 | 处理时间 |
+|---------|---------|
+| 5分钟 | 1-2分钟 |
+| 30分钟 | 5-8分钟 |
+| 1小时 | 10-15分钟 |
+
+---
+
+## 🎯 自动优化项
+
+脚本自动完成以下优化,无需手动操作:
+
+| 优化项 | 说明 |
+|--------|------|
+| 繁转简 | 自动将繁体字幕转为简体 |
+| 去语气词 | 删除"嗯"、"啊"、"那个"等 |
+| 修正错误 | 自动修正常见转录错误 |
+| 音频降噪 | FFT降噪+高低频过滤 |
+| 画面美颜 | 亮度+饱和度微调 |
+| 音量标准化 | 统一音量级别 |
+
+---
+
+## 📝 手动分步操作
+
+如需更精细控制,可分步执行:
+
+### 1. 转录
+
+```bash
+# 激活环境
+eval "$(~/miniforge3/bin/conda shell.zsh hook)"
+conda activate mlx-whisper
+
+# 提取音频
+ffmpeg -y -i "视频.mp4" -vn -ar 16000 -ac 1 audio.wav
+
+# MLX Whisper转录
+mlx_whisper audio.wav --model mlx-community/whisper-small-mlx --language zh --output-format srt
+```
+
+### 2. 字幕清洗
+
+```bash
+# 繁转简+修正错误
+python3 scripts/fix_subtitles.py --input transcript.srt --output clean.srt
+```
+
+### 3. 视频增强
+
+```bash
+# 降噪+美颜
+ffmpeg -y -i "视频.mp4" \
+ -vf "eq=brightness=0.05:saturation=1.1" \
+ -af "afftdn=nf=-25:nr=10:nt=w,highpass=f=80,lowpass=f=8000,volume=1.2" \
+ -c:v h264_videotoolbox -b:v 5M \
+ -c:a aac -b:a 128k \
+ enhanced.mp4
+```
+
+### 4. 烧录字幕
+
+```bash
+# Clean版(推荐)
+python3 scripts/burn_subtitles_clean.py -i enhanced.mp4 -s clean.srt -o 成片.mp4
+```
+
+---
+
+## 🎨 字幕样式
+
+### 默认样式(Clean版)
+
+| 元素 | 字号 | 颜色 | 效果 |
+|------|------|------|------|
+| 内容字幕 | 42px(竖屏)/ 36px(横屏) | 白色 | 黑色描边,无阴影 |
+| 关键词 | 同上 | 金黄色 | 自动高亮 |
+
+### 关键词高亮列表
+
+自动高亮的关键词(金黄色):
+- 数字:100万、30万、10万、5万、1万
+- 概念:私域、AI、自动化、矩阵、IP、获客、变现、转化
+- 平台:抖音、公众号、微信、存客宝
+
+---
+
+## 🔊 音频处理参数
+
+| 滤镜 | 作用 | 参数 |
+|------|------|------|
+| highpass | 去低频杂音 | f=80Hz |
+| lowpass | 去高频噪音 | f=8000Hz |
+| afftdn | FFT降噪 | nf=-25, nr=10 |
+| volume | 音量调整 | 1.2倍 |
+
+---
+
+## 📁 脚本列表
+
+| 脚本 | 功能 | 使用频率 |
+|------|------|---------|
+| **soul_slice_pipeline.py** | Soul 切片一体化流水线 | ⭐⭐⭐ 最常用 |
+| **soul_enhance.py** | 封面+字幕(简体)+加速+去语气词 | ⭐⭐⭐ |
+| **soul_vertical_crop.py** | Soul 竖屏中段批量裁剪(横版→498×1080 去白边) | ⭐⭐⭐ |
+| **kill_ffmpeg_when_clip_done.py** | 剪辑结束后自动关掉 ffmpeg(监视剪映/PID 或立即杀) | ⭐ 按需 |
+| **scene_detect_to_highlights.py** | 镜头/场景检测 → highlights.json(PySceneDetect,可接 batch_clip) | ⭐⭐ |
+| chapter_themes_to_highlights.py | 按章节 .md 主题提取片段(本地模型→highlights.json) | ⭐⭐⭐ |
+| identify_highlights.py | 高光识别(API 优先→Ollama→规则,默认 gpt-4o) | ⭐⭐ |
+| batch_clip.py | 批量切片 | ⭐⭐ |
+| one_video.py | 单视频一键成片 | ⭐⭐ |
+| burn_subtitles_clean.py | 字幕烧录(无阴影) | ⭐ |
+| fix_subtitles.py | 字幕清洗(繁转简) | ⭐ |
+
+---
+
+## 🛠 环境配置
+
+### 已安装(默认使用MLX Whisper)
+
+- **MLX Whisper**: `~/miniforge3/envs/mlx-whisper` ⭐ **默认转录引擎**
+ - Apple Silicon优化,比CPU Whisper快10倍+
+ - 2.5小时视频转录仅需3分钟
+- **字体**: `03_卡木(木)/木叶_视频内容/视频切片/fonts/`(优先)
+- **字幕**: 统一简体中文(soul_enhance 自动繁转简)
+
+### 转录命令(默认)
+
+```bash
+# 激活MLX环境
+eval "$(~/miniforge3/bin/conda shell.zsh hook)"
+conda activate mlx-whisper
+
+# MLX Whisper转录(推荐)
+mlx_whisper audio.wav --model mlx-community/whisper-small-mlx --language zh --output-format all
+```
+
+### 高光识别模型(API 优先)
+
+高光识别默认使用**当前可用最佳模型**:优先走 **OpenAI 兼容 API**(见下),未配置或失败时再用本地 Ollama,最后规则兜底。
+
+- **单接口**:`OPENAI_API_BASE`、`OPENAI_API_KEY`、`OPENAI_MODEL`(默认 `gpt-4o`)。
+- **多接口故障切换**:`OPENAI_API_BASES`、`OPENAI_API_KEYS`、`OPENAI_MODELS`(逗号分隔,按顺序尝试)。
+- 不写死密钥,从环境变量读取;详见 `运营中枢/参考资料/卡若AI异常处理与红线.md` 与 API 稳定性规则。
+
+### 依赖检查
+
+```bash
+# FFmpeg
+ffmpeg -version
+
+# MLX环境
+eval "$(~/miniforge3/bin/conda shell.zsh hook)"
+conda activate mlx-whisper
+python -c "import mlx_whisper; print('OK')"
+
+# Python库
+pip3 list | grep -E "moviepy|Pillow|opencc|openai"
+```
+
+### 安装依赖
+
+```bash
+pip3 install --break-system-packages moviepy Pillow opencc-python-reimplemented
+
+# 镜头切分(可选):PySceneDetect
+pip3 install 'scenedetect[opencv]'
+```
+
+---
+
+## 🔧 剪辑结束后自动关 ffmpeg
+
+脚本 **soul_enhance**、**batch_clip**、**soul_slice_pipeline** 在退出时(含 Ctrl+C)会自动结束本进程启动的 ffmpeg 子进程,避免剪辑结束后仍占用 CPU。
+
+若使用 **剪映/VideoFusion** 等 GUI 剪辑,可先运行监视脚本,剪辑应用退出后自动杀 ffmpeg:
+
+```bash
+# 先启动监视,再打开剪映;关掉剪映后会自动结束 ffmpeg
+python3 脚本/kill_ffmpeg_when_clip_done.py --app VideoFusion
+
+# 或监视指定 PID
+python3 脚本/kill_ffmpeg_when_clip_done.py --pid 12345
+
+# 仅立即杀掉当前所有 ffmpeg
+python3 脚本/kill_ffmpeg_when_clip_done.py --kill-now
+```
+
+---
+
+## ❓ 常见问题
+
+### Q: 转录不准确?
+A: 使用medium模型:将脚本中的`whisper-small-mlx`改为`whisper-medium-mlx`
+
+### Q: 字幕太小/太大?
+A: 修改`one_video.py`第142行的`font_size`值
+
+### Q: 处理太慢?
+A:
+1. 视频已自动使用VideoToolbox GPU加速
+2. 字幕默认限制80条以内
+
+### Q: 输出文件太大?
+A: 降低码率:将`-b:v 5M`改为`-b:v 3M`
+
+---
+
+## 🎬 切片动效包装(联动能力)
+
+用 React 程序化生成片头/片尾/封面,与切片产出一键合成。**每次执行视频切片时,若用户提到片头/片尾/包装/批量封面,则联动本能力**。
+
+### 联动规则(必守)
+
+| 场景 | 是否联动 | 操作 |
+|:---|:---|:---|
+| 用户说片头/片尾/程序化包装/批量封面 | ✓ | 先执行切片 → 渲染动效模板 → 合成 |
+| 默认 Soul 切片、单视频成片 | 可选 | 执行后提示可选用切片动效包装 |
+
+### 10秒视频模板(卡若AI 品牌)
+
+路径:`视频切片/切片动效包装/10秒视频/`
+
+| Composition | 说明 |
+|:---|:---|
+| Video10s | 简洁版:渐变 + 标题 + 副标题 |
+| Video10sRich | 内容丰富版:粒子 + 极限环 + 流动线条 |
+
+规格:竖屏 1080×1920,10 秒,30fps。
+
+### 一键命令(动效包装)
+
+```bash
+cd 03_卡木(木)/木叶_视频内容/视频切片/切片动效包装/10秒视频
+
+# 预览
+npm run dev
+
+# 渲染片头(简洁版)
+npx remotion render src/index.ts Video10s /Users/karuo/Documents/卡若Ai的文件夹/导出/程序化视频/片头.mp4
+
+# 渲染片头(丰富版)
+npx remotion render src/index.ts Video10sRich /Users/karuo/Documents/卡若Ai的文件夹/导出/程序化视频/片头_丰富.mp4
+```
+
+### 与切片合成流程
+
+```
+切片产出(clips_enhanced/)
+ ↓
+【联动】渲染片头/片尾
+ ↓
+ffmpeg 合成:片头 + 切片 + 片尾
+```
+
+### 参考资料
+
+- 速查:`视频切片/切片动效包装/参考资料/切片动效包装速查.md`
+- 官方:https://www.remotion.dev/docs
+
+---
+
+## 🎞 剪映思路借鉴与自实现(可选能力)
+
+> 参考 **剪映专业版**(`/Applications/VideoFusion-macOS.app`)内可读配置与流程,用开源方案自实现「智能剪口播」与「智能镜头分割」,不依赖剪映二进制。详见:`参考资料/剪映_智能剪口播与智能片段分割_逆向分析.md`。
+
+### 智能剪口播(口播稿 → 按文案/时间轴切片段)
+
+| 剪映逻辑 | 本技能对应实现 |
+|----------|----------------|
+| 语音→文字 + 时间戳 | **MLX Whisper** 转录 → `transcript.srt` |
+| 按文案智能剪、口播稿↔时间轴对齐 | **高光识别**(`identify_highlights` / `chapter_themes_to_highlights`)→ `highlights.json` → `batch_clip` |
+| 前端配置键 | `script_ai_cut_config`、`transcript_options`(仅作对照,不读写剪映) |
+
+**结论**:现有流程「转录 → 字幕转简 → 高光识别 → 批量切片 → soul_enhance」已覆盖「智能剪口播」能力;按句/按段细切可与 `transcript.srt` 时间戳结合,在 `highlights.json` 中按句生成条目即可。
+
+### 智能镜头分割(按镜头/场景切分)
+
+剪映 **SceneEditDetection** 思路(仅借鉴思路与参数,算法用开源实现):
+
+- **输入**:帧序列;剪映内部为 96×96 小图 + 数组缓冲。
+- **算法思路**:图像特征 + 滑动窗口 + 后处理阈值 → 输出镜头边界。
+- **剪映可读参数**(`SceneEditDetection/config.json`):
+ `sliding_window_size: 7`、`img_feat_dims: 128`、`post_process_threshold: 0.35`、backbone/predhead 模型名(内部用,不引用)。
+
+**自实现方案**:使用 **PySceneDetect**(ContentDetector/AdaptiveDetector),按阈值与最小场景长度得到切点,再转为与 `batch_clip` 兼容的 `highlights.json`。
+
+**一键:镜头检测 → highlights → 批量切片 → 增强**
+
+```bash
+cd 03_卡木(木)/木叶_视频内容/视频切片/脚本
+pip install 'scenedetect[opencv]' # 仅首次
+
+# 镜头检测 → 生成 highlights.json
+python3 scene_detect_to_highlights.py -i "原视频.mp4" -o "输出目录/highlights_from_scenes.json" -t 27 --min-scene-len 15
+
+# 用生成的 highlights 做切片 + 增强(与现有流水线一致)
+python3 batch_clip.py -i "原视频.mp4" -l "输出目录/highlights_from_scenes.json" -o "输出目录/clips/" -p scene
+python3 soul_enhance.py -c "输出目录/clips/" -l "输出目录/highlights_from_scenes.json" -t "输出目录/transcript.srt" -o "输出目录/clips_enhanced/"
+```
+
+**参数速查**:
+
+| 参数 | 说明 | 建议 |
+|------|------|------|
+| `--threshold` / `-t` | 内容变化阈值,越大切点越少 | 27(可试 20~35) |
+| `--min-scene-len` | 最小场景长度(帧) | 15 |
+| `--min-duration` | 过滤短于 N 秒的片段 | 按需 |
+| `--max-clips` / `-n` | 最多保留片段数 | 0=不限制 |
+
+**与「高光切片」二选一**:
+- **高光切片**:按话题/金句/提问(需转录 + 高光识别),适合口播、访谈。
+- **镜头切片**:按画面切换切分,适合多机位、快剪、无稿素材;可先跑 `scene_detect_to_highlights` 再走同一套 `batch_clip` + `soul_enhance`。
+
+### 参考资料(剪映与流程)
+
+- **剪映逆向分析**:`03_卡木(木)/木叶_视频内容/视频切片/参考资料/剪映_智能剪口播与智能片段分割_逆向分析.md`
+ - 智能剪口播 H5 路径、智能片段分割 config 与参数、自实现建议与合规说明。
+- **热点切片标准流程**:`参考资料/热点切片_标准流程.md`(五步、两目录、命令速查)。
+- **高光识别提示词**:`参考资料/高光识别提示词.md`(提问→回答、节奏感、快速混剪优先片段规则)。
+
+---
+
+## 📊 输出示例
+
+```
+输入: 会议录像.mp4 (500MB, 30分钟)
+ ↓
+输出: 会议录像_带字幕.mp4 (200MB)
+ - 中文字幕已烧录
+ - 音频已降噪
+ - 画面已优化
+ - 可直接发布抖音/视频号
+```
+
+---
+
+## 🔗 工作目录
+
+```
+03_卡木(木)/木叶_视频内容/视频切片/
+├── 脚本/
+│ ├── soul_slice_pipeline.py # ⭐ Soul 一体化
+│ ├── soul_enhance.py # ⭐ 封面+字幕+加速
+│ ├── scene_detect_to_highlights.py # 镜头检测→highlights(剪映思路自实现)
+│ ├── one_video.py # 单视频成片
+│ └── ...
+├── 参考资料/
+│ ├── 剪映_智能剪口播与智能片段分割_逆向分析.md # 剪映思路与参数参考
+│ ├── 热点切片_标准流程.md
+│ └── 竖屏中段裁剪参数说明.md
+├── 切片动效包装/ # 联动能力:片头/片尾/程序化
+│ ├── 10秒视频/ # React 程序化模板
+│ └── 参考资料/切片动效包装速查.md
+├── fonts/
+└── SKILL.md
+```
diff --git a/.cursor/archive/skills-karuo-party/karuo-party/skills/运营报表_SKILL.md b/.cursor/archive/skills-karuo-party/karuo-party/skills/运营报表_SKILL.md
new file mode 100644
index 0000000..81c99e6
--- /dev/null
+++ b/.cursor/archive/skills-karuo-party/karuo-party/skills/运营报表_SKILL.md
@@ -0,0 +1,522 @@
+---
+name: Soul派对运营报表
+description: Soul 派对运营数据全自动写入飞书表格(按月份选 2月/3月 标签)→ 会议纪要图片入表 → 发飞书群(数据+纪要图);与智能纪要联动,一站式可执行。含 Token 自动刷新、写入校验、小程序数据、派对录屏链接。完整流程可复制执行,支持基因胶囊打包。
+triggers: 运营报表、派对填表、派对截图填表发群、会议纪要上传、本月运营数据、全部月份统计、派对纪要、智能纪要、106场、107场、113场、114场、115场
+parent: 飞书管理
+owner: 水桥
+group: 水
+version: "3.0"
+updated: "2026-03-04"
+---
+
+# Soul 派对运营报表 · 基因胶囊
+
+> **一句话**:派对截图 + TXT → 飞书运营报表(按月份选表)→ 填数据 + 填纪要图 + 派对录屏链接 + 发群(文字 + 图片),与**会议纪要**联动,完整流程可复制执行,可打包为基因胶囊。
+
+---
+
+## 零、完整流程提取(可复制执行)
+
+以下为从「派对结束」到「报表+群消息+纪要图」全链路的**逐步清单**与**一键命令**,便于 AI 或人工按序执行。
+
+### 0.1 流程图
+
+```mermaid
+flowchart LR
+ subgraph 输入
+ A1[关闭页截图] --> A2[小助手弹窗]
+ A2 --> A3[派对 TXT]
+ A3 --> A4[飞书妙记链接]
+ end
+ subgraph 步骤
+ B1[1. 注册场次+填数据] --> B2[2. 发群文字]
+ B2 --> B3[3. 生成纪要图]
+ B3 --> B4[4. 纪要图入表]
+ B4 --> B5[5. 纪要图发群]
+ end
+ subgraph 输出
+ C1[飞书运营报表]
+ C2[飞书群消息]
+ end
+ A1 --> B1
+ B1 --> C1
+ B2 --> C2
+ B4 --> C1
+ B5 --> C2
+```
+
+### 0.2 前置条件
+
+| 项 | 说明 |
+|:---|:---|
+| Python 3 + requests | `pip3 install requests` |
+| 飞书 Token | 脚本目录下 `.feishu_tokens.json`,过期时运行 `python3 auto_log.py` |
+| 场次已注册 | 在 `soul_party_to_feishu_sheet.py` 中已添加 ROWS、SESSION_DATE_COLUMN、SESSION_MONTH、PARTY_VIDEO_LINKS(可选)、MINIPROGRAM_EXTRA / MINIPROGRAM_EXTRA_3(可选) |
+| 派对 TXT | 如 `soul 派对 115场 20260304.txt`,用于纪要文本/纪要图 |
+
+### 0.3 逐步命令(以 115 场为例)
+
+| 步 | 动作 | 输入 | 命令 | 输出/校验 |
+|:---|:---|:---|:---|:---|
+| 1 | 填效果数据+小程序+派对录屏+发群 | 场次号 115 | `cd 飞书管理/脚本 && python3 soul_party_to_feishu_sheet.py 115` | 控制台见「已写入」「已同步推送到飞书群」「已写入派对录屏链接」 |
+| 2 | 纪要文本入表(可选) | TXT 路径、日期列 4 | `python3 write_party_minutes_from_txt.py "/path/to/soul 派对 115场 20260304.txt" 4` | 控制台见「已写入派对智能纪要到今日总结」 |
+| 3 | 生成纪要图 | 见智能纪要 Skill | JSON→HTML→截图,输出到 `卡若Ai的文件夹/报告/soul_115场_智能纪要_20260304.png` | 得到 PNG 文件 |
+| 4 | 纪要图入表 | PNG 路径、sheet-id、date-col | `python3 feishu_write_minutes_to_sheet.py --party-image "卡若Ai的文件夹/报告/soul_115场_智能纪要_20260304.png" --sheet-id bJR5sA --date-col 4` | 控制台见「已上传派对智能纪要图片」 |
+| 5 | 纪要图发群 | PNG 路径 | `cd 智能纪要/脚本 && python3 send_to_feishu.py --image "卡若Ai的文件夹/报告/soul_115场_智能纪要_20260304.png"` | 飞书群收到长图 |
+
+**路径约定**:飞书管理脚本目录 = `02_卡人(水)/水桥_平台对接/飞书管理/脚本/`;智能纪要脚本 = `02_卡人(水)/水桥_平台对接/智能纪要/脚本/`;报告输出 = `卡若Ai的文件夹/报告/`。
+
+### 0.4 一键顺序命令块(复制即用)
+
+```bash
+# 假设已配置 115 场且 TXT 与报告路径如下,按顺序执行
+FEISHU_SCRIPT="/Users/karuo/Documents/个人/卡若AI/02_卡人(水)/水桥_平台对接/飞书管理/脚本"
+JIYAO_SCRIPT="/Users/karuo/Documents/个人/卡若AI/02_卡人(水)/水桥_平台对接/智能纪要/脚本"
+REPORT="/Users/karuo/Documents/卡若Ai的文件夹/报告"
+TXT="/Users/karuo/Documents/聊天记录/soul/soul 派对 115场 20260304.txt"
+
+cd "$FEISHU_SCRIPT"
+python3 auto_log.py
+python3 soul_party_to_feishu_sheet.py 115
+python3 write_party_minutes_from_txt.py "$TXT" 4
+
+# 纪要图需先按智能纪要 Skill 生成 HTML 再截图得到 PNG,再执行:
+# python3 feishu_write_minutes_to_sheet.py --party-image "$REPORT/soul_115场_智能纪要_20260304.png" --sheet-id bJR5sA --date-col 4
+# cd "$JIYAO_SCRIPT" && python3 send_to_feishu.py --image "$REPORT/soul_115场_智能纪要_20260304.png"
+```
+
+### 0.5 新场次从零到完成清单
+
+1. **在 `soul_party_to_feishu_sheet.py` 中**:添加 `ROWS['116']`、`SESSION_DATE_COLUMN['116']`、`SESSION_MONTH['116']`,以及在 `_maybe_send_group` 的 `date_label`、`src_date` 中加 `'116'`;若需派对录屏则填 `PARTY_VIDEO_LINKS['116']`;若需小程序则填 `MINIPROGRAM_EXTRA_3['5']`(3 月 5 日)。
+2. **执行填表**:`python3 soul_party_to_feishu_sheet.py 116`。
+3. **可选**:纪要文本 `write_party_minutes_from_txt.py "" 5`;纪要图按智能纪要生成后 `feishu_write_minutes_to_sheet.py --party-image --sheet-id bJR5sA --date-col 5`,再 `send_to_feishu.py --image `。
+
+### 0.6 故障排查速查
+
+| 现象 | 处理 |
+|:---|:---|
+| 未找到日期列 | 先 `python3 auto_log.py` 再重试;确认 SESSION_DATE_COLUMN、SESSION_MONTH 与表头一致 |
+| 90202 wrong range | 单格写入时 range 写成 `E29:E29` 形式 |
+| 派对录屏未写入 | 检查 PARTY_VIDEO_LINKS 是否非空且格式为完整 URL |
+| 小程序数据未写入 | 3 月用 MINIPROGRAM_EXTRA_3,键为当月「日期号」如 '4' |
+| 飞书群未收到 | 检查 Webhook、机器人是否启用 |
+
+---
+
+## 一站式完整流程(填数据 → 填图片 → 发群)
+
+**目标**:同一场派对做完「填运营报表数据 → 把会议纪要图片填进报表 → 把纪要图发到飞书群」,顺序执行、流程清晰。
+
+| 步骤 | 动作 | 命令 / 说明 |
+|:---|:---|:---|
+| **1** | **填数据 + 发群(文字)** | `cd 飞书管理/脚本`
`python3 soul_party_to_feishu_sheet.py 115`
→ 效果数据写入当月表对应日期列,并**自动推送竖状文字到飞书群**(含报表链接) |
+| **2** | **生成会议纪要图** | 按 **智能纪要 Skill**(`02_卡人(水)/水桥_平台对接/智能纪要/SKILL.md`):txt → JSON → HTML → 截图 PNG,输出到 `卡若Ai的文件夹/报告/` |
+| **3** | **填图片到报表** | `cd 飞书管理/脚本`
`python3 feishu_write_minutes_to_sheet.py --party-image "<报告路径>/soul_115场_智能纪要_20260304.png" --sheet-id bJR5sA --date-col 4`
→ 纪要图写入运营报表「今日总结」对应列(3 月 115 场 = 第 4 列) |
+| **4** | **把纪要图发到飞书群** | `cd 智能纪要/脚本`
`python3 send_to_feishu.py --image "<报告路径>/soul_115场_智能纪要_20260304.png"`
→ 默认 Webhook 为**运营报表同一飞书群**,群内会收到纪要长图 |
+
+**执行顺序**:1 → 2 → 3 → 4,即可完成「数据入表 + 纪要图入表 + 群内先收文字再收纪要图」。
+
+- **2 月场次**:步骤 3 不传 `--sheet-id`/`--date-col` 时,默认写 2 月表 19/20 列;步骤 4 不变。
+- **同群**:运营报表发群与纪要图发群使用同一 Webhook(见 1.3),群内先看到场次数据,再看到纪要图。
+
+---
+
+## 快速开始(30 秒上手)
+
+```bash
+# ❶ 安装依赖(一次性)
+pip3 install requests
+
+# ❷ 刷新飞书 Token(每天首次或 Token 过期时)
+cd 飞书管理/脚本 && python3 auto_log.py
+
+# ❸ 写入派对效果数据(自动选 2月/3月 工作表 + 发群)
+python3 soul_party_to_feishu_sheet.py 115
+
+# ❹ 生成派对智能纪要文本并写入「今日总结」(可选,与纪要图二选一或都做)
+python3 write_party_minutes_from_txt.py "/path/to/soul 派对 115场 20260304.txt" 4
+
+# ❺ 批量写入小程序数据(可选)
+python3 write_miniprogram_batch.py
+```
+
+所有脚本路径:飞书管理相关在 `飞书管理/脚本/`,纪要生成与发图在 `智能纪要/脚本/`。**3 月场次**会自动写入 3 月工作表标签,不会误写到 2 月。
+
+---
+
+## 一、完整配置清单
+
+### 1.1 飞书应用
+
+| 项目 | 值 |
+|:---|:---|
+| App ID | `cli_a48818290ef8100d` |
+| App Secret | `dhjU0qWd5AzicGWTf4cTqhCWJOrnuCk4` |
+| 授权回调 | `http://localhost:5050/api/auth/callback` |
+| 权限 | `wiki:wiki` `docx:document` `drive:drive` |
+
+### 1.2 运营报表(飞书电子表格)
+
+| 项目 | 值 |
+|:---|:---|
+| 表格链接(2月) | https://cunkebao.feishu.cn/wiki/wikcnIgAGSNHo0t36idHJ668Gfd?sheet=7A3Cy9 |
+| 表格链接(3月) | https://cunkebao.feishu.cn/wiki/wikcnIgAGSNHo0t36idHJ668Gfd?sheet=bJR5sA |
+| spreadsheet_token | `wikcnIgAGSNHo0t36idHJ668Gfd` |
+| 2 月 sheet_id | `7A3Cy9` |
+| 3 月 sheet_id | `bJR5sA` |
+| 表格结构 | A 列=指标名,第 1 行=日期(1、2…),第 2 行可含「113场」「114场」等,第 3~12 行=效果数据,第 15 行=小程序访问,第 28 行=今日总结,第 29 行=派对录屏(飞书妙记链接) |
+| 月份选择 | 脚本按 `SESSION_MONTH` 自动选 2 月或 3 月工作表,避免串月 |
+
+### 1.3 飞书群 Webhook(报表数据 + 纪要图同群)
+
+| 项目 | 值 |
+|:---|:---|
+| Webhook URL | `https://open.feishu.cn/open-apis/bot/v2/hook/34b762fc-5b9b-4abb-a05a-96c8fb9599f1` |
+| 用途 | ① 填表后自动推送竖状格式消息(场次数据+报表链接);② 会议纪要图片发群(智能纪要 `send_to_feishu.py --image` 默认即此 Webhook) |
+
+### 1.4 Token 管理
+
+| 项目 | 说明 |
+|:---|:---|
+| Token 文件 | 脚本同目录 `.feishu_tokens.json` |
+| 含字段 | `access_token`、`refresh_token`、`auth_time` |
+| 自动刷新 | 所有脚本遇 401 自动用 refresh_token 刷新,无需手动 |
+| 手动刷新 | `python3 auto_log.py` (静默刷新,不需浏览器) |
+
+---
+
+## 二、脚本清单与用途
+
+### 2.1 核心脚本(日常使用)
+
+| 脚本 | 功能 | 命令 |
+|:---|:---|:---|
+| `soul_party_to_feishu_sheet.py` | 按场次写入效果数据到**当月工作表**对应日期列 + 飞书群推送(2 月/3 月自动选标签) | `python3 soul_party_to_feishu_sheet.py 115` |
+| `write_party_minutes_from_txt.py` | 从 TXT 生成智能纪要**文本**写入「今日总结」行(需指定日期列号) | `python3 write_party_minutes_from_txt.py "" 4` |
+| `auto_log.py` | Token 刷新 + 飞书日志写入 | `python3 auto_log.py` |
+
+### 2.2 辅助脚本
+
+| 脚本 | 功能 | 命令 |
+|:---|:---|:---|
+| `feishu_write_minutes_to_sheet.py` | 会议纪要/派对总结**图片**上传到「今日总结」对应日期列(默认 2 月 19/20 日列);3 月某场需指定 sheet 与日期列;**发群**需另执行智能纪要 `send_to_feishu.py --image` | `python3 feishu_write_minutes_to_sheet.py [内部图] [派对图]`
3 月 115 场:`--party-image --sheet-id bJR5sA --date-col 4` |
+| `feishu_sheet_monthly_stats.py` | 月度运营数据统计 | `python3 feishu_sheet_monthly_stats.py 2` 或 `all` |
+| `write_miniprogram_to_sheet.py` | **单日**写入小程序三核心数据(访问次数、访客、交易金额) | `python3 write_miniprogram_to_sheet.py 23 55 55 0` |
+| `write_miniprogram_batch.py` | **批量**将 `MINIPROGRAM_EXTRA` 中所有日期的小程序数据写入报表 | `python3 write_miniprogram_batch.py` |
+
+### 2.3 派对录屏链接(自动写入)
+
+填表时若在 `soul_party_to_feishu_sheet.py` 中配置了 `PARTY_VIDEO_LINKS[场次]`(飞书妙记完整 URL),会**自动**写入「派对录屏」行对应列(如 115 场 → E29)。新场次需在脚本中补全链接后重新执行该场次填表。
+
+### 2.4 小程序运营数据(自动写入)
+
+每日填表时,若在 `soul_party_to_feishu_sheet.py` 中配置了 **2 月** `MINIPROGRAM_EXTRA` 或 **3 月** `MINIPROGRAM_EXTRA_3`,会**自动**把当日小程序三核心数据写入对应日期列。数据需从 **Soul 小程序 / 微信公众平台 → 小程序 → 统计 → 实时访问、概况** 获取后填入配置:
+
+| 指标 | 数据来源 | 行(A 列关键词) |
+|:---|:---|:---|
+| 访问次数 | 微信公众平台 → 小程序 → 统计 → 实时访问 | 小程序访问 |
+| 访客 | 同上 | 访客 |
+| 交易金额 | 同上 | 交易金额 |
+
+**配置方式**(在 `soul_party_to_feishu_sheet.py` 中):
+
+```python
+# 派对录屏(飞书妙记链接),写入「派对录屏」行
+PARTY_VIDEO_LINKS = {
+ '115': 'https://cunkebao.feishu.cn/minutes/obcnxxxx...', # 从飞书妙记复制
+}
+
+# 2 月小程序数据
+MINIPROGRAM_EXTRA = {
+ '23': {'访问次数': 55, '访客': 55, '交易金额': 0}, # 2月23日
+}
+# 3 月小程序数据(113/114/115 场填表时自动写 3 月表)
+MINIPROGRAM_EXTRA_3 = {
+ '4': {'访问次数': 60, '访客': 60, '交易金额': 0}, # 3月4日 115场,从 Soul 小程序后台获取后填入
+}
+```
+
+- 数据来源:Soul 小程序 / 微信公众平台 → 小程序 → 统计,每日手动查看后填入
+- 填派对表时自动带出:运行 `soul_party_to_feishu_sheet.py` 某场时,2 月用 `MINIPROGRAM_EXTRA`、3 月用 `MINIPROGRAM_EXTRA_3` 同列写入小程序三项,并若有 `PARTY_VIDEO_LINKS` 则写入派对录屏行
+- 单日写入(仅 2 月表):`python3 write_miniprogram_to_sheet.py 23 55 55 0`(日期列号 访问次数 访客 交易金额)
+- 历史补全:在 `MINIPROGRAM_EXTRA` 中配齐多日数据后执行 `python3 write_miniprogram_batch.py`
+
+---
+
+## 三、完整操作流程
+
+**整体顺序**:先执行 **[ 一站式完整流程 ]**(本文件开头)中的 ① 填数据发群 → ② 生成纪要图 → ③ 填图片到报表 → ④ 纪要图发群,再按需做小程序或纪要文本。
+
+### 3.1 每日派对结束后操作(当前流程)
+
+```
+输入:派对关闭页截图 + 小助手弹窗截图 + TXT 聊天记录(+ 可选:小程序当日数据)
+输出:飞书运营报表(当月标签)写入 + 飞书群推送(文字) + 纪要图入表 + 纪要图发群(与会议纪要联动)
+```
+
+**月份与工作表**:脚本根据 `SESSION_MONTH` 自动选择 2 月或 3 月工作表,3 月场次(如 113、114、115)写入 3 月标签,不会写入 2 月。
+
+#### Step 1:提取数据(从截图)
+
+从派对关闭页和小助手弹窗提取 10 项数据:
+
+| 序号 | 指标 | 来源 |
+|:---|:---|:---|
+| 1 | 主题 | TXT 提炼 ≤12 字 |
+| 2 | 时长(分钟) | 关闭页「派对时长」 |
+| 3 | Soul推流人数 | 关闭页「本场获得额外曝光」 |
+| 4 | 进房人数 | 关闭页「派对成员」或小助手「进房人数」 |
+| 5 | 人均时长 | 小助手「人均时长」 |
+| 6 | 互动数量 | 小助手「互动数量」 |
+| 7 | 礼物 | 关闭页「本场收到礼物」 |
+| 8 | 灵魂力 | 关闭页「收获灵魂力」 |
+| 9 | 增加关注 | 关闭页「新增粉丝」或小助手「增加关注」 |
+| 10 | 最高在线 | 关闭页「最高在线」 |
+
+#### Step 2:在脚本中注册新场次
+
+打开 `soul_party_to_feishu_sheet.py`,在 `ROWS` 字典中添加:
+
+```python
+# 格式:'场次号': [主题, 时长, 推流, 进房, 人均, 互动, 礼物, 灵魂力, 关注, 最高在线]
+'107': ['主题关键词 ≤12字', 140, 35000, 400, 8, 90, 3, 25, 10, 45],
+```
+
+在 `SESSION_DATE_COLUMN` 和 `SESSION_MONTH` 中添加映射(**按月份选工作表标签**,3 月填 3 月表):
+
+```python
+SESSION_DATE_COLUMN = {'105': '20', '106': '21', '107': '23', '113': '2', '114': '3', '115': '4'}
+SESSION_MONTH = {'105': 2, '106': 2, '107': 2, '113': 3, '114': 3, '115': 3}
+```
+
+并在 `_maybe_send_group` 的 `date_label`、`src_date` 中为该场次加上对应「X月X日」和 TXT 日期(如 `'115': '3月4日'`、`'115': '20260304'`)。
+
+#### Step 3:执行写入 + 校验
+
+```bash
+# 写入效果数据(自动选 2月/3月 表 + 校验 + 发群)
+python3 soul_party_to_feishu_sheet.py 115
+
+# 生成智能纪要文本并写入「今日总结」(日期列号 = 当月几号,如 3月4日 填 4)
+python3 write_party_minutes_from_txt.py "/path/to/soul 派对 115场 20260304.txt" 4
+```
+
+成功输出示例(3 月场次):
+```
+✅ 已选 3月 工作表(sheet_id=bJR5sA)
+✅ 已写入飞书表格:115场 效果数据(竖列 E3:E12,共10格),校验通过
+✅ 已同步推送到飞书群(竖状格式)
+✅ 已写入派对智能纪要到「今日总结」→ 2月4日列,校验通过
+```
+
+若需将**智能纪要图片**放入「今日总结」并**发到飞书群**:见下节「智能纪要图片上传到报表 + 发群」。
+
+---
+
+## 3.2 智能纪要图片上传到报表 + 发群(十步清单)
+
+与 **智能纪要 Skill**(`02_卡人(水)/水桥_平台对接/智能纪要/SKILL.md`)联动:纪要图写入运营报表「今日总结」对应列,并**发到运营报表同一飞书群**。
+
+| 序号 | 步骤 | 说明 |
+|:---|:---|:---|
+| 1 | 准备派对 txt | 如 `soul 派对 115场 20260304.txt`(聊天记录/soul) |
+| 2 | 智能提炼 JSON | 按智能纪要规范从 txt 提炼分享人、重点片段、干货、行动项,生成 `xxx_meeting.json` |
+| 3 | 生成 HTML | `智能纪要/脚本/generate_meeting.py --input xxx_meeting.json --output "卡若Ai的文件夹/报告/soul_115场_智能纪要_20260304.html"` |
+| 4 | 导出目录 | HTML/PNG 一律导出到 `卡若Ai的文件夹/报告/`,不落在 Skill 内 |
+| 5 | 截图 PNG | `智能纪要/脚本/screenshot.py "<报告路径>.html" --output "<报告路径>.png"` |
+| 6 | 确认场次与月份 | 115 场 → 3 月表、日期列 4;2 月场次用默认 19/20 列 |
+| 7 | 上传到报表 | `飞书管理/脚本/feishu_write_minutes_to_sheet.py --party-image "<报告路径>.png" --sheet-id bJR5sA --date-col 4`(3 月) |
+| 8 | **纪要图发群** | `智能纪要/脚本/send_to_feishu.py --image "<报告路径>.png"`(默认 Webhook = 运营报表群,群内收到纪要长图) |
+| 9 | 2 月表 | 不指定时默认 `SHEET_ID=7A3Cy9`,派对图→19 列、内部会议→20 列;发群命令同上 |
+| 10 | 协作 | 纪要内容与样式以智能纪要 Skill 为准;本 Skill 负责写入报表、Token 及与发群流程衔接 |
+
+**3 月场次参数速查**:`--sheet-id bJR5sA`,`--date-col` = 当月日期(如 4 日填 `4`)。纪要生成与截图命令详见智能纪要 Skill「智能纪要图片上传到运营报表」小节。
+
+---
+
+## 四、写入校验机制
+
+所有写入操作均含**写后读回校验**:
+
+| 场景 | 校验方式 |
+|:---|:---|
+| 效果数据写入 | 写入后读回首格(主题),比对一致才算成功 |
+| 智能纪要写入 | 写入后读回单元格内容,检查字数 > 0 |
+| Token 过期 | 自动刷新后重试,不降级为追加行(避免写入错误位置) |
+| 日期列未找到 | 直接报错退出,不降级为追加行 |
+
+校验未通过时脚本会打印具体差异信息,方便排查。
+
+---
+
+## 五、飞书群推送格式
+
+**两类推送(同一飞书群)**:① 填表后自动推送竖状文字(场次数据+报表链接);② 纪要图发群需执行智能纪要 `send_to_feishu.py --image `,默认即本群 Webhook。一站式顺序见文首「一站式完整流程」。
+
+写入成功后自动发送到飞书群(竖状格式,每行一项)。链接按当月工作表变化(3 月场次会带 `sheet=bJR5sA`):
+
+```
+【Soul 派对运营报表】
+链接:https://cunkebao.feishu.cn/wiki/wikcnIgAGSNHo0t36idHJ668Gfd?sheet=bJR5sA
+
+115场(3月4日)已登记:
+主题:破产两次 家庭先于事业
+时长(分钟):156
+Soul推流人数:36974
+进房人数:484
+人均时长(分钟):8
+互动数量:82
+礼物:1
+灵魂力:3
+增加关注:15
+最高在线:56
+数据来源:soul 派对 115场 20260304.txt
+```
+
+---
+
+## 六、智能纪要生成规则
+
+**文本纪要**:`write_party_minutes_from_txt.py` 从派对 TXT 自动提炼结构化纪要:
+
+| 板块 | 内容 |
+|:---|:---|
+| 关键词 | 从 TXT 头部 `关键词:` 行提取 |
+| 一、核心内容 | 按关键词匹配提取:退伍军人、AI切入、私域、编导对赌、项目切割等 |
+| 二、金句 | 从对话中提炼可操作的建议 |
+| 三、下一步 | 行动建议(联系管理、搜索培训等) |
+
+纪要**文本**写入运营报表「今日总结」行、对应日期列(需传入日期列号,如 3 月 4 日传 `4`)。**纪要图片**上传到同一格并**发群**:见 **§3.2 智能纪要图片上传到报表 + 发群**;3 月用 `--party-image --sheet-id bJR5sA --date-col <日>`,发群用 `智能纪要/脚本/send_to_feishu.py --image `。
+
+---
+
+## 七、跨平台兼容
+
+### macOS(推荐)
+
+```bash
+# 安装依赖
+pip3 install requests
+
+# 所有命令直接用 python3
+python3 soul_party_to_feishu_sheet.py 106
+```
+
+### Windows
+
+```cmd
+# 安装依赖
+pip install requests
+
+# Windows 用 python(不是 python3)
+python soul_party_to_feishu_sheet.py 106
+python write_party_minutes_from_txt.py "C:\Downloads\soul 派对 106场 20260221.txt" 21
+python auto_log.py
+```
+
+### 路径差异
+
+| 项目 | macOS | Windows |
+|:---|:---|:---|
+| 脚本目录 | `/Users/karuo/Documents/个人/卡若AI/02_卡人(水)/水桥_平台对接/飞书管理/脚本/` | `C:\Users\用户名\卡若AI\02_卡人(水)\水桥_平台对接\飞书管理\脚本\` |
+| Python | `python3` | `python` |
+| TXT 路径 | `/Users/karuo/Downloads/xxx.txt` | `C:\Users\用户名\Downloads\xxx.txt` |
+| Token 文件 | 脚本同目录 `.feishu_tokens.json`(两个平台一致) | 同左 |
+
+### 环境变量覆盖(可选)
+
+所有配置项均可通过环境变量覆盖,无需改脚本:
+
+```bash
+export FEISHU_SPREADSHEET_TOKEN=wikcnIgAGSNHo0t36idHJ668Gfd
+export FEISHU_SHEET_ID=7A3Cy9
+export FEISHU_GROUP_WEBHOOK=https://open.feishu.cn/open-apis/bot/v2/hook/34b762fc-...
+export FEISHU_APP_ID=cli_a48818290ef8100d
+export FEISHU_APP_SECRET=dhjU0qWd5AzicGWTf4cTqhCWJOrnuCk4
+```
+
+---
+
+## 八、常见问题
+
+| 问题 | 解决 |
+|:---|:---|
+| `❌ 无法获取飞书 Token` | 运行 `python3 auto_log.py` 刷新 Token |
+| `❌ 未找到日期列` | Token 过期导致读表失败,先 `python3 auto_log.py` 再重试 |
+| `⚠️ 飞书群推送失败` | 检查 Webhook URL 是否有效、群机器人是否被禁用 |
+| `❌ 写入失败 401` | Token 过期,脚本会自动刷新并重试;若仍失败则 `python3 auto_log.py` |
+| Windows 中文路径乱码 | 确保终端编码为 UTF-8:`chcp 65001` |
+| `pip3 not found` | Windows 用 `pip`;macOS 可能需 `pip3` 或 `python3 -m pip` |
+
+---
+
+## 九、表格结构参考
+
+2 月表(sheet=7A3Cy9)、3 月表(sheet=bJR5sA)结构一致:
+
+```
+表头第1行: [空] | 3月 | 1 | 2 | 3 | 4 | 5 | 6 | ...
+第2行: 一、效果数据 | | 113场 | 114场 | 115场 | 116场 | ...
+第3行: 主题 | | xx | xx | xx | |
+第4行: 时长 | | xx | xx | xx | |
+...
+第12行: 最高在线 | | xx | xx | xx | |
+...
+第15行: 小程序访问| | xx | xx | xx | | ← 访问次数、访客、交易金额
+...
+第28行: 今日总结 | | xx | xx | xx | | ← 智能纪要(文本或图片)
+```
+
+按 `SESSION_DATE_COLUMN` 与 `SESSION_MONTH` 决定写入哪一列、哪张表。
+
+---
+
+## 十、新增场次模板
+
+每次新增场次,在 `soul_party_to_feishu_sheet.py` 中改以下几处:
+
+```python
+# 1. ROWS 字典加一行(主题可带冲击性,≤12 字)
+'116': ['主题≤12字', 时长, 推流, 进房, 人均, 互动, 礼物, 灵魂力, 关注, 最高在线],
+
+# 2. SESSION_DATE_COLUMN 加日期映射(当月几号)
+SESSION_DATE_COLUMN = {..., '116': '5'}
+
+# 3. SESSION_MONTH 加月份(3 月场次必填 3,否则会写入 2 月表)
+SESSION_MONTH = {..., '116': 3}
+
+# 4. _maybe_send_group 内 date_label、src_date 加映射(否则不发群)
+# date_label = {..., '116': '3月5日'}
+# src_date = {..., '116': '20260305'}
+
+# 5. 若当日有小程序数据,在 MINIPROGRAM_EXTRA 中加:
+# MINIPROGRAM_EXTRA = {..., '5': {'访问次数': 60, '访客': 60, '交易金额': 0}}
+```
+
+---
+
+## 十一、基因胶囊打包入口
+
+本 Skill 支持打包为基因胶囊,便于继承与分发。打包后产出位于 `卡若Ai的文件夹/导出/基因胶囊/`。
+
+```bash
+cd /Users/karuo/Documents/个人/卡若AI
+python3 "05_卡土(土)/土砖_技能复制/基因胶囊/脚本/gene_capsule.py" pack "02_卡人(水)/水桥_平台对接/飞书管理/运营报表_SKILL.md"
+# 或按技能名(在 SKILL_REGISTRY 中匹配)
+python3 "05_卡土(土)/土砖_技能复制/基因胶囊/脚本/gene_capsule.py" pack "Soul派对运营报表"
+```
+
+打包后将生成:胶囊 JSON、基因胶囊功能流程图.md、说明文档.md(含解包命令与引用)。
+
+---
+
+## 版本记录
+
+| 版本 | 日期 | 说明 |
+|:---|:---|:---|
+| 1.0 | 2026-02-20 | 初版:截图填表发群 |
+| 2.0 | 2026-02-22 | 基因胶囊:Token 自动刷新、写入校验、智能纪要、跨平台、完整配置清单 |
+| 2.1 | 2026-03-04 | 月份路由:2月/3月 工作表分离(7A3Cy9 / bJR5sA),SESSION_MONTH 防串月;支持 113~115 场;小程序批量 write_miniprogram_batch;运营报表 SKILL 与当前流程同步 |
+| 2.2 | 2026-03-04 | **智能纪要上传到报表**:§3.2 十步清单(txt→JSON→HTML→PNG→feishu_write_minutes_to_sheet);与智能纪要 Skill 联动;3 月用 --party-image --sheet-id bJR5sA --date-col |
+| 2.3 | 2026-03-04 | **会议纪要 + 运营报表 + 发群一站式**:文首新增「一站式完整流程」四步(①填数据发群 ②生成纪要图 ③填图片到报表 ④纪要图发群);飞书群统一:数据推送与纪要图发群同 Webhook,纪要图发群用智能纪要 `send_to_feishu.py --image`;§3.2 增加「发群」步骤与说明 |
+| 3.0 | 2026-03-04 | **完整流程提取 + 基因胶囊**:新增「零、完整流程提取」:流程图、前置条件、逐步命令表、一键命令块、新场次清单、故障排查;派对录屏链接写入(E29:E29 范围);§十一 基因胶囊打包入口与 pack 命令 |
diff --git a/.cursor/archive/skills-karuo-party/karuo-party/skills/飞书视频文字下载_SKILL.md b/.cursor/archive/skills-karuo-party/karuo-party/skills/飞书视频文字下载_SKILL.md
new file mode 100644
index 0000000..5552023
--- /dev/null
+++ b/.cursor/archive/skills-karuo-party/karuo-party/skills/飞书视频文字下载_SKILL.md
@@ -0,0 +1,131 @@
+---
+name: 飞书视频和文字下载
+description: 飞书妙记单条/批量下载视频(mp4)与文字(txt),纯 API+Cookie,不打开浏览器。含 Cookie 获取链、默认输出目录、查找最早长视频。
+triggers: 飞书视频下载、飞书文字下载、妙记下载视频、妙记导出文字、飞书妙记下载、下载飞书视频、下载飞书文字、飞书视频和文字下载
+owner: 水桥
+group: 水
+version: "1.0"
+updated: "2026-03-12"
+parent_skill: 智能纪要
+---
+
+# 飞书视频和文字下载
+
+> **基因能力**:从飞书妙记链接或 object_token,命令行下载**视频(mp4)**与**文字(txt)**,无需打开浏览器。
+> 归属:水桥 · 智能纪要子能力,可独立打包为基因胶囊复用。
+
+---
+
+## 一、默认输出目录
+
+| 类型 | 默认目录 |
+|:---|:---|
+| **文字(txt)** | `/Users/karuo/Documents/聊天记录/soul` |
+| **视频(mp4)** | `/Users/karuo/Movies/soul视频/原视频` |
+
+脚本未指定 `-o`/`--output` 时使用上表;解包到其他环境时可修改为本地路径。
+
+---
+
+## 二、权限:Cookie 获取链(5 级)
+
+妙记**文字导出**与**视频下载**均依赖 Web Cookie(Open API 的 tenant_token 无法访问妙记正文/视频)。
+
+1. **cookie_minutes.txt** 第一行(脚本同目录或 `智能纪要/脚本/`)
+2. **环境变量** `FEISHU_MINUTES_COOKIE`
+3. **本机浏览器**(browser_cookie3:Safari/Chrome/Firefox/Edge)
+4. **Cursor 内置浏览器**:SQLite 明文
+ `~/Library/Application Support/Cursor/Partitions/cursor-browser/Cookies`
+ 查询 `host_key LIKE '%feishu%' OR host_key LIKE '%cunkebao%'`
+5. **手动兜底**:浏览器 F12 → 飞书妙记 list 请求 → 复制 Cookie 到 cookie_minutes.txt
+
+---
+
+## 三、命令行用法
+
+### 3.1 脚本根路径(本基因默认)
+
+```text
+SCRIPT_DIR="/Users/karuo/Documents/个人/卡若AI/02_卡人(水)/水桥_平台对接/智能纪要/脚本"
+```
+
+解包到其他项目时,将上述路径改为本地「智能纪要/脚本」所在路径。
+
+### 3.2 下载视频(单条)
+
+```bash
+# 链接或 object_token 均可
+python3 "$SCRIPT_DIR/feishu_minutes_download_video.py" "https://cunkebao.feishu.cn/minutes/obcnc53697q9mj6h1go6v25e"
+python3 "$SCRIPT_DIR/feishu_minutes_download_video.py" obcnc53697q9mj6h1go6v25e -o ~/Downloads/
+```
+
+- 输出:默认 `原视频/` 下,文件名含标题与日期。
+- 依赖:`requests`;Cookie 见第二节。
+
+### 3.3 导出文字(单条)
+
+```bash
+# 导出为 txt(同上,需 Cookie)
+python3 "$SCRIPT_DIR/feishu_minutes_export_github.py" "https://cunkebao.feishu.cn/minutes/obcnc53697q9mj6h1go6v25e" -o "/Users/karuo/Documents/聊天记录/soul"
+```
+
+- 输出:默认 `聊天记录/soul` 下 txt 文件。
+
+### 3.4 批量文字(按场次范围)
+
+```bash
+python3 "$SCRIPT_DIR/download_soul_minutes_101_to_103.py" --from 90 --to 102
+```
+
+### 3.5 查找「最早且时长≥1小时且有画面」的妙记
+
+```bash
+python3 "$SCRIPT_DIR/find_oldest_long_video_minute.py"
+python3 "$SCRIPT_DIR/find_oldest_long_video_minute.py" --max-status 200
+python3 "$SCRIPT_DIR/find_oldest_long_video_minute.py" --list-only
+```
+
+- 使用 list API 的 `duration`(毫秒)筛 ≥1 小时,再按 create_time 从早到晚用 status API 筛有 `video_download_url`,输出最早一条的 object_token、标题、日期、时长。
+
+---
+
+## 四、核心 API(供二次开发)
+
+| 能力 | 方法 | 说明 |
+|:---|:---|:---|
+| **列表** | `GET /minutes/api/space/list?size=50&space_name=1&last_time={ts}` | 分页用 `last_time` 为上一页最后一条的 create_time |
+| **文字** | `POST /minutes/api/export` | params: object_token, format=2, add_speaker=true;Header: Cookie + Referer + bv-csrf-token |
+| **视频** | `GET /minutes/api/status?object_token=xxx` | 返回 `data.video_info.video_download_url`,再 GET 该 URL 流式下载 |
+
+- 域名:`cunkebao.feishu.cn` 或 `meetings.feishu.cn`(同一套 Cookie)。
+- list 条目含 `duration`(毫秒)、`create_time`、`topic`、`object_token`。
+
+---
+
+## 五、脚本清单(依赖父目录 智能纪要/脚本)
+
+| 脚本 | 功能 |
+|:---|:---|
+| `feishu_minutes_download_video.py` | 单条妙记视频下载(status → mp4) |
+| `feishu_minutes_export_github.py` | 单条妙记文字导出(export → txt) |
+| `feishu_auth_helper.py` | tenant_token / Cookie 测试、refresh-cookie |
+| `cursor_cookie_util.py` | 从 Cursor 浏览器提取 Cookie(feishu/github) |
+| `download_soul_minutes_101_to_103.py` | 批量场次文字(--from/--to) |
+| `find_oldest_long_video_minute.py` | 查找最早、时长≥1h、有视频的妙记 |
+
+---
+
+## 六、解包后使用(继承本基因)
+
+1. 将本基因 **unpack** 到目标项目的 `智能纪要/飞书视频文字下载/` 或任意目录。
+2. 确保目标环境存在「智能纪要/脚本」或等价脚本目录,并安装 `requests`。
+3. 把本 SKILL 中 `SCRIPT_DIR` 改为目标环境中的脚本路径。
+4. 配置 Cookie:cookie_minutes.txt 或 FEISHU_MINUTES_COOKIE 或 Cursor Cookie 提取。
+
+---
+
+## 七、相关文档
+
+- 父技能:`02_卡人(水)/水桥_平台对接/智能纪要/SKILL.md`
+- 权限与排查:`智能纪要/参考资料/飞书妙记下载-权限与排查说明.md`
+- 账号与 API 索引:`运营中枢/工作台/00_账号与API索引.md`
diff --git a/.cursor/config/__pycache__/paths.cpython-311.pyc b/.cursor/config/__pycache__/paths.cpython-311.pyc
new file mode 100644
index 0000000..ae9f8dd
Binary files /dev/null and b/.cursor/config/__pycache__/paths.cpython-311.pyc differ
diff --git a/.cursor/config/model_switch.json b/.cursor/config/model_switch.json
new file mode 100644
index 0000000..72d6139
--- /dev/null
+++ b/.cursor/config/model_switch.json
@@ -0,0 +1,13 @@
+{
+ "default": "cursor",
+ "roles": [
+ {"role": "老板分身", "model": "cursor"},
+ {"role": "开发助理", "model": "cursor"},
+ {"role": "小程序开发工程师", "model": "cursor"},
+ {"role": "管理端开发工程师", "model": "cursor"},
+ {"role": "后端工程师", "model": "cursor"},
+ {"role": "产品经理", "model": "cursor"},
+ {"role": "软件测试", "model": "cursor"},
+ {"role": "团队", "model": "cursor"}
+ ]
+}
diff --git a/.cursor/config/paths.py b/.cursor/config/paths.py
new file mode 100644
index 0000000..e9e15dd
--- /dev/null
+++ b/.cursor/config/paths.py
@@ -0,0 +1,134 @@
+#!/usr/bin/env python3
+# -*- coding: utf-8 -*-
+"""
+Soul 创业派对 - 路径别名
+以项目根为工作区,脚本统一引用。迁移到其他电脑时只需修改 workspace.txt。
+"""
+
+from pathlib import Path
+
+# ========== 工作区根目录 ==========
+_THIS_FILE = Path(__file__).resolve()
+_CURSOR_DIR = _THIS_FILE.parent.parent
+ROOT = _CURSOR_DIR.parent
+
+_WORKSPACE_OVERRIDE = _THIS_FILE.parent / "workspace.txt"
+if _WORKSPACE_OVERRIDE.exists():
+ for line in _WORKSPACE_OVERRIDE.read_text(encoding="utf-8").strip().splitlines():
+ line = line.strip()
+ if line and not line.startswith("#"):
+ ROOT = Path(line).resolve()
+ break
+
+# ========== 核心目录别名 ==========
+CURSOR = ROOT / ".cursor"
+RULES = CURSOR / "rules"
+SKILLS = CURSOR / "skills"
+SCRIPTS = CURSOR / "scripts"
+PROCESS = CURSOR / "process"
+MEETING = CURSOR / "meeting"
+ARCHIVE = CURSOR / "archive"
+ARCHIVE_SOUL_AGENTS = ARCHIVE / "agents-legacy-soul"
+CONFIG = CURSOR / "config"
+MODEL_SWITCH = CONFIG / "model_switch.json"
+DOCS = CURSOR / "docs"
+
+# ========== Agent 目录(Soul 开发团队结构) ==========
+AGENT = CURSOR / "agent"
+
+# 管理层
+AGENT_LEAD = AGENT / "老板分身"
+EVOLUTION_LEAD = AGENT_LEAD / "evolution"
+
+# 支撑层
+AGENT_ASSISTANT = AGENT / "开发助理"
+EVOLUTION_ORANGE = AGENT_ASSISTANT / "evolution"
+ARCHIVED_ORANGE = AGENT_ASSISTANT / "archived"
+PROJECT_INDEX = AGENT_ASSISTANT / "项目索引"
+SCRIPT_ORANGE = AGENT_ASSISTANT / "script"
+
+# Soul 开发角色(已归档目录,仅供脚本/只读引用)
+AGENT_MINIPROGRAM = ARCHIVE_SOUL_AGENTS / "小程序开发工程师"
+AGENT_ADMIN = ARCHIVE_SOUL_AGENTS / "管理端开发工程师"
+AGENT_BACKEND = ARCHIVE_SOUL_AGENTS / "后端工程师"
+AGENT_PRODUCT = ARCHIVE_SOUL_AGENTS / "产品经理"
+AGENT_TEST = ARCHIVE_SOUL_AGENTS / "软件测试"
+AGENT_TEAM = ARCHIVE_SOUL_AGENTS / "团队"
+
+# 玩值 Wanzhi(new/wz-*)
+AGENT_WZ_API = AGENT / "玩值API工程师"
+AGENT_WZ_ADMIN = AGENT / "玩值管理端工程师"
+AGENT_WZ_APP = AGENT / "玩值移动端工程师"
+
+# ========== 常用文件 ==========
+RULE_MAIN = RULES / "老板分身-索引.mdc"
+LOG_EVOLUTION = SCRIPTS / "进化日志.md"
+TEMPLATE_EXPERIENCE = SCRIPTS / "经验模板.md"
+
+# ========== 角色 → agent 目录名映射 ==========
+ROLE_TO_AGENT = {
+ # 管理层
+ "老板分身": "老板分身",
+ "开发助理": "开发助理",
+ "助理橙子": "开发助理",
+ "助手橙子": "开发助理",
+ # Soul 开发角色(目录在 archive/agents-legacy-soul/<原名>)
+ "小程序开发工程师": "小程序开发工程师",
+ "小程序": "小程序开发工程师",
+ "管理端开发工程师": "管理端开发工程师",
+ "管理端": "管理端开发工程师",
+ "后端工程师": "后端工程师",
+ "后端": "后端工程师",
+ "后端开发": "后端工程师",
+ # 产品与质量(新经验默认归开发助理;考古读归档目录)
+ "产品经理": "开发助理",
+ "产品": "开发助理",
+ "软件测试": "开发助理",
+ "测试": "开发助理",
+ "测试人员": "开发助理",
+ # 通用
+ "团队": "开发助理",
+ # 玩值三项目智能体
+ "玩值API工程师": "玩值API工程师",
+ "玩值 API 工程师": "玩值API工程师",
+ "玩值管理端工程师": "玩值管理端工程师",
+ "玩值移动端工程师": "玩值移动端工程师",
+ "wz-api": "玩值API工程师",
+ "wz-admin": "玩值管理端工程师",
+ "wz-app": "玩值移动端工程师",
+}
+
+
+def agent_evolution(role: str) -> Path:
+ """获取角色对应的 evolution 目录。"""
+ agent_name = ROLE_TO_AGENT.get(role, role)
+ soul_roles = (
+ "小程序开发工程师",
+ "管理端开发工程师",
+ "后端工程师",
+ "产品经理",
+ "软件测试",
+ "团队",
+ "安全工程师",
+ )
+ if agent_name in soul_roles:
+ return ARCHIVE_SOUL_AGENTS / agent_name / "evolution"
+ return AGENT / agent_name / "evolution"
+
+
+def agent_script(role: str) -> Path:
+ """获取角色对应的 script 目录。"""
+ agent_name = ROLE_TO_AGENT.get(role, role)
+ if agent_name in (
+ "小程序开发工程师",
+ "管理端开发工程师",
+ "后端工程师",
+ "产品经理",
+ "软件测试",
+ "团队",
+ "安全工程师",
+ ):
+ p = ARCHIVE_SOUL_AGENTS / agent_name / "script"
+ if p.exists():
+ return p
+ return AGENT / agent_name / "script"
diff --git a/.cursor/config/workspace.txt b/.cursor/config/workspace.txt
new file mode 100644
index 0000000..f665aee
--- /dev/null
+++ b/.cursor/config/workspace.txt
@@ -0,0 +1 @@
+E:/Gongsi/Mycontent
diff --git a/.cursor/config/workspace.txt.example b/.cursor/config/workspace.txt.example
new file mode 100644
index 0000000..6f72ae3
--- /dev/null
+++ b/.cursor/config/workspace.txt.example
@@ -0,0 +1,2 @@
+# 复制本文件为 workspace.txt,取消下行注释并填入你的项目根绝对路径
+# {{PROJECT_ROOT_PATH}}
diff --git a/.cursor/config/目录地图.md b/.cursor/config/目录地图.md
new file mode 100644
index 0000000..19dd926
--- /dev/null
+++ b/.cursor/config/目录地图.md
@@ -0,0 +1,45 @@
+# .cursor 目录地图 - Soul 创业派对
+
+> 脚本统一通过 `config/paths.py` 引用路径。迁移时只需改 `workspace.txt` 即可。
+
+---
+
+## 一、工作区覆盖(迁移用)
+
+| 文件 | 说明 |
+|------|------|
+| `.cursor/config/workspace.txt` | 可选。写入一行**绝对路径**,覆盖自动推断的项目根。迁移到其他电脑时创建此文件即可。 |
+
+---
+
+## 二、核心别名一览
+
+| 别名 | 路径 | 说明 |
+|------|------|------|
+| `ROOT` | 项目根 | 工作区根目录 |
+| `CURSOR` | `.cursor/` | cursor 配置根 |
+| `RULES` | `.cursor/rules/` | 规则 |
+| `SKILLS` | `.cursor/skills/` | 技能 |
+| `SCRIPTS` | `.cursor/scripts/` | 脚本 |
+| `PROCESS` | `.cursor/process/` | 工作流 |
+| `MEETING` | `.cursor/meeting/` | 会议纪要 |
+| `ARCHIVE` | `.cursor/archive/` | 历史归档 |
+| `DOCS` | `.cursor/docs/` | 文档 |
+| `CONFIG` | `.cursor/config/` | 配置 |
+| `MODEL_SWITCH` | `.cursor/config/model_switch.json` | 按角色配置模型 |
+
+---
+
+## 三、Agent 目录(Soul 开发团队结构)
+
+| 别名 | 路径 | 说明 |
+|------|------|------|
+| `AGENT` | `.cursor/agent/` | 智能体根 |
+| `AGENT_LEAD` | `.cursor/agent/老板分身/` | 老板分身(最高权限) |
+| `AGENT_ASSISTANT` | `.cursor/agent/开发助理/` | 开发助理(橙子) |
+| `AGENT_MINIPROGRAM` | `.cursor/agent/小程序开发工程师/` | 小程序开发 |
+| `AGENT_ADMIN` | `.cursor/agent/管理端开发工程师/` | 管理端开发 |
+| `AGENT_BACKEND` | `.cursor/agent/后端工程师/` | 后端开发 |
+| `AGENT_PRODUCT` | `.cursor/agent/产品经理/` | 产品经理 |
+| `AGENT_TEST` | `.cursor/agent/软件测试/` | 测试人员 |
+| `AGENT_TEAM` | `.cursor/agent/团队/` | 跨角色共享经验 |
diff --git a/.cursor/docs/cursor规则与架构分析及优化建议.md b/.cursor/docs/cursor规则与架构分析及优化建议.md
new file mode 100644
index 0000000..7a4d9d5
--- /dev/null
+++ b/.cursor/docs/cursor规则与架构分析及优化建议.md
@@ -0,0 +1,156 @@
+# Soul 创业派对 · .cursor 规则与架构分析及优化建议
+
+> 分析日期:2026-03-19
+> 范围:`.cursor/` 下 rules、skills、agent、config、meeting、scripts
+> **2026-03-20**:已批量将 `e:\Gongsi\...` 改为仓库相对路径 `.cursor/...`;`party-ai-dev.mdc` 已补充与 `.cursor` 的优先级;根目录 `.gitignore` 已忽略 `karuo-party/credentials/`;新增 `.cursor/README.md`、`.cursorignore`(db-exec node_modules)。
+
+---
+
+## 整体架构图
+
+
+
+---
+
+## 一、整体架构总览
+
+### 1.1 项目与 .cursor 的关系
+
+```
+项目根(一场soul的创业实验-永平)
+├── miniprogram/ # 微信原生小程序 C 端 → /api/miniprogram/*
+├── soul-admin/ # React 管理后台(主用)→ /api/admin/*、/api/db/*
+├── soul-api/ # Go + Gin + GORM 接口服务
+├── next-project/ # 仅预览,非线上
+├── new-soul/soul-admin/ # 新版参考,迁移时对照
+└── .cursor/ # Cursor AI 规则与智能体配置
+ ├── rules/ # 全局/场景规则(alwaysApply 或 globs)
+ ├── skills/ # 按角色/场景的 Skill(写作、上传、开发、会议等)
+ ├── agent/ # 角色经验与项目索引(evolution、项目索引)
+ ├── config/ # paths.py、workspace 等
+ ├── meeting/ # 会议纪要
+ ├── scripts/ # 进化脚本、Gitea 同步、db-exec 等
+ └── docs/ # 本分析等文档
+```
+
+### 1.2 规则层(Rules)与技能层(Skills)关系
+
+| 类型 | 作用 | 典型文件 |
+|------|------|----------|
+| **Rules** | 会话自检、项目边界、谁调哪组 API、何时加载哪个 Skill | soul-project-boundary.mdc、老板分身-索引.mdc、soul-meeting.mdc、soul-change-checklist.mdc、party-ai-dev.mdc |
+| **Skills** | 具体执行规范:怎么写代码、怎么开会、怎么检查变更 | miniprogram-dev、admin-dev、api-dev、team-meeting、change-checklist、assistant-doc-sync |
+
+- **角色推断**:按「当前编辑目录」或「用户触发词」→ 确定角色 → **必须 Read 对应 Skill 文件**后执行。
+- **老板分身**:权限最高,可调度所有角色;开会时由乘风按 team-meeting 主持;经验自动收集写各角色 evolution。
+
+### 1.3 三端与 API 路由(核心原则)
+
+| 端 | 目录 | 允许调用的 API | 禁止 |
+|----|------|----------------|------|
+| 小程序 | miniprogram/ | `/api/miniprogram/*` | admin、db |
+| 管理端 | soul-admin/ | `/api/admin/*`、`/api/db/*` | miniprogram 混用 |
+| 后端 | soul-api/ | 按使用方挂 miniprogram / admin / db 分组 | 通用路径混用 |
+
+---
+
+## 二、优化与迭代建议
+
+### 2.1 路径可移植性(高优先级)✅ 已落地
+
+**原问题**:rules 与部分 skills 中曾写死 **Windows 绝对路径** `e:\Gongsi\Mycontent\.cursor\skills\...`,在 macOS/Linux 或不同机器上会失效。**当前**:已统一为仓库根相对路径 `.cursor/skills/...` 等,详见 `rules/soul-project-boundary.mdc` 的「路径约定」。
+
+**涉及文件**:
+
+- `rules/老板分身-索引.mdc`:team-meeting SKILL 路径
+- `rules/soul-project-boundary.mdc`:所有「必须 Read 的主 Skill 文件」表格(按编辑目录、按语义触发词、按场景触发词)
+- `rules/soul-meeting.mdc`:team-meeting、assistant-doc-sync 路径
+- `rules/soul-change-checklist.mdc`:change-checklist SKILL 路径
+- `skills/assistant-doc-sync/SKILL.md`:项目索引路径
+- `skills/mysql-direct/SKILL.md`:`cd e:\Gongsi\Mycontent`
+
+**建议**:
+
+1. **统一改为相对项目根的路径**
+ 例如:`项目根/.cursor/skills/team-meeting/SKILL.md`,或在规则中明确写:
+ 「以当前项目根为基准,Read `.cursor/skills/{skill-name}/SKILL.md`」。
+2. 若 Cursor 支持「工作区根」变量,可写成占位符(如 `{workspace}/.cursor/skills/...`),在文档中说明各系统下的解析方式。
+3. **config/paths.py** 已定义 `SKILLS = CURSOR / "skills"`,可在 `.cursor/README.md` 或 rules 中说明:**所有 Skill 路径以 `paths.py` 的 SKILLS 为准,规则中仅写相对 SKILLS 的路径**(如 `skills/team-meeting/SKILL.md`),由 AI 结合当前项目根解析。
+
+### 2.2 跨平台脚本与入口
+
+**问题**:老板分身规则里「若无法写文件则输出 JSON,并提示用户双击 `agent/开发助理/script/一键-添加经验.bat`」。`.bat` 仅适用于 Windows,Mac/Linux 用户无法使用。
+
+**建议**:
+
+1. 增加 **Shell 版**:`一键-添加经验.sh`,实现相同逻辑(或调用同一份 Python/Node 脚本)。
+2. 在规则中改为:「提示用户执行 `agent/开发助理/script/一键-添加经验.bat`(Windows)或 `一键-添加经验.sh`(Mac/Linux),或根据环境说明」。
+
+### 2.3 party-ai-dev.mdc 与 老板分身 的优先级
+
+**问题**:`party-ai-dev.mdc` 要求「优先使用派对 AI(派对AI/BOOTSTRAP.md、SKILL_REGISTRY.md)」,而老板分身等规则在 `.cursor/rules` 下,若同时生效可能产生「先读卡若还是先读派对」的冲突。
+
+**建议**:
+
+1. 在 **party-ai-dev.mdc** 或 **老板分身-索引.mdc** 中明确写清:
+ 「当本仓库为 Soul 派对项目且存在 派对AI/ 目录时,优先按 party-ai-dev 启动顺序;否则按 .cursor/rules 与 skills 执行。」
+2. 或约定:**派对AI 仅用于「在派对AI 目录下开发」的会话**;**在 miniprogram/soul-admin/soul-api 等目录下开发时,仅用 .cursor 的 rules+skills**,避免双重入口。
+
+### 2.4 soul-change-checklist 与 change-checklist Skill 的引用方式
+
+**问题**:soul-change-checklist.mdc 第三十条要求 Read 的路径仍是 Windows 绝对路径。
+
+**建议**:与 2.1 一致,改为「项目根/.cursor/skills/change-checklist/SKILL.md」或相对路径说明,并在 checklist 规则末尾加一句:「Skill 详细流程见 `.cursor/skills/change-checklist/SKILL.md`」。
+
+### 2.5 会议纪要与收尾路径
+
+**问题**:soul-meeting.mdc 中会议纪要、项目索引、会议记录索引等路径未写死 Windows,但 assistant-doc-sync SKILL 里项目索引写的是 `e:\Gongsi\Mycontent\.cursor\agent\...`。
+
+**建议**:assistant-doc-sync 内所有路径改为「项目根/.cursor/agent/...」或相对路径,与 config/paths.py 中的 AGENT、PROJECT_INDEX 等保持一致表述。
+
+### 2.6 角色与 Skill 的集中索引
+
+**现状**:角色→Skill 的映射分散在 soul-project-boundary(按目录、按触发词、按场景)和 paths.py(ROLE_TO_AGENT)。
+
+**建议**:在 `.cursor/README.md` 或 `docs/` 下维护一份「角色 ↔ Skill 一览表」,便于新人/新 Agent 快速查阅;rules 中可写「详见 .cursor/README.md#角色与Skill映射」。
+
+### 2.7 经验自动收集的脚本与 Mac 兼容
+
+**现状**:evolution 写入由 scripts/evolution.py 等完成,paths 来自 config/paths.py,已跨平台;仅「一键-添加经验」的入口是 .bat。
+
+**建议**:同 2.2,补充 .sh 或统一用 Python 脚本入口,在规则中同时给出 Windows 与 Mac/Linux 的说明。
+
+---
+
+## 三、规则与 Skill 清单速查
+
+| 名称 | 类型 | 作用 |
+|------|------|------|
+| soul-project-boundary.mdc | Rule | 项目边界、三端 API 约定、角色推断与 Skill 加载 |
+| 老板分身-索引.mdc | Rule | 老板分身权限、经验自动收集、编码习惯、三端分工 |
+| soul-meeting.mdc | Rule | 开会/散会触发、会议纪要路径、收尾流程 |
+| soul-change-checklist.mdc | Rule | 变更后关联检查清单(防漏改) |
+| party-ai-dev.mdc | Rule | 优先派对 AI、飞书复盘、小程序上传约定 |
+| miniprogram-dev | Skill | 小程序开发规范 |
+| admin-dev | Skill | 管理端开发规范 |
+| api-dev | Skill | 后端 API 规范 |
+| product-manager | Skill | 产品需求与验收 |
+| testing | Skill | 测试与回归 |
+| team-meeting | Skill | 多角色会议流程 |
+| assistant-doc-sync | Skill | 小橙/文档同步/经验入库/会议收尾 |
+| change-checklist | Skill | 变更关联检查详细流程 |
+| role-flow-control | Skill | 跨端协同与角色流程 |
+| three-tier-arch | Skill | 三端架构与框架分析 |
+| new-version-analyze | Skill | 新版分析、迁移对比 |
+| next-preview / next-split | Skill | next-project 仅预览、拆解指引 |
+| mysql-direct | Skill | MySQL 直接操作、db-exec |
+
+---
+
+## 四、总结
+
+- **架构**:项目为三端(小程序 + 管理端 + soul-api),.cursor 通过 rules 定边界与触发、skills 定执行细节、agent 存经验与项目索引,**config/paths.py** 为路径与角色映射中心。
+- **优先迭代**:
+ 1)所有 **Skill/agent 路径** 改为可移植(相对项目根或相对 .cursor);
+ 2)**一键-添加经验** 增加 Mac/Linux 入口;
+ 3)**party-ai 与 .cursor** 的适用场景或优先级写清楚。
+- 按上述调整后,在不同系统和不同克隆路径下都能一致生效,且便于后续扩展角色或 Skill。
diff --git a/.cursor/docs/feishu_开发群与项目复盘.md b/.cursor/docs/feishu_开发群与项目复盘.md
new file mode 100644
index 0000000..e572a2d
--- /dev/null
+++ b/.cursor/docs/feishu_开发群与项目复盘.md
@@ -0,0 +1,38 @@
+# 飞书「开发群」与 Soul 项目复盘约定
+
+## 绑定关系
+
+- **Soul 创业派对(永平)**、**派对 AI 相关自动化**、**卡若 AI 侧发往本项目的复盘**,默认使用**同一开发群机器人 Webhook**。
+- Webhook 与项目在配置上是**一一绑定**:换群 = 改环境变量或下方脚本中的默认 URL,并确保飞书里该群已添加对应自定义机器人。
+
+## 默认 Webhook(开发群)
+
+环境变量(推荐在本机 shell 或 `scripts/.env.feishu` 同目录的 `.env` 中导出):
+
+| 变量名 | 用途 |
+|--------|------|
+| `FEISHU_DEV_GROUP_WEBHOOK` | **主约定**:开发群统一入口;未设置时各脚本使用内置默认值。 |
+
+当前默认 URL(与飞书群内机器人一致):
+
+`https://open.feishu.cn/open-apis/bot/v2/hook/c558df98-e13a-419f-a3c0-7e428d15f494`
+
+## 已接此 Webhook 的脚本(代码内默认或可读此变量)
+
+| 脚本 | 说明 |
+|------|------|
+| `scripts/send_chapter_poster_to_feishu.py` | 章节摘要 + 海报图(小程序码) |
+| 卡若AI `飞书管理/脚本/send_review_to_feishu_webhook.py` | 卡若 AI 复盘(文本/卡片) |
+| 卡若AI `飞书管理/脚本/soul_party_to_feishu_sheet.py` | 派对运营表同步后的群推送 |
+
+**复盘发哪里**:与 Soul 开发相关的**日终/迭代复盘** → 发 **`FEISHU_DEV_GROUP_WEBHOOK` 对应群**。
+彩民/运营另群如需保留,可通过各脚本 `--webhook` 或单独环境变量覆盖。
+
+## 界面截图发群说明
+
+- 飞书自定义机器人发图需先走**应用上传**得到 `image_key`(见 `send_chapter_poster_to_feishu.py` 内逻辑)。
+- 无现成截图时:在复盘文本中附 **管理端 / 小程序 / API 文档** 等**可点击链接**,与海报一并发出。
+
+## 与「SKR / 开发群」口头约定
+
+- 群内链接、机器人由**项目侧**维护;**派对 AI** 与 **卡若 AI** 推送配置统一指向本开发群,避免复盘散落多个群。
diff --git a/.cursor/docs/skill-writing-principles.md b/.cursor/docs/skill-writing-principles.md
new file mode 100644
index 0000000..28bccb1
--- /dev/null
+++ b/.cursor/docs/skill-writing-principles.md
@@ -0,0 +1,60 @@
+# Skill 撰写原则(吸收 Anthropic 实践)
+
+> 来源:Thariq Shihipar 分享的 Anthropic 内部 Skills 方法论。本文档供维护 `.cursor/skills/` 时参考。
+
+## 1. 写 Claude 不知道的东西
+
+Claude 对编程已很熟,有默认偏好。好的 Skill 应聚焦**能把 Claude 推出惯性思维**的信息:
+
+- 团队独特规范(如 Soul 三端路由隔离、Toast 禁 alert)
+- 行业或项目不成文惯例
+- 过去踩过的坑、边界条件
+- 与通用实践不同的决策(如为什么用 key 而非 appId)
+
+**避免**:大段重复 Claude 已掌握的通用知识。
+
+## 2. Gotchas 是 Skill 的灵魂
+
+任何 Skill 里**信息密度最高**的就是踩坑清单。应从实际失败中积累,随使用不断更新。
+
+- 格式:陷阱 → 后果 → 正确做法
+- 信号强于「怎么做对」:告诉 AI「千万别这么做」更有效
+- 每次 Claude 踩新坑,就往对应 Skill 的 Gotchas 表追加一行
+
+## 3. 给指令留灵活空间
+
+Skill 高度可复用,写得太死会令 AI 在稍不同情况下手足无措。
+
+- 给**方向与原则**,而非死板步骤
+- 提供必要信息,同时留出根据实际情况灵活应变的余地
+- 检查清单、必守规则是好的;过度细化的「第一步第二步第三步」可酌情简化
+
+## 4. 让 Skill 拥有记忆
+
+通过内部存储实现记忆:
+
+- 追加式日志(如 `sync-log.md`):记录每次执行摘要,下次可对比增量
+- 进化池(evolution):经验按日期沉淀,形成团队知识库
+- 理想:Skill 不只干活,还能「记得上次干了什么」
+
+## 5. 给现成的代码与模板
+
+AI 擅长组合与决策,不擅长记公司特有细节。最高效协作方式:
+
+- 提供脚本(如 db-exec、一键-添加经验.bat)
+- 提供模板(evolution 模板、会议纪要模板)
+- 把精力留给 AI 做组合和决策,而不是从零写样板
+
+## 6. Skill 是文件夹,不是单文件
+
+一个 Skill 可包含:
+
+- `SKILL.md`:入口,行为与触发条件
+- `scripts/`:可执行脚本
+- `references/`:参考文档、路径速查
+- `assets/`:模板、配置
+- 日志文件:记忆
+
+## 7. 从简单开始,持续迭代
+
+大多数 Skill 一开始只有几行文字加一条踩坑提醒。随 Claude 遇到新边界情况,再一点一点完善。不追求一步到位。
diff --git a/.cursor/docs/soul-project-cursor-architecture.png b/.cursor/docs/soul-project-cursor-architecture.png
new file mode 100644
index 0000000..d92a99f
Binary files /dev/null and b/.cursor/docs/soul-project-cursor-architecture.png differ
diff --git a/.cursor/docs/三角色边界定义.md b/.cursor/docs/三角色边界定义.md
new file mode 100644
index 0000000..5ac7f12
--- /dev/null
+++ b/.cursor/docs/三角色边界定义.md
@@ -0,0 +1,174 @@
+# Soul 创业派对 - 三角色边界定义(开发)
+
+> 按各自负责的**源码目录**与**业务功能**定义,防止互窜、明确职责。团队为 **2 前端 + 1 后端 + 1 产品 + 1 助理**,详见 [开发团队职责定义.md](./开发团队职责定义.md)。
+
+---
+
+## 一、开发角色总览
+
+| 角色 | 源码目录 | 对接 API 前缀 | 技术栈 |
+|------|----------|---------------|--------|
+| 小程序开发工程师 | miniprogram/ | /api/miniprogram/* | 微信原生 WXML/WXSS/JS |
+| 管理端开发工程师 | soul-admin/ | /api/admin/*、/api/db/* | React + Vite + TypeScript + Tailwind |
+| 后端开发 | soul-api/ | 实现上述全部 | Go + Gin + GORM + PowerWeChat |
+
+---
+
+## 二、小程序开发工程师
+
+### 2.1 负责源码
+
+| 路径 | 说明 |
+|------|------|
+| miniprogram/pages/* | 页面(index、chapters、read、my、referral、match、settings、withdraw-records、vip 等) |
+| miniprogram/utils/* | 工具(scene、payment、chapterAccessManager、readingTracker) |
+| miniprogram/components/* | 组件 |
+| miniprogram/custom-tab-bar/* | 自定义 TabBar |
+| miniprogram/app.js、app.json、app.wxss | 全局配置与入口 |
+
+### 2.2 负责业务功能
+
+| 功能域 | 页面/入口 | 对接接口 |
+|--------|-----------|----------|
+| 首页与浏览 | index、chapters、search | /api/miniprogram/book/*、config |
+| 阅读与付费 | read | /api/miniprogram/pay、pay/notify、user/check-purchased、user/purchase-status |
+| 找伙伴 | match | /api/miniprogram/match/*、ckb/* |
+| 推广与分销 | referral | /api/miniprogram/referral/*、earnings |
+| 提现 | 推广中心申请、我的待确认、withdraw-records | /api/miniprogram/withdraw、withdraw/records、withdraw/pending-confirm |
+| 我的 | my | /api/miniprogram/user/*、vip/*、withdraw/* |
+| 设置 | settings | /api/miniprogram/login、phone、config |
+| 地址 | addresses | /api/miniprogram/user/addresses |
+
+### 2.3 边界约束
+
+- **禁止**:调用 `/api/admin/*`、`/api/db/*`;不得使用 next-project 接口。
+- **请求**:统一通过 `getApp().request(url, options)`,baseUrl 指向 soul-api。
+
+---
+
+## 三、管理端开发工程师
+
+### 3.1 负责源码
+
+| 路径 | 说明 |
+|------|------|
+| soul-admin/src/pages/* | 页面(Dashboard、Content、Chapters、Orders、Users、Withdrawals、Payment、Settings、QRCodes、Distribution 等) |
+| soul-admin/src/components/* | 组件(ui、modules 等) |
+| soul-admin/src/api/* | 请求封装(client.ts、auth.ts) |
+| soul-admin/src/layouts/* | 布局 |
+| soul-admin/src/hooks/* | hooks |
+
+### 3.2 负责业务功能
+
+| 功能域 | 页面 | 对接接口 |
+|--------|------|----------|
+| 仪表盘 | DashboardPage | /api/admin/* |
+| 内容管理 | ContentPage | /api/admin/content |
+| 章节管理 | ChaptersPage | /api/admin/chapters |
+| 订单 | OrdersPage | /api/orders |
+| 用户管理 | UsersPage | /api/db/users |
+| 提现审核 | WithdrawalsPage | /api/admin/withdrawals |
+| 支付配置 | PaymentPage | /api/admin/payment |
+| 推广设置 | ReferralSettingsPage | /api/admin/referral-settings |
+| 系统设置 | SettingsPage | /api/admin/settings |
+| 二维码 | QRCodesPage | /api/db/config 等 |
+| 分销概览 | DistributionPage | /api/admin/distribution/overview |
+| VIP 角色 | VipRolesPage | /api/db/vip-roles |
+
+### 3.3 边界约束
+
+- **允许**:`/api/admin/*`、`/api/db/*`,以及 `/api/orders` 等与现网一致的管理端接口。
+- **禁止**:调用 `/api/miniprogram/*`;不得使用小程序登录或小程序 token。
+- **请求**:统一通过 `client.ts` 的 get/post/put/del;鉴权用 `auth.ts` 的 Bearer admin_token。
+
+---
+
+## 四、后端开发
+
+### 4.1 负责源码
+
+| 路径 | 说明 |
+|------|------|
+| soul-api/internal/router | 路由注册(miniprogram、admin、db、payment 各组) |
+| soul-api/internal/handler | 业务 handler |
+| soul-api/internal/model | 数据模型 |
+| soul-api/internal/wechat | 微信、支付、转账等封装 |
+| soul-api/internal/config | 配置加载 |
+| soul-api/internal/database | 数据库连接 |
+| soul-api/internal/auth | 鉴权(JWT) |
+| soul-api/internal/middleware | 中间件 |
+
+### 4.2 负责路由分组与业务
+
+| 路由组 | 前缀 | 使用方 | 典型业务 |
+|--------|------|--------|----------|
+| miniprogram | /api/miniprogram/* | 小程序 | 登录、支付、书籍、推荐、提现、VIP、用户 |
+| admin | /api/admin/* | 管理端 | 登录、章节、内容、支付配置、提现审核、设置、分销 |
+| db | /api/db/* | 管理端 | 用户、配置、书籍、章节、VIP 角色、初始化 |
+| payment | /api/payment/* | 微信/支付宝回调 | 支付回调、订单、商家转账回调 |
+
+### 4.3 边界约束
+
+- **按使用方挂路由**:小程序接口只挂 miniprogram;管理端接口只挂 admin/db;不得混用。
+- **禁止**:在 miniprogram 组挂仅 admin 用的接口;在 admin/db 组挂小程序专属逻辑。
+
+### 4.4 特殊路由说明
+
+| 类型 | 示例 | 说明 |
+|------|------|------|
+| 微信/支付宝回调 | /api/payment/*、/api/miniprogram/pay/notify | 由微信/支付宝主动调用,无鉴权;后端负责验签、解密 |
+| 管理端扁平路径 | /api/orders | 管理端使用,与 /api/admin/*、/api/db/* 并列 |
+
+---
+
+## 五、支付/提现相关职责归属
+
+| 环节 | 小程序开发工程师 | 管理端开发工程师 | 后端开发 |
+|------|--------------|--------------|----------------|
+| 支付下单 | 调 /api/miniprogram/pay,调起 wx.requestPayment | - | 实现 Pay handler,调用微信统一下单 |
+| 支付回调 | - | - | 实现 PayNotify,验签、更新订单、分佣 |
+| 提现申请 | 调 /api/miniprogram/withdraw | - | 实现 WithdrawPost;校验余额、写 withdrawals |
+| 提现审核 | - | 调 /api/admin/withdrawals 列表、通过/拒绝 | 实现 AdminWithdrawalsList、Action;调微信打款 |
+| 提现回调 | - | - | 实现 PaymentWechatTransferNotify;验签、解密、更新状态 |
+| 待确认收款 | 调 /api/miniprogram/withdraw/pending-confirm | - | 实现 WithdrawPendingConfirm |
+
+---
+
+## 六、速查:编辑目录 → 角色
+
+| 编辑目录 | 角色 | 必遵守 | 主 Skill |
+|----------|------|--------|----------|
+| miniprogram/** | 小程序开发工程师 | soul-miniprogram-boundary | SKILL-小程序开发.md |
+| soul-admin/** | 管理端开发工程师 | soul-admin-boundary | SKILL-管理端开发.md |
+| soul-api/** | 后端开发 | soul-api | SKILL-API开发.md |
+
+---
+
+## 七、跨端协同与变更检查
+
+| 场景 | 动作 |
+|------|------|
+| **跨端功能开发** | 加载 SKILL-角色流程控制.md,按「需求分析 → 并行开发 → 管理端启动」执行 |
+| **变更完成准备提交** | **必过** soul-change-checklist.mdc + SKILL-变更关联检查.md |
+| **接口契约** | 后端开发输出(路径、请求/响应、字段);小程序/管理端按契约对接 |
+
+---
+
+## 八、排除项
+
+- **next-project/**:仅预览,不参与线上;新增/优化以 miniprogram、soul-admin、soul-api 为准。
+
+---
+
+## 九、相关文档
+
+| 文档 | 说明 |
+|------|------|
+| [开发团队职责定义](./开发团队职责定义.md) | 五角色团队、Skills 分配 |
+| [角色驱动Skills分析](./角色驱动Skills分析.md) | Skills 组织方式、改进点 |
+| [SKILL-角色流程控制](../skills/role-flow-control/SKILL.md) | 跨端协同流程、决策表 |
+| soul-project-boundary.mdc | 项目边界、防互窜原则 |
+
+---
+
+**更新日期**:2026-02
diff --git a/.cursor/docs/开发团队职责定义.md b/.cursor/docs/开发团队职责定义.md
new file mode 100644
index 0000000..f01a27e
--- /dev/null
+++ b/.cursor/docs/开发团队职责定义.md
@@ -0,0 +1,149 @@
+# Soul 创业派对 - 开发团队职责定义
+
+> **开发团队**:2 前端 + 1 后端 + 1 产品 + 1 测试 + 1 助理。按职责分配 Skills,有**经验库**用于根据经验自动升级 Skills。速查见 [.cursor/README.md](../README.md)。
+
+---
+
+## 一、开发团队总览
+
+| 角色 | 职责 | 负责目录/场景 | 主 Skill |
+|------|------|---------------|----------|
+| **小程序开发工程师** | 微信原生小程序 C 端 | miniprogram/ | SKILL-小程序开发.md |
+| **管理端开发工程师** | React 管理后台 | soul-admin/ | SKILL-管理端开发.md |
+| **后端开发** | Go + Gin + GORM 接口服务 | soul-api/ | SKILL-API开发.md |
+| **产品经理** | 需求、验收、协调 | 开发文档/1、需求/、临时需求池/ | SKILL-产品经理.md |
+| **测试人员** | 功能测试、回归测试、三端联调 | miniprogram、soul-admin、soul-api | SKILL-测试.md |
+| **助理橙子** | 讨论后记录、文档同步 | 触发词:小橙、橙子、讨论完毕 | SKILL-助理橙子-文档同步.md |
+
+---
+
+## 二、开发角色(源码)
+
+### 2.1 小程序开发工程师
+
+| 项目 | 说明 |
+|------|------|
+| **源码** | miniprogram/pages、utils、components、app.js |
+| **API** | 只调 `/api/miniprogram/*` |
+| **禁止** | 不调 `/api/admin/*`、`/api/db/*` |
+| **主 Skill** | SKILL-小程序开发.md |
+| **辅助** | 三端架构 → API开发 → 变更关联检查 |
+| **协同** | SKILL-角色流程控制.md(跨端时) |
+
+### 2.2 管理端开发工程师
+
+| 项目 | 说明 |
+|------|------|
+| **源码** | soul-admin/src/pages、components、api、layouts |
+| **API** | 只调 `/api/admin/*`、`/api/db/*`、`/api/orders` 等 |
+| **禁止** | 不调 `/api/miniprogram/*` |
+| **主 Skill** | SKILL-管理端开发.md |
+| **辅助** | 三端架构 → API开发 → 变更关联检查 |
+| **协同** | SKILL-角色流程控制.md(跨端时) |
+
+### 2.3 后端开发
+
+| 项目 | 说明 |
+|------|------|
+| **源码** | soul-api/internal/router、handler、model、wechat、config |
+| **路由** | 按使用方挂 miniprogram / admin / db / payment |
+| **主 Skill** | SKILL-API开发.md |
+| **辅助** | soul-api 规范 → 三端架构 → 变更关联检查 → MySQL直接操作 |
+| **协同** | SKILL-角色流程控制.md(跨端时) |
+
+---
+
+## 三、非开发角色
+
+### 3.1 产品经理
+
+| 项目 | 说明 |
+|------|------|
+| **职责** | 需求分析、需求文档、验收标准、与开发协调 |
+| **文档** | 开发文档/1、需求/、临时需求池/、开发文档/10、项目管理/ |
+| **主 Skill** | SKILL-产品经理.md |
+| **产出** | 需求汇总、需求分析、验收清单、项目推进表 |
+
+### 3.2 测试人员
+
+| 项目 | 说明 |
+|------|------|
+| **职责** | 功能测试、回归测试、三端(小程序、管理端、API)联调验证 |
+| **测试范围** | miniprogram、soul-admin、soul-api |
+| **主 Skill** | SKILL-测试.md |
+| **产出** | 测试用例、测试报告、Bug 列表 |
+| **协同** | 与开发角色对接 Bug、验收前测试 |
+
+### 3.3 助理橙子
+
+| 项目 | 说明 |
+|------|------|
+| **职责** | 讨论后记录、文档同步、更新开发文档 |
+| **触发** | 小橙、橙子、橙橙、🍊、「讨论完毕」「记录一下」「同步到开发文档」 |
+| **主 Skill** | SKILL-助理橙子-文档同步.md |
+| **规则** | assistant-xiaofeng.mdc |
+
+---
+
+## 四、Skills 分配速查
+
+| 角色 | 主 Skill | 辅助 Skill | 协同 Skill |
+|------|----------|------------|------------|
+| 小程序开发工程师 | SKILL-小程序开发 | 三端架构、API开发、变更关联检查 | 角色流程控制 |
+| 管理端开发工程师 | SKILL-管理端开发 | 三端架构、API开发、变更关联检查 | 角色流程控制 |
+| 后端开发 | SKILL-API开发 | soul-api 规范、三端架构、变更关联检查、MySQL直接操作 | 角色流程控制 |
+| 产品经理 | SKILL-产品经理 | 需求汇总、运营与变更 | - |
+| 测试人员 | SKILL-测试 | 变更关联检查、小程序/管理端/API 规范 | - |
+| 助理橙子 | SKILL-助理橙子-文档同步 | - | - |
+
+### 通用 / 场景 Skill(全员)
+
+| 场景 | Skill | 何时选用 |
+|------|-------|----------|
+| 跨端功能开发 | SKILL-角色流程控制 | 开发涉及多端时 |
+| 变更完成 | SKILL-变更关联检查、soul-change-checklist | **开发改完必过** |
+| 文档同步 | SKILL-助理橙子-文档同步 | 讨论完毕、记录、同步文档 |
+| next-project | SKILL-next-project仅预览 | 编辑 next-project/ 或区分线上后端 |
+
+---
+
+## 五、角色推断
+
+| 触发条件 | 推断角色 | 加载 |
+|----------|----------|------|
+| 编辑 miniprogram/** | 小程序开发工程师 | SKILL-小程序开发 + soul-miniprogram-boundary |
+| 编辑 soul-admin/** | 管理端开发工程师 | SKILL-管理端开发 + soul-admin-boundary |
+| 编辑 soul-api/** | 后端开发 | SKILL-API开发 + soul-api |
+| 编辑 开发文档/1、需求/、临时需求池/ | 产品经理 | SKILL-产品经理 |
+| 说 测试、测试用例、回归测试、功能测试、QA | 测试人员 | SKILL-测试 |
+| 说 小橙、橙子、讨论完毕、记录、同步文档 | 助理橙子 | SKILL-助理橙子-文档同步 |
+
+---
+
+## 六、开发团队经验库
+
+| 项目 | 说明 |
+|------|------|
+| **位置** | `.cursor/agent/*/evolution/`,每角色独立 evolution 目录 |
+| **项目索引** | 每角色有 `项目索引.md`,根据开发进度做总结、保存进度,**每次保存写日期** |
+| **经验存储** | 按天存储,文件名 `YYYY-MM-DD.md`,在对应角色文件夹下 |
+| **用途** | 沉淀 bug 修复、最佳实践、决策、踩坑;根据经验**自动升级 Skills** |
+| **触发** | 用户说「吸收经验」「升级 skills」「记录经验」→ 助理橙子执行入库 + 升级 |
+| **流程** | 提炼 → 写入 `{角色}/YYYY-MM-DD.md` → 更新 `{角色}/项目索引.md` → 更新 `经验清单.md` → 升级 SKILL |
+
+详见 [经验清单](../agent/开发助理/经验清单.md)、[.cursor README](../README.md)。
+
+---
+
+## 七、相关文档
+
+| 文档 | 说明 |
+|------|------|
+| [经验清单](../agent/开发助理/经验清单.md) | 经验索引、Skills 升级触发 |
+| [三角色边界定义](./三角色边界定义.md) | 开发三角色源码与业务边界 |
+| [角色驱动Skills分析](./角色驱动Skills分析.md) | Skills 组织方式 |
+| [SKILL-角色流程控制](../skills/role-flow-control/SKILL.md) | 跨端协同流程 |
+
+---
+
+**更新日期**:2026-02
diff --git a/.cursor/docs/目录无法加载-排查分析.md b/.cursor/docs/目录无法加载-排查分析.md
new file mode 100644
index 0000000..d420e98
--- /dev/null
+++ b/.cursor/docs/目录无法加载-排查分析.md
@@ -0,0 +1,74 @@
+# 正式版小程序「目录无法加载数据」排查分析
+
+## 一、数据流梳理
+
+| 页面/时机 | 接口 | 用途 |
+|----------|------|------|
+| App onLaunch | `GET /api/miniprogram/book/all-chapters` | 预加载全书章节到 globalData.bookData |
+| 目录页 onLoad | `GET /api/miniprogram/book/parts` | **主接口**:懒加载篇章列表(不含章节详情) |
+| 目录页展开篇章 | `GET /api/miniprogram/book/chapters-by-part?partId=xxx` | 按篇章拉取章节列表 |
+
+**目录页展示依赖的是 `book/parts`**,不依赖 `all-chapters`。`all-chapters` 失败只会影响首页等处的预加载,目录页应能独立加载。
+
+---
+
+## 二、后端接口验证结果
+
+运行 `SOUL_TEST_ENV=soulapi python scripts/test/check-catalog-api.py` 实测:
+
+- **book/parts**:✅ 正常,返回 6 个篇章、90 个章节、5 个固定模块
+- **all-chapters**:偶发 SSL 连接中断(大响应体时)
+- **health**:偶发 SSL 握手超时
+
+**结论**:正式环境 soulapi 的 `book/parts` 接口可用且数据正常,后端不是主要瓶颈。
+
+---
+
+## 三、可能原因与排查步骤
+
+### 1. baseUrl 指向错误
+
+- **现象**:正式版请求到 souldev 或 localhost
+- **处理**:`app.js` 中 baseUrl 改为注释切换方式,正式环境使用 `https://soulapi.quwanzhi.com`
+
+### 2. 服务器域名未配置(优先排查)
+
+- **现象**:正式版请求失败,开发工具勾选「不校验合法域名」时正常
+- **处理**:微信公众平台 → 开发 → 开发管理 → 开发设置 → 服务器域名 → **request 合法域名**
+- **必须包含**:`https://soulapi.quwanzhi.com`
+- **注意**:正式版、体验版都会校验,缺配置会导致请求被拦截
+
+### 3. 正式环境数据库为空
+
+- **现象**:接口返回 `parts: []`、`totalSections: 0`
+- **排查**:执行诊断脚本,若 parts 为空则检查正式库 `chapters` 表
+- **处理**:确认正式库已导入 `soul_miniprogram.sql` 及必要迁移脚本
+
+### 4. SSL/网络不稳定
+
+- **现象**:偶发连接中断、超时
+- **排查**:多次调用诊断脚本,观察是否间歇失败
+- **处理**:检查正式服务器 SSL 配置、反向代理、超时设置
+
+### 5. 前端错误处理导致无提示
+
+- **现象**:请求失败但用户只看到空白
+- **代码**:`chapters.js` 的 `loadParts` 在 catch 中 `setData({ bookData: [], totalSections: 0 })`,不弹窗
+- **建议**:可在 catch 中增加 `wx.showToast({ title: '加载失败,请重试', icon: 'none' })` 便于用户感知
+
+---
+
+## 四、建议操作顺序
+
+1. **确认 request 合法域名**:在微信公众平台添加 `https://soulapi.quwanzhi.com`
+2. **本地验证接口**:`SOUL_TEST_ENV=soulapi python scripts/test/check-catalog-api.py`
+3. **正式版真机测试**:清除小程序缓存后重新打开,观察目录页是否加载
+4. **若仍失败**:在 `chapters.js` 的 `loadParts` 中加 `console.log` 或 `wx.showModal` 输出错误信息,便于定位
+
+---
+
+## 五、相关文件
+
+- 小程序:`miniprogram/app.js`(baseUrl)、`miniprogram/pages/chapters/chapters.js`(loadParts)
+- 后端:`soul-api/internal/handler/book.go`(BookParts、BookChaptersByPart)
+- 诊断脚本:`scripts/test/check-catalog-api.py`
diff --git a/.cursor/docs/角色协同流程图.html b/.cursor/docs/角色协同流程图.html
new file mode 100644
index 0000000..37c893f
--- /dev/null
+++ b/.cursor/docs/角色协同流程图.html
@@ -0,0 +1,140 @@
+
+
+
+
+
+ Soul 创业派对 - 角色协同流程图
+
+
+
+
+ Soul 创业派对 - 角色协同流程图
+ 小程序功能开发(新增/优化)驱动的三端协同流程 · 线框图
+
+
+
1. 主流程图(阶段划分)
+
+flowchart TB
+ subgraph 阶段1["阶段 1:需求分析与接口设计"]
+ A[需求/变更发起] --> B[API 开发者分析 miniprogram 接口]
+ A --> C[管理端开发者分析]
+ C --> C1{管理端是否需要?}
+ C1 -->|需要| C2[记录:字段/配置/审核/统计]
+ C1 -->|不需要| C3[无需管理端调整]
+ B --> D[输出接口契约]
+ C2 --> D
+ end
+
+ 阶段1 --> 阶段2
+
+ subgraph 阶段2["阶段 2:并行开发"]
+ E[API 开发者实现 miniprogram 接口]
+ F[API 开发者实现 admin/db 接口
若管理端需要]
+ G[小程序开发者实现功能]
+ E --> G
+ end
+
+ 阶段2 --> 阶段3
+
+ subgraph 阶段3["阶段 3:小程序完成 → 管理端启动"]
+ H[小程序完成并自测 ✓]
+ H --> I{管理端需要?}
+ I -->|是| J[管理端开发者开始调整]
+ I -->|否| K[跳过]
+ J --> L[API 开发者补充 admin/db
若有新增需求]
+ end
+
+ 阶段3 --> 阶段4
+
+ subgraph 阶段4["阶段 4:联调与收尾"]
+ M[三端联调]
+ N[过 soul-change-checklist]
+ O[提交]
+ M --> N --> O
+ end
+
+
+
+
+
2. 角色时序图(谁在何时做什么)
+
+sequenceDiagram
+ participant P as 产品/需求
+ participant MP as 小程序开发者
+ participant AD as 管理端开发者
+ participant API as API 开发者
+
+ P->>API: 1. 需求/变更
+ P->>AD: 1. 需求/变更
+
+ Note over API,AD: 阶段 1:需求分析
+ API->>API: 分析 miniprogram 接口需求
+ AD->>AD: 分析管理端是否需要字段/配置/审核/统计
+ AD->>API: 反馈:需要 / 不需要 + 具体项
+ API->>API: 输出接口契约
+
+ Note over API,MP: 阶段 2:并行开发
+ API->>API: 实现 miniprogram 接口
+ par 若管理端需要
+ API->>API: 实现 admin/db 接口
+ end
+ API->>MP: 接口可用
+ MP->>MP: 实现小程序功能
+
+ Note over MP,AD: 阶段 3:小程序完成 → 管理端
+ MP->>MP: 完成并自测 ✓
+ MP->>AD: 小程序完成
+ alt 管理端需要
+ AD->>AD: 开始管理端调整
+ AD->>API: 若有新增接口需求
+ API->>API: 补充 admin/db 接口
+ end
+
+ Note over API,AD: 阶段 4:联调
+ API->>API: 联调
+ MP->>MP: 联调
+ AD->>AD: 联调
+ Note over P,AD: 过 soul-change-checklist → 提交
+
+
+
+
+
3. 三角色职责与依赖
+
+flowchart LR
+ subgraph 角色["三角色"]
+ MP[小程序开发者
miniprogram/]
+ AD[管理端开发者
soul-admin/]
+ API[API 开发者
soul-api/]
+ end
+
+ subgraph 路径["API 路径"]
+ P1["/api/miniprogram/*"]
+ P2["/api/admin/*
/api/db/*"]
+ end
+
+ MP -->|只调| P1
+ AD -->|只调| P2
+ API -->|提供| P1
+ API -->|提供| P2
+
+ MP -.->|依赖| API
+ AD -.->|依赖| API
+ AD -.->|小程序完成后启动| MP
+
+
+
+
+
+
diff --git a/.cursor/docs/角色驱动Skills分析.md b/.cursor/docs/角色驱动Skills分析.md
new file mode 100644
index 0000000..e7f74d3
--- /dev/null
+++ b/.cursor/docs/角色驱动Skills分析.md
@@ -0,0 +1,91 @@
+# 角色驱动 Skills 方式 - 分析与完善
+
+## 一、当前方式概述
+
+**开发团队**五角色:小程序开发工程师、管理端开发工程师、后端开发、产品经理、助理橙子。Skills 按角色分配:
+
+> 职责定义:[开发团队职责定义.md](./开发团队职责定义.md) | 源码边界:[三角色边界定义.md](./三角色边界定义.md) | 入口:[.cursor/README.md](../README.md)
+
+- **主 Skill**:开发风格与规范(必须遵循)
+- **辅助 Skill**:按需选用
+- **协同 Skill**:跨端时用 SKILL-角色流程控制
+
+---
+
+## 二、优点
+
+| 优点 | 说明 |
+|------|------|
+| **职责清晰** | 每个角色对应明确的主 Skill,开发风格不混用 |
+| **顺序明确** | 辅助 Skill 有推荐查阅顺序,减少「不知道该看哪个」 |
+| **协同有据** | SKILL-角色流程控制 统一跨端协作流程 |
+| **与 boundary 一致** | 角色 ↔ 目录 ↔ boundary 一一对应 |
+
+---
+
+## 三、可改进点
+
+| 问题 | 影响 | 改进方向 |
+|------|------|----------|
+| **目录→角色推断不显式** | Agent 需从「编辑目录」推断「当前角色」,再查 Skills | 增加「目录→角色→应加载 Skills」速查表 |
+| **辅助 Skill 何时选用不明确** | 辅助 1、2、3 的触发场景模糊 | 为每个辅助 Skill 补充「何时选用」 |
+| **主 Skill 缺少触发词** | Cursor 可能难以自动发现应加载的 Skill | 为主 Skill 增加 YAML description,含 miniprogram、soul-admin、soul-api 等触发词 |
+| **协同场景单一** | 仅覆盖「小程序驱动」流程 | 可补充「API 先行」「管理端先行」的简要说明 |
+| **通用 Skill 与角色关系** | 变更检查、MySQL 等何时介入不够清晰 | 在角色清单中标注「变更后必过」「API 开发者数据库操作时」 |
+
+---
+
+## 四、完善措施(已实施)
+
+1. **README**:增加「目录→角色→Skills」速查表;辅助 Skill 补充「何时选用」。
+2. **soul-project-boundary**:开发时增加「根据当前编辑目录推断角色,加载对应主 Skill」。
+3. **主 Skill**:增加 YAML frontmatter,description 含触发词(miniprogram、soul-admin、soul-api)。
+4. **SKILL-角色流程控制**:补充「API 先行」「管理端先行」的简要流程说明。
+
+---
+
+## 五、使用流程(完善后)
+
+```
+1. 用户/Agent 在 miniprogram/ 下编辑
+ → 推断:当前角色 = 小程序开发者
+ → 加载:主 Skill(SKILL-小程序开发)+ 对应 boundary
+
+2. 若涉及跨端功能(如新功能需管理端配置)
+ → 加载:SKILL-角色流程控制
+ → 按阶段执行
+
+3. 变更完成后
+ → 加载:SKILL-变更关联检查、soul-change-checklist
+ → 过一遍关联层
+
+4. 若 API 开发者需操作数据库且 MCP 不可用
+ → 加载:SKILL-MySQL直接操作
+```
+
+---
+
+## 六、优化效果
+
+| 优化项 | 效果 |
+|--------|------|
+| 速查表 | 目录→角色→Skills 一目了然,减少查找时间 |
+| 何时选用 | 辅助 Skill 触发场景明确,避免误用或漏用 |
+| 主/辅 Skill frontmatter | 含触发词,便于 Cursor Agent 自动发现 |
+| 角色推断表 | soul-project-boundary 中显式映射,开发时直接对照 |
+| API/管理端先行 | 角色流程控制补充多驱动场景 |
+
+---
+
+## 七、后续迭代方向
+
+| 方向 | 说明 |
+|------|------|
+| **Glob 自动加载** | 若 Cursor 支持按 glob 自动加载 Skill,可配置 miniprogram/** → soul-miniprogram-dev |
+| **Checklist 自动化** | 变更后自动提示「请过 soul-change-checklist」 |
+| **角色切换提醒** | 跨目录编辑时提醒「当前角色已切换」 |
+| **Skill 版本号** | 主 Skill 增加版本/更新日期,便于追踪迭代 |
+
+---
+
+**更新日期**:2026-02
diff --git a/.cursor/meeting/2026-02-27_开发进度同步会议.md b/.cursor/meeting/2026-02-27_开发进度同步会议.md
new file mode 100644
index 0000000..955f3e7
--- /dev/null
+++ b/.cursor/meeting/2026-02-27_开发进度同步会议.md
@@ -0,0 +1,74 @@
+# 会议纪要 - 2026-02-27 | 开发进度同步会议
+
+> 本文件由**助理橙子**在会议结束后自动生成。
+
+---
+
+## 基本信息
+
+- **时间**:2026-02-27
+- **议题**:同步开发进度给橙子,会议结束后同步到开发文档
+- **触发方式**:开个会议
+- **参与角色**:产品经理、后端开发、管理端开发工程师、小程序开发工程师
+
+---
+
+## 各角色发言
+
+### 【产品经理】
+
+- 项目索引初始化、.cursor 规则优化已完成
+- 《分销规则》《规则说明》已讨论,决议见会议纪要
+- 待办:补充「资料不解锁」含义;明确购买内容 ≥3 章弹窗的触发与跳转逻辑
+
+### 【后端开发】
+
+- computeOrderCommission 会员分润差异化(20%/10%)已实现
+- vip_roles、vip_activated_at、referral_config 扩展已完成;miniprogram/admin/db 三组路由就绪
+- 后续:提现、找伙伴接口增加资料完善校验,返回 ERR_PROFILE_INCOMPLETE
+
+### 【管理端开发工程师】
+
+- 内容管理仅 API 按钮、推广中心、SetVipModal、VIP 角色管理、推广设置会员分润配置、VIP 排序均已落地
+- 功能:用户管理、订单管理、提现审核、VIP 管理、内容/章节管理、配置项管理、数据统计
+
+### 【小程序开发工程师】
+
+- 永平落地已完成:海报 scene、我的收益、推广中心、VIP 相关;找伙伴、提现、阅读、分销核心功能已上线
+- 后续:资料完善弹窗、≥3 章购买弹窗、找伙伴前置校验
+
+---
+
+## 讨论过程
+
+- 各角色按项目索引汇报进度,无分歧
+- 共识:永平落地与会员分润已完成,下一阶段聚焦资料完善与购买引导
+
+---
+
+## 会议决议
+
+1. 永平落地与会员分润差异化已完成
+2. 下一阶段:资料完善校验(提现/找伙伴)、≥3 章购买弹窗
+3. 搁置:打包购买引导、存客宝对接
+
+---
+
+## 待办事项
+
+| 责任角色 | 任务 | 优先级 | 截止建议 |
+|---------|------|--------|---------|
+| 产品经理 | 补充《规则说明》「资料不解锁」含义 | 中 | 开发前 |
+| 产品经理 | 明确购买内容 ≥3 章弹窗触发与跳转逻辑 | 中 | 开发前 |
+| 后端开发 | 提现、找伙伴接口增加资料完善校验 | 中 | 资料完善功能开发时 |
+| 小程序开发工程师 | 资料完善弹窗、≥3 章弹窗、找伙伴前置校验 | 中 | 按排期 |
+
+---
+
+## 各角色经验与业务理解更新
+
+> 本次为进度同步会议,无新增经验条目;开发进度已同步至开发文档。
+
+---
+
+*会议纪要由助理橙子生成 | 开发进度已同步至 `开发文档/10、项目管理/运营与变更.md` 第七部分*
diff --git a/.cursor/meeting/2026-02-28_P0测试清单.md b/.cursor/meeting/2026-02-28_P0测试清单.md
new file mode 100644
index 0000000..1076ba8
--- /dev/null
+++ b/.cursor/meeting/2026-02-28_P0测试清单.md
@@ -0,0 +1,81 @@
+# stitch_soul P0 测试清单
+
+> 测试人员按此清单验证 P0 功能。
+
+---
+
+## 一、开发完成情况
+
+| 阶段 | 状态 | 说明 |
+|------|------|------|
+| **P0** | ✅ 完成 | 首页/目录 + NEW + 精选推荐算法 |
+| P1 | 未开始 | 会员落地页 |
+| P2 | 未开始 | 导师 + 预约 |
+| P3 | 未开始 | 资料编辑扩展 |
+
+---
+
+## 二、P0 接口测试
+
+**前提**:soul-api 已启动,数据库已执行 `add-chapters-is-new.sql`。
+
+### 2.1 后端接口(可用 PowerShell 脚本或 curl 验证)
+
+```powershell
+# 在 soul-api 目录下执行
+# 在仓库根目录下执行(与 miniprogram、soul-api 同级)
+cd soul-api
+.\scripts\test-p0-endpoints.ps1
+```
+
+或手动验证:
+
+| 接口 | 期望 |
+|------|------|
+| `GET /api/miniprogram/book/all-chapters` | success: true,data 为数组,每项含 `isNew` 字段 |
+| `GET /api/miniprogram/book/recommended` | success: true,data 为 1~3 条,每项含 `tag`(热门/推荐/精选) |
+| `GET /api/miniprogram/book/latest-chapters` | success: true,data 为数组(按 updated_at 降序) |
+| `GET /api/miniprogram/book/hot` | success: true,data 为数组(按阅读量或兜底排序) |
+
+### 2.2 管理端测试
+
+| 步骤 | 操作 | 期望 |
+|------|------|------|
+| 1 | 登录 soul-admin | 成功 |
+| 2 | 进入「内容管理」 | 章节列表正常 |
+| 3 | 点击某一节「编辑」 | 弹出编辑框 |
+| 4 | 勾选「标记 NEW」并保存 | 保存成功,无报错 |
+| 5 | 刷新列表,再次编辑同一节 | 「标记 NEW」保持勾选 |
+
+### 2.3 小程序测试
+
+| 步骤 | 操作 | 期望 |
+|------|------|------|
+| 1 | 打开小程序首页 | 加载正常 |
+| 2 | 查看「最新更新」Banner | 显示一条章节,点击可进入阅读 |
+| 3 | 查看「精选推荐」 | 显示 3 条,带 热门/推荐/精选 标签 |
+| 4 | 查看「最新新增」 | 有 isNew 的章节在此展示 |
+| 5 | 进入「目录」页 | 从服务端加载,按篇章聚合 |
+| 6 | 在目录中查看标记 NEW 的章节 | 显示 NEW 标签 |
+| 7 | 查看免费/¥1 显示 | 免费节显示「免费」,付费节显示「¥1」 |
+
+---
+
+## 三、联调验证
+
+| 验证点 | 说明 |
+|--------|------|
+| 管理端标记 NEW → 小程序展示 | 在管理端勾选某节 NEW,小程序目录/首页「最新新增」应出现 |
+| 精选推荐排除序言/尾声/附录 | 若 part_title 含「序言」「尾声」「附录」,不应出现在 recommended/hot |
+| 阅读量兜底 | 无 reading_progress 数据时,hot/recommended 应返回 updated_at 排序的兜底结果 |
+
+---
+
+## 四、已知限制
+
+- **阅读量**:当前依赖 `reading_progress` 表,新环境无数据时会走兜底(按 updated_at)。
+- **固定 3 章兜底**:若连章节列表都拿不到,会返回空;未实现「预设固定 3 章」配置。
+
+---
+
+*测试完成后可更新本文件,标注通过/失败及问题。*
diff --git a/.cursor/meeting/2026-02-28_个人资料页实现评估.md b/.cursor/meeting/2026-02-28_个人资料页实现评估.md
new file mode 100644
index 0000000..402c50e
--- /dev/null
+++ b/.cursor/meeting/2026-02-28_个人资料页实现评估.md
@@ -0,0 +1,91 @@
+# 会议纪要 - 2026-02-28 | 个人资料页实现评估
+
+> 本文件由**助理橙子**在会议结束后自动生成。
+
+---
+
+## 基本信息
+
+- **时间**:2026-02-28
+- **议题**:个人资料展示页(profile-show)与编辑页(profile-edit)实现评估
+- **触发方式**:开个会议评估怎么实现
+- **参与角色**:产品经理、后端开发、管理端开发工程师、小程序开发工程师、测试人员
+
+---
+
+## 各角色发言
+
+### 【产品经理】
+
+- **展示页**(enhanced_professional_profile):面向他人查看,含头像、昵称、MBTI/地区、基本信息、个人故事、互助需求、项目介绍。
+- **编辑页**(comprehensive_profile_editor_v1_1):完整表单,需与展示页字段一一对应。
+- **待澄清**:展示页有「我擅长」展示,编辑页需补充;两页配色建议统一。
+
+**验收标准**:我的 → 展示页 → 编辑页 → 保存 → 返回,流程闭环;字段完整对应。
+
+### 【后端开发】
+
+- 现有 `GET/POST /api/miniprogram/user/profile` 已覆盖 nickname, avatar, mbti, region, industry, position, businessScale, **skills**, phone, wechatId, 个人故事三字段、互助需求两字段、projectIntro。
+- 无需新接口;skills 已支持读写,编辑页只需前端对接。
+
+### 【管理端开发工程师】
+
+- 个人资料为 C 端能力,管理端无新增任务。
+
+### 【小程序开发工程师】
+
+- **profile-show**:已按 enhanced_professional_profile 完成,配色 #5EEAD4 / #050B14 / #0F1720。
+- **profile-edit**:已按 comprehensive_profile_editor_v1_1 实现功能,配色 #4FD1C5 / #000。
+- **待做**:① 编辑页增加「我擅长」输入框;② 配色统一为 enhanced 风格。
+
+### 【测试人员】
+
+- 验证展示页与编辑页字段一致、保存后数据正确回显。
+- 手机号/微信号脱敏与复制;头像上传、昵称、MBTI 选择;空/超长输入边界。
+
+---
+
+## 讨论过程
+
+- 产品确认:skills 必须在编辑页体现。
+- 产品确认:两页配色统一为 enhanced 风格(#5EEAD4)以强化品牌一致。
+- 小程序确认:skills 后端已有,配色替换工作量小。
+
+---
+
+## 会议决议
+
+1. **skills 字段**:在 profile-edit「基本信息」区块增加「我擅长」输入框,与 profile-show 对应。
+2. **视觉统一**:profile-edit 配色统一为 enhanced(accent #5EEAD4, background #050B14, card #0F1720)。
+3. **实现顺序**:先补 skills,再做配色统一。
+4. **流程**:我的 → profile-show → ⋯ → profile-edit → 保存 → 返回,已打通,无需修改。
+
+---
+
+## 待办事项
+
+| 责任角色 | 任务 | 优先级 | 截止建议 |
+|---------|------|--------|---------|
+| 小程序开发工程师 | profile-edit 增加「我擅长」输入框及 JS 读写 | 高 | 本次迭代 |
+| 小程序开发工程师 | profile-edit 配色统一为 enhanced 风格 | 中 | 本次迭代 |
+
+---
+
+## 问题与作答区
+
+| # | 问题 | 责任角色 | 作答 |
+|---|------|---------|------|
+| 1 | 编辑页导航栏是否需增加右侧 more_horiz + 头像(设计稿有此元素)? | 产品经理 | (待补充) |
+| 2 | 展示页「成为超级个体」按钮点击后的具体跳转路径? | 产品经理 | (待补充) |
+
+---
+
+## 各角色经验与业务理解更新
+
+- 个人资料展示页与编辑页需字段、配色、流程一致,便于用户理解。
+- profile-edit 与 profile-show 共用同一套 API,skills 等扩展字段需双向同步。
+- 本次会议决议已写入本纪要;各角色经验已同步至 `agent/{角色}/evolution/2026-02-28.md`。
+
+---
+
+*会议纪要由助理橙子生成 | 2026-02-28*
diff --git a/.cursor/meeting/2026-02-28_临时需求池stitch_soul需求评审.md b/.cursor/meeting/2026-02-28_临时需求池stitch_soul需求评审.md
new file mode 100644
index 0000000..afead4a
--- /dev/null
+++ b/.cursor/meeting/2026-02-28_临时需求池stitch_soul需求评审.md
@@ -0,0 +1,572 @@
+# 会议纪要 - 2026-02-28 | 临时需求池 stitch_soul 需求评审
+
+> 本文件由**助理橙子**在会议结束后自动生成。
+
+---
+
+## 基本信息
+
+- **时间**:2026-02-28
+- **议题**:分析临时需求池 soul20260228/stitch_soul 全部需求
+- **触发方式**:开个会议,所有人都来,分析这个需求
+- **参与角色**:产品经理、后端开发、管理端开发工程师、小程序开发工程师、测试人员
+
+---
+
+## 各角色发言
+
+### 【产品经理】
+
+从 10 个稿子可归纳出 **stitch_soul 串联「内容→会员→导师」变现路径**:
+
+- **首页**(optimized_home_content_feed_v1):品牌、搜索、最新更新、阅读进度、超级个体、精选推荐、最新新增(NEW)
+- **目录**(catalog_with_new_additions_v1):73 章、篇章结构、NEW 标签、免费/¥1 付费
+- **会员落地**(premium_membership_landing_v1):¥1980/年,内容权益(章节、案例库、智能纪要、会议纪要)+ 社交权益(匹配、排行、资源、VIP 标识)
+- **导师**:列表(搜索/分类)+ 详情(介绍/服务/价格/预约),单次咨询 ¥600~2500
+- **个人资料**:展示(基本信息/个人故事/互助需求/项目介绍)、编辑(完整表单)、手机号/微信号弹窗
+- **我的**:VIP 标识、分享收益、阅读统计、最近阅读、订单
+
+**待澄清**:73 章与现有内容库是否同一套;导师与内容作者是否同一人;「案例库」是独立内容池还是章节分类;会员权益与价格策略。
+
+**建议优先级**:首页/目录/会员 > 导师 > 资料。
+
+### 【后端开发】
+
+**现有基础**:soul-api 已有 chapter、book、vip 模型;导师能力需新建或扩展现有 match 体系(现有 mentor 为 match 类型,非独立导师实体)。
+
+**待设计**:导师列表/详情/搜索筛选、预约单、会员权益与预约支付打通;接口挂 `/api/miniprogram/*`。与产品核对 chapter/book/vip 现状后,给出导师/预约/会员权益的模型与接口方案。
+
+### 【管理端开发工程师】
+
+管理端需配套:章节/导师 CRUD、NEW 标记、会员权益配置、预约单管理、收益/提现审核。接口走 `/api/admin/*`、`/api/db/*`,字段与小程序/后端一致。待后端方案确定后规划具体页面。
+
+### 【小程序开发工程师】
+
+10 个稿子覆盖首页/目录/会员/导师列表/导师详情/资料展示/资料编辑/我的五类页面,交互清晰,需 `/api/miniprogram/*`。待需求与接口确定后分阶段实现。
+
+### 【测试人员】
+
+关键场景为阅读/付费、会员、导师预约、资料完善;三端联调(小程序↔API、管理端↔API)验证点。待需求确定后补充联调用例和回归清单。
+
+---
+
+## 讨论过程
+
+- 产品询问后端:73 章、book、vip 现状是否明确。
+- 后端回复:需与产品共同核对 chapter/book/vip 后再定导师/预约/会员权益模型。
+- 管理端确认需管理章节、导师、会员、预约、收益。
+- 小程序确认页面稿清晰,等接口与需求后分阶段开发。
+- 测试:待业务规则确定后补充用例。
+
+
+---
+
+## 会议决议
+
+1. **stitch_soul 定位**: stitch 产品线在 Soul 创业派对上的扩展,串联「内容阅读 + 会员 + 导师咨询」变现路径。
+2. **产品**:需在正式需求文档中明确 73 章、导师、案例库、会员的业务定义与验收标准。
+3. **后端**:梳理 chapter/book/vip,设计导师/预约/会员权益模型与接口方案。
+4. **开发优先级**:首页/目录/会员 > 导师列表与详情 > 资料编辑 / 我的。
+5. **待确认项**:73 章与内容库关系、导师与作者关系、案例库定义、会员权益与价格。
+6. **管理端跟进原则(已采纳)**:以后小程序有功能变更时,管理端须根据 C 端能力主动补充管理功能;后端需支持对应配置能力。**本需求示例**:导师价格 → 后端支持每个导师独立价格配置(单次/半年/年度),管理端导师编辑页提供价格配置。已写入 role-flow-control、admin-dev Skill。
+
+---
+
+## 待办事项
+
+| 责任角色 | 任务 | 优先级 | 截止建议 |
+|---------|------|--------|---------|
+| 产品经理 | 撰写 stitch_soul 正式需求文档,明确业务边界 | 高 | 3 天内 |
+| 后端工程师 | 梳理 chapter/book/vip,输出导师/预约/会员权益模型与接口方案 | 高 | 产品文档确认后 |
+| 管理端开发工程师 | 待后端方案确定后,规划章节/导师/会员/预约管理页面 | 中 | 后端方案确定后 |
+| 小程序开发工程师 | 待需求与接口确定后,分阶段实现首页/目录/会员/导师/资料 | 中 | 接口确定后 |
+| 测试人员 | 待需求确定后,补充阅读/付费/会员/导师/资料联调用例 | 中 | 需求确定后 |
+
+---
+
+## 问题与作答区
+
+| # | 问题 | 责任角色 | 作答 |
+|---|------|---------|------|
+| 1 | 73 章与现有内容库是否同一套? | 产品经理 | 73 章为内容章数的统计 |
+| 2 | 导师与内容作者是否同一人? | 产品经理 | 不是。导师是导师,属于咨询服务对接的人 |
+| 3 | 「案例库」是独立内容池还是章节分类? | 产品经理 | 章节分类 |
+| 4 | 会员权益与价格策略(¥1980/年、权益边界)? | 产品经理 | 会员权益:所有章节全部免费,并自动进入超级个体名单 |
+| 5 | chapter/book/vip 现有模型与业务定义? | 后端工程师 | 见下方「后端补充说明」 |
+
+### 后端补充说明(问题 5)
+
+**Chapter(chapters 表)**
+- 每行 = 一节(section),`id` 为业务标识如 `1.1`、`preface`
+- `part_id` / `part_title`:篇章;`chapter_id` / `chapter_title`:章;`section_title`:节标题
+- `content`、`is_free`、`price`、`sort_order`:正文、免费/付费、价格、排序
+- 73 章 = chapters 表行数统计,与产品「73 章为内容章数」一致
+
+**Book**
+- 无独立表;「书」= chapters 的聚合视图
+- 接口:`/api/book/all-chapters` 返回全部 chapters,`/api/book/chapter/:id` 按 id 查单节
+
+**VIP**
+- `vip_roles` 表:超级个体角色配置(name、sort),供管理端下拉选择
+- `users` 表:`is_vip`、`vip_expire_date`、`vip_activated_at`、`vip_sort`、`vip_role`、`vip_name`、`vip_avatar`、`vip_project`、`vip_contact`、`vip_bio`
+- 权益判断:`is_vip=1` 且 `vip_expire_date>NOW()`;无则从 orders 兜底(product_type=`fullbook`/`vip`,pay_time+365 天)
+- 默认价格:¥1980;权益已定义在 vip.go(智能纪要、会议纪要库、案例库、链接资源、解锁全章、匹配伙伴、排行、VIP 标识)
+
+**精选推荐与热门章节(业务规则补充)**
+- **精选推荐**(首页「为你推荐」前 3 章):按正文章节阅读量从高到低排序,同量按更新时间;取前 3 章,依次标「热门」「推荐」「精选」。兜底:无阅读数据时按最近更新取 3 章;再兜底为预设固定 3 章。
+- **热门章节**(搜索页等):同上阅读量排序,取更多条(如 10 条)。兜底:无阅读数据时按购买次数;再兜底为默认列表。
+- **排除**:序言、尾声、附录不参与排序与推荐。
+- **管理端**:算法驱动,无需运营勾选「推荐」;固定兜底章节可产品预设或后台配置。
+
+---
+
+## 实现方案讨论(基于澄清后的需求)
+
+> 各角色分析理解 1~5 题作答及后端补充说明后,发表实现看法。
+
+### 【产品经理】
+
+需求已厘清,可按以下 MVP 范围推进:
+
+- **73 章**:沿用 chapters 表,73 = 行数统计;「案例库」按篇章/章节分类展示即可。
+- **会员**:全章免费 + 自动进入超级个体名单;¥1980 沿用现 vip 逻辑。
+- **导师**:独立于内容作者,需新建导师实体与预约流程。
+
+**验收标准建议**:① 首页展示最新更新、阅读进度、超级个体、精选推荐、最新新增;② 目录按篇章聚合、支持 NEW 标识、免费/¥1;③ 会员落地页支付后 is_vip=1、vip_expire_date 正确;④ 导师列表可搜索筛选、详情可预约;⑤ 资料编辑保存后手机/微信号必填方可使用提现与找伙伴。
+
+### 【后端工程师】
+
+**可直接复用的**:chapters、users(含 vip 字段)、orders、vip 开通逻辑。小程序已有 `/api/miniprogram/book/*`、`/api/miniprogram/vip/*`、`/api/miniprogram/user/*`。
+
+**需新增/扩展**:
+
+1. **chapters 表**:新增 `is_new`(或类似字段)支持 NEW 标签;若无则用 `created_at` 近 N 天判断。
+2. **首页聚合**:`book/latest-chapters` 已有;可新增 `book/recommended`(精选)、首页「最新新增」复用 latest 按时间筛。
+3. **导师模块**:新建 `mentors` 表(头像、姓名、简介、技能标签、价格、服务内容、判断风格等);新建 `mentor_consultations`(预约单:user_id、mentor_id、时间、状态、支付);接口:`GET/POST /api/miniprogram/mentors`(列表/详情/预约)。
+4. **个人资料扩展**:users 表可扩展 `story_*`、`help_offer`、`help_need`、`project_intro` 等;或新建 `user_profiles` 关联 users。编辑接口扩展现有 `user/profile`。
+
+**实施顺序**:① 章节 NEW 标记 + 首页/目录所需接口补齐 → ② 会员落地(现 vip 已够)→ ③ 导师表 + 预约接口 → ④ 资料扩展。
+
+### 【管理端开发工程师】
+
+**可复用**:章节管理(admin/chapters、db/book)、用户/VIP 管理(db/users、db/vip-roles)。
+
+**需新增**:
+
+1. **导师管理**:`/api/admin/mentors` 或 `/api/db/mentors`,CRUD + 上下架;依赖后端 mentors 表。
+2. **预约管理**:`/api/admin/mentor-consultations`,列表、状态筛选、导出。
+3. **章节 NEW**:若 chapters 新增 is_new,管理端章节编辑页增加「标记 NEW」勾选。
+4. **会员**:现 db/users 已支持 Set VIP;权益文案可配置化(若后续需要)。
+
+**实施顺序**:待后端 mentors、consultations 表与接口就绪后,再开发导师管理、预约管理页面;章节 NEW 可与后端同步上线。
+
+### 【小程序开发工程师】
+
+**可复用**:首页(index)、目录(catalog)、VIP 页(vip)、个人中心(profile)、支付流程。现有 `book/all-chapters`、`vip/status`、`user/profile`、`pay` 等已覆盖基础能力。
+
+**需新增/改造**:
+
+1. **首页**:按稿子接入「最新更新」「精选推荐」「最新新增」;`book/latest-chapters`、`book/hot` 已有,需确认 recommended 接口;超级个体复用 `vip/members`。
+2. **目录**:按 part 聚合、展示 NEW、免费/¥1;数据源 `book/all-chapters`,NEW 依赖后端字段或策略。
+3. **会员落地**:新页或改造 vip 页,权益展示 + 购买按钮,支付走现 `pay`。
+4. **导师**:新页「选择导师」「导师详情」,接入 `mentors` 列表/详情/预约接口。
+5. **资料编辑**:扩展表单字段(个人故事、互助需求、项目介绍)、手机/微信号弹窗(稿子 comprehensive_profile_editor_v1_2)。
+
+**实施顺序**:① 首页/目录 UI 与数据对接 → ② 会员落地 → ③ 导师列表+详情 → ④ 资料编辑扩展。
+
+### 【测试人员】
+
+**核心场景**:
+
+1. **阅读/付费**:免费节直接读;付费节未购/VIP 不可读;VIP 全章可读;单节购买与 VIP 购买互不冲突。
+2. **会员**:开通后 is_vip、vip_expire_date 正确;超级个体名单可见;权益生效。
+3. **导师**:列表搜索/筛选、详情展示、预约创建、支付(若预约收费)。
+4. **资料**:编辑保存成功;手机/微信号未填时提现、找伙伴应拦截并引导弹窗。
+
+**联调**:小程序↔API(book、vip、user、mentors);管理端↔API(chapters、mentors、consultations)。
+
+**实施顺序**:接口就绪后补充用例;优先阅读/会员,再导师、资料。
+
+### 实现路线图(共识)
+
+| 阶段 | 后端 | 管理端 | 小程序 | 测试 |
+|-----|------|-------|--------|------|
+| **P0** | chapters 支持 NEW;book/latest、book/recommended 确认或补齐 | 章节编辑支持 NEW | 首页/目录 UI 与数据对接 | 阅读/会员用例 |
+| **P1** | 会员沿用现 vip;无新增接口 | — | 会员落地页 | 会员开通验收 |
+| **P2** | mentors 表 + consultations 表;列表/详情/预约接口 | 导师 CRUD、预约列表 | 导师列表+详情+预约 | 导师预约流程 |
+| **P3** | users 扩展或 user_profiles;profile 接口扩展 | — | 资料编辑扩展、手机/微弹窗 | 资料+拦截校验 |
+
+**启动条件**:产品确认 MVP 范围与验收标准后,后端先输出 P0 接口方案,管理端/小程序按路线图跟进。
+
+---
+
+## 各开发对新需求的看法
+
+### 【后端开发】
+
+需求清晰,与现有 chapter/book/vip 模型兼容度高,可复用为主、增量开发。导师和资料扩展是主要新增点,技术风险可控。建议产品尽早确认 P2 导师价格配置方式(固定/可配置)、预约状态流转,便于接口设计。
+
+### 【管理端开发工程师】
+
+见下方「管理端建设性与补充说明」。
+
+### 【小程序开发工程师】
+
+稿子完整、交互明确,实现难度主要在数据对接和组件复用。首页/目录 P0 已落地;会员、导师、资料按阶段推进即可。建议后端接口响应格式稳定后再做样式微调,减少返工。
+
+### 【测试人员】
+
+场景边界清楚,可分批补充用例。需关注:导师预约与支付的联调、资料未填时的拦截逻辑、会员与单节购买的权益优先级。
+
+---
+
+## 管理端建设性与补充说明(基于小程序需求)
+
+> 管理端对应小程序各模块,除基础 CRUD 外,建议补充以下能力以更好支持运营与数据闭环。
+
+| 小程序模块 | 管理端基础能力 | 建设性补充 | 说明 |
+|-----------|----------------|------------|------|
+| **首页/目录** | 章节 NEW 标记 | ① 精选推荐固定兜底章节配置
② 章节阅读量/点击数据看板 | 算法兜底可运营配置;运营需看到哪些章节受欢迎 |
+| **会员落地** | 用户 VIP 开通 | ① 会员权益文案配置化
② 会员开通/续费统计 | 权益文案可随活动调整;统计支撑运营决策 |
+| **导师/预约** | 导师 CRUD、预约列表 | ① 导师排序/推荐位
② 咨询项目与价格配置
③ 预约数据统计(按导师/按时间) | 小程序列表顺序可运营控制;价格可调;数据支撑导师运营 |
+| **我的/分享收益** | (若已有收益逻辑) | ① 收益明细与分润规则配置
② 提现审核流程 | 小程序有分享收益、可提现金额,管理端需审核与配置 |
+| **个人资料** | — | ① 用户资料完善率统计
② (若涉及敏感)资料审核 | 支撑找伙伴匹配质量;可选能力 |
+
+### 管理端补充优先级建议
+
+| 优先级 | 补充项 | 与小程序关联 |
+|--------|--------|--------------|
+| 高 | 导师排序/推荐位、咨询项目价格配置 | 小程序导师列表展示顺序、v2 弹窗价格 |
+| 高 | 精选推荐兜底章节配置 | 小程序首页精选推荐无数据时的展示 |
+| 中 | 会员权益文案配置、开通统计 | 小程序会员落地页权益展示 |
+| 中 | 预约数据统计 | 导师运营效果评估 |
+| 低 | 资料完善率、提现审核 | 找伙伴质量、收益闭环 |
+
+---
+
+## 开发协作方案
+
+> 各开发角色如何协作、谁先谁后、交接点、并行与串行。
+
+### 协作总原则
+
+- **产品先行**:MVP 范围与验收标准确定后,开发方可启动。
+- **后端先行**:接口契约先出,小程序/管理端再对接。
+- **分阶段接力**:按 P0→P1→P2→P3 推进,每阶段有明确交付与验收。
+- **接口契约**:后端每阶段输出接口文档(路径、请求/响应、字段),前端按契约开发。
+
+### 阶段内协作时序
+
+```
+┌─────────────────────────────────────────────────────────────────────────────────────┐
+│ P0:首页/目录 + NEW 标记 │
+└─────────────────────────────────────────────────────────────────────────────────────┘
+
+ 产品确认 MVP 与验收
+ │
+ ▼
+ 【后端】输出 P0 接口契约
+ · chapters 是否新增 is_new?若否,说明「最新新增」判定规则(如 created_at 近 7 天)
+ · book/latest-chapters、book/recommended 响应格式
+ · book/all-chapters 是否返回 is_new 或等价信息
+ │
+ ├──────────────────────────┬──────────────────────────┐
+ ▼ ▼ ▼
+ 【后端】实现 P0 接口 【管理端】章节编辑支持 NEW 【小程序】首页/目录 UI
+ (迁移脚本 + handler) (依赖 chapters 结构) (按契约 Mock 或直连)
+ │ │ │
+ └──────────────────────────┴──────────────────────────┘
+ │
+ ▼
+ 【测试】阅读/会员用例补充 ──► 联调验证 ──► P0 验收
+```
+
+```
+┌─────────────────────────────────────────────────────────────────────────────────────┐
+│ P1:会员落地 │
+└─────────────────────────────────────────────────────────────────────────────────────┘
+
+ 无新接口,沿用现 vip 与 pay
+ │
+ ▼
+ 【小程序】会员落地页(权益展示 + 购买按钮)
+ │
+ ▼
+ 【测试】会员开通验收 ──► P1 验收
+```
+
+```
+┌─────────────────────────────────────────────────────────────────────────────────────┐
+│ P2:导师 + 预约 │
+└─────────────────────────────────────────────────────────────────────────────────────┘
+
+ 【后端】输出 P2 接口契约
+ · mentors 表结构、字段
+ · mentor_consultations 表结构、状态流转
+ · GET/POST /api/miniprogram/mentors(列表/详情/预约)
+ · GET/POST /api/admin/mentors、/api/admin/mentor-consultations
+ │
+ ├──────────────────────────┬──────────────────────────┐
+ ▼ ▼ ▼
+ 【后端】实现 mentors + 预约接口 【管理端】导师 CRUD、预约列表 【小程序】导师列表+详情+预约
+ (迁移 + 小程序接口 + admin 接口) (依赖 admin 接口) (按契约对接)
+ │ │ │
+ └──────────────────────────┴──────────────────────────┘
+ │
+ ▼
+ 【测试】导师预约流程 ──► 三端联调 ──► P2 验收
+```
+
+```
+┌─────────────────────────────────────────────────────────────────────────────────────┐
+│ P3:资料编辑扩展 │
+└─────────────────────────────────────────────────────────────────────────────────────┘
+
+ 【后端】输出 P3 接口契约
+ · users 扩展字段 或 user_profiles 表
+ · user/profile 接口扩展(个人故事、互助需求、项目介绍、手机、微信号)
+ · 提现/找伙伴入口的「手机/微未填」校验规则
+ │
+ ├──────────────────────────┐
+ ▼ ▼
+ 【后端】实现 profile 扩展 【小程序】资料编辑扩展、弹窗
+ │ │
+ └──────────────────────────┘
+ │
+ ▼
+ 【测试】资料 + 拦截校验 ──► P3 验收
+```
+
+### 角色职责与交接
+
+| 角色 | 职责 | 交接给谁 | 交接物 |
+|------|------|----------|--------|
+| **产品** | 确认 MVP、验收标准;P0 前完成 | 全体 | 需求文档(或会议纪要中验收部分) |
+| **后端** | 每阶段输出接口契约并实现 | 管理端、小程序、测试 | 接口文档(路径、字段、示例) |
+| **管理端** | P0 章节 NEW;P2 导师/预约管理 | 测试 | 可用的管理端页面 |
+| **小程序** | P0~P3 按阶段实现 C 端页面 | 测试 | 可联调的小程序 |
+| **测试** | 每阶段补充用例、联调验收 | 产品 | 验收报告 |
+
+### 并行与串行
+
+| 关系 | 说明 |
+|------|------|
+| **产品 → 后端** | 串行:产品确认后,后端才能定方案 |
+| **后端 → 管理端/小程序** | 串行开头:接口契约出后才能开发;契约出后可并行 |
+| **管理端 ↔ 小程序** | 并行:同阶段内各自对接各自接口,无互相依赖 |
+| **P0 ↔ P1** | P1 可早于 P0 完成(会员无新接口);但建议 P0 先验收再开 P2 |
+| **P2 管理端** | 依赖后端 mentors 接口;可与小程序并行,但都等后端 |
+
+### 沟通节点
+
+| 节点 | 参与 | 目的 |
+|------|------|------|
+| 需求确认会 | 产品 + 全体 | 定 MVP、验收标准 |
+| P0 接口评审 | 后端 + 管理端 + 小程序 | 确认 chapters is_new、book 接口格式 |
+| P2 接口评审 | 后端 + 管理端 + 小程序 | 确认 mentors、consultations 模型 |
+| 每阶段联调 | 后端 + 管理端 + 小程序 + 测试 | 验证功能、过 checklist |
+| 阻塞时 | 阻塞方 + 被依赖方 | 快速澄清、调整契约 |
+
+### 协作 checklist(每阶段结束前)
+
+- [ ] 后端:接口已挂到正确路由组,文档已更新
+- [ ] 管理端:仅用 `/api/admin/*`、`/api/db/*`,字段与接口一致
+- [ ] 小程序:仅用 `/api/miniprogram/*`,错误处理完整
+- [ ] 测试:用例已补充,联调通过
+- [ ] 全体:过 soul-change-checklist
+
+---
+
+## 各角色经验与业务理解更新
+
+本次会议结论已同步至各角色 `agent/{角色}/evolution/2026-02-28.md`。
+
+### 产品经理
+
+- stitch_soul 串联「内容→会员→导师」变现路径;临时需求池 10 个稿子覆盖完整流程;需在正式需求文档中明确业务定义与验收标准。
+
+### 后端开发
+
+- 需新建或扩展导师实体;现有 chapter/book/vip 可与产品核对后复用;接口挂 `/api/miniprogram/*`。
+
+### 管理端开发工程师
+
+- 需管理章节、导师、会员、预约、收益;待后端方案确定后规划管理页面。
+
+### 小程序开发工程师
+
+- 首页/目录/会员/导师/资料五类页面;待需求与接口确定后分阶段实现。
+
+### 测试人员
+
+- 关键场景:阅读/付费/会员/导师预约/资料;待需求确定后补充联调用例。
+
+### 团队共享
+
+- stitch_soul 与现有三端架构协同;路由约定保持不变:小程序 `/api/miniprogram/*`,管理端 `/api/admin/*`、`/api/db/*`。
+
+---
+
+---
+
+## 开发团队重新分析:怎么实现(可执行方案)
+
+> 在问题 1~5 作答、精选推荐算法、实现路线图基础上,结合现有代码梳理出的可执行实现方案。
+
+### 现状与差异
+
+| 能力 | 现状 | 与需求差异 |
+|------|------|------------|
+| 精选推荐 / 热门 | `book/hot` 按 sort_order 取 10 条 | 需按**阅读量**排序;排除序言/尾声/附录;精选取 3 条并打「热门/推荐/精选」 |
+| 最新更新 / 最新新增 | `book/latest-chapters` 按 updated_at 取 20 条;**未挂 miniprogram** | 需挂到 miniprogram;「最新新增」可复用或加 is_new 筛选 |
+| 目录 NEW | chapters 无 is_new | 需新增 is_new 或按 created_at 近 N 天 |
+| 会员 | vip + pay 已有 | 无差异 |
+| 导师 | 无 | 需新建 mentors、consultations |
+| 资料扩展 | user/profile 有基础字段 | 需扩展 story/help_offer/help_need/project_intro |
+
+### 分阶段实现清单(可执行)
+
+#### P0:首页/目录 + NEW + 精选推荐算法
+
+| 序号 | 角色 | 动作 | 产出 |
+|-----|------|------|------|
+| P0-1 | 后端 | chapters 表新增 `is_new`(bool);迁移脚本 + AutoMigrate | 字段可用 |
+| P0-2 | 后端 | `book/hot` 改为按阅读量排序:从 reading_progress 按 section_id 分组 count;兜底按 updated_at;排除 part 含「序言/尾声/附录」 | 符合算法 |
+| P0-3 | 后端 | 新增 `book/recommended`:同 hot 逻辑,取前 3 条,返回时带 tag(热门/推荐/精选) | 首页精选用 |
+| P0-4 | 后端 | `book/latest-chapters` 挂到 miniprogram 组 | 小程序可调 |
+| P0-5 | 后端 | `book/all-chapters` 响应中每章带 `isNew` | 目录 NEW 展示 |
+| P0-6 | 管理端 | 章节编辑页增加「标记 NEW」勾选 | 运营可配置 |
+| P0-7 | 小程序 | 首页:最新更新(latest)、精选推荐(recommended)、最新新增(all-chapters 筛 isNew)、超级个体(vip/members)、阅读进度(已有) | 首页按稿子完成 |
+| P0-8 | 小程序 | 目录:按 part 聚合、展示 NEW、免费/¥1 | 目录按稿子完成 |
+| P0-9 | 测试 | 阅读/会员用例;精选推荐取数、NEW 展示 | 联调通过 |
+
+**阅读量数据来源**:`reading_progress` 表按 `section_id` 分组 count;若无数据则用兜底(updated_at 或固定 3 章)。
+
+#### P1:会员落地
+
+| 序号 | 角色 | 动作 | 产出 |
+|-----|------|------|------|
+| P1-1 | 小程序 | 会员落地页(权益展示 + ¥1980 购买),支付走现 pay | 可开通会员 |
+| P1-2 | 测试 | 会员开通验收 | 通过 |
+
+#### P2:导师 + 预约
+
+| 序号 | 角色 | 动作 | 产出 |
+|-----|------|------|------|
+| P2-1 | 后端 | 新建 mentors 表(**支持每个导师独立价格配置**:单次/半年/年度)、mentor_consultations 表;迁移脚本 | 表就绪 |
+| P2-2 | 后端 | `GET/POST /api/miniprogram/mentors`(列表/详情/预约,价格从导师配置读取);`GET/POST /api/admin/mentors`、`/api/admin/mentor-consultations` | 接口可用 |
+| P2-3 | 管理端 | 导师管理 CRUD、**导师价格配置(每个导师独立)**、预约列表(状态筛选、导出) | 可管理导师 |
+| P2-4 | 小程序 | 导师列表、导师详情、**联系导师按钮点击 → 弹出 v2 弹窗(选择咨询项目)**、预约入口 | 可预约 |
+| P2-5 | 测试 | 导师预约流程 | 联调通过 |
+
+#### P3:资料编辑扩展
+
+| 序号 | 角色 | 动作 | 产出 |
+|-----|------|------|------|
+| P3-1 | 后端 | users 扩展 story_best_month、story_achievement、story_turning、help_offer、help_need、project_intro;或新建 user_profiles | 字段可用 |
+| P3-2 | 后端 | `user/profile` 接口读写扩展字段;提现/找伙伴入口校验手机/微 | 接口可用 |
+| P3-3 | 小程序 | 资料编辑扩展表单;手机/微未填时弹窗并拦截提现/找伙伴;**我的页头像资料卡片加「编辑」图标 → 跳转个人资料展示页** | 符合稿子 |
+| P3-4 | 测试 | 资料保存、拦截校验 | 通过 |
+
+### 接口契约速查(后端输出后可据此开发)
+
+**P0**
+
+- `GET /api/miniprogram/book/recommended` → `{ data: [{ id, mid, sectionTitle, partTitle, tag: "热门"|"推荐"|"精选", ... }] }`
+- `GET /api/miniprogram/book/hot` → 同算法,limit 10,无 tag
+- `GET /api/miniprogram/book/latest-chapters` → 新增挂载
+- `GET /api/miniprogram/book/all-chapters` → 每项增加 `isNew`
+
+**P2**
+
+- `GET /api/miniprogram/mentors?q=&skill=` → 列表(含每导师价格,从配置读取)
+- `GET /api/miniprogram/mentors/:id` → 详情(含单次/半年/年度价格,从配置读取)
+- `POST /api/miniprogram/mentors/:id/book` → 预约
+- 后端 mentors 表/模型:支持 `price_single`、`price_half_year`、`price_year` 等按导师配置;管理端 PUT `/api/admin/mentors` 或 db 接口支持写入
+
+**P3**
+
+- `user/profile` 请求/响应增加 story_*、help_offer、help_need、project_intro
+
+### 实施顺序(单人在多端开发时)
+
+1. 产品确认 MVP 与验收标准(可复用会议纪要)。
+2. 后端完成 P0-1~P0-5,输出接口契约 → 管理端 P0-6、小程序 P0-7~P0-8 并行。
+3. P0 联调验收后,P1 小程序独立完成。
+4. 后端 P2-1~P2-2 → 管理端 P2-3、小程序 P2-4 并行。
+5. 后端 P3-1~P3-2 → 小程序 P3-3。
+
+---
+
+## 附录:页面重构专项会议(设计稿全覆盖 10 张图)
+
+> **触发**:用户要求读取 stitch_soul 全部图片并开会,明确涉及「页面重构」的需求;此前会议未逐张覆盖设计稿。
+
+---
+
+### 各角色发言(页面重构专项)
+
+**【产品经理】**
+10 张稿子覆盖 8 类页面:首页、目录、会员落地、我的(VIP+收益)、个人资料展示、资料编辑(完整+弹窗)、导师列表、导师详情(含咨询选择弹窗)。页面重构优先级:首页/目录(P0 已有)→ 会员落地(P1)→ 导师(P2)→ 资料展示与编辑(P3)。需澄清:找伙伴高亮逻辑、导师详情 v1/v2 与弹窗关系、我的页与个人资料的跳转关系。
+
+**【后端开发】**
+页面重构主要影响前端;后端需配合:P2 导师列表/详情/预约接口返回的字段需支撑卡片展示(头像、简介、标签数组、价格);P3 资料扩展字段需覆盖个人故事、互助需求、项目介绍。接口契约与现有实现方案一致。
+
+**【管理端开发工程师】**
+管理端无对应设计稿,但导师管理、预约列表、章节 NEW 勾选等页面需与小程序风格一致(深色主题、标签样式)。可参考 stitch_soul 的组件规范做管理端组件库扩展。
+
+**【小程序开发工程师】**
+10 张稿子结构清晰,适合组件化:权益卡片、标签、弹窗、底部按钮、数据统计卡片可抽成通用组件。深色主题需统一定义变量。首页、目录 P0 已完成;会员落地、导师、资料按阶段推进时需严格按稿子还原布局与交互。
+
+**【测试人员】**
+页面重构验收重点:① 各页面与设计稿一致性(布局、颜色、标签);② 弹窗触发时机(手机/微未填、咨询选择);③ 深色主题在小程序中的表现;④ 组件复用时样式无错乱。
+
+---
+
+### 设计稿清单与重构识别
+
+> 基于对 stitch_soul 下 **全部 10 张 design 图片** 的逐张阅读,补充「页面重构」识别与组件化建议。此前会议未逐张覆盖,本节补齐。
+
+### 设计稿清单与重构识别
+
+| # | 目录 | 页面类型 | 页面重构要点 | 映射阶段 |
+|---|------|----------|--------------|----------|
+| 1 | `optimized_home_content_feed_v1` | 首页内容流 | **布局**:顶部品牌+搜索→最新更新大卡片→阅读进度→超级个体→精选推荐→最新新增;**组件**:搜索框、大卡片、进度条、头像列表、内容卡片、NEW 标签;**交互**:展开/折叠、价格 ¥1 | P0 |
+| 2 | `catalog_with_new_additions_v1` | 目录 | **布局**:书籍概览卡片→按 part 可展开/折叠列表;**组件**:免费/NEW/¥1 标签、章节列表项、折叠箭头;**主题**:深色模式;**交互**:找伙伴图标高亮(动态状态) | P0 |
+| 3 | `premium_membership_landing_v1` | 会员落地页 | **布局**:导航→VIP 宣传区→内容权益 4 卡 + 社交权益 4 卡(双列)→底部固定按钮;**组件**:权益卡片(图标+文字)、¥1980 按钮;**色彩**:绿/黄/橙强调色;**需提取**:权益卡片通用组件 | P1 |
+| 4 | `professional_profile_with_earnings_vip` | 我的(VIP+收益) | **布局**:用户区(VIP 徽章、会员/匹配/排行标签、到期时间)→分享收益→阅读统计→最近阅读→我的订单/关于作者;**组件**:数据卡片、统计三列、最近阅读项;**状态**:VIP、收益、可提现 | P1/P3 |
+| 5 | `enhanced_professional_profile` | 个人资料展示 | **布局**:头像+昵称+MBTI/地区→基本信息→个人故事→互助需求→项目介绍→「成为超级个体」;**组件**:卡片分组、复制按钮、奖杯/星星/循环图标;**动态内容**:故事/需求长度不固定 | P3 |
+| 6 | `comprehensive_profile_editor_v1_1` | 资料编辑(完整版) | **布局**:温馨提示→头像→基本信息→核心联系方式→个人故事→互助需求→项目介绍→保存;**组件**:表单输入、下拉、地区图钉、多行文本;**样式**:深色主题,浅绿强调 | P3 |
+| 7 | `comprehensive_profile_editor_v1_2` | 资料编辑(弹窗) | **组件**:居中弹窗,手机号/微信号输入、保存/取消;**触发**:提现/找伙伴入口时手机或微信号未填;**需设计**:弹窗触发时机、必填校验 | P3 |
+| 8 | `mentor_listing_screen` | 导师列表 | **布局**:搜索→筛选标签→推荐导师列表;**组件**:导师卡片(头像、姓名、简介、标签、价格、预约);**数据**:需头像、姓名、简介、标签数组、价格、ID | P2 |
+| 9 | `mentor_detail_profile_1` | 导师详情 v1 | **布局**:头像+姓名+理念+引言→01 为什么找→02 提供什么→03 收费标准→04 判断风格→联系导师;**组件**:编号区块、标签组、收费表格、划线价;**强调色**:青色 | P2 |
+| 10 | `mentor_detail_profile_2` | 导师详情 v2(咨询选择弹窗) | **组件**:居中弹窗,单选(单次/半年/年度)、原价划掉、推荐标签、确认选择;**背景**:模糊的「我的」页;**复用**:可与会员/购买类弹窗共用模式 | P2 |
+
+### 跨稿子组件抽取建议
+
+| 组件 | 复用页面 | 说明 |
+|------|----------|------|
+| 权益卡片 | 会员落地、导师详情 | 图标+标题+描述,圆角深灰背景 |
+| 标签(Tag) | 目录、导师列表、导师详情、个人资料 | 免费/NEW/¥1、技能标签、MBTI/地区 |
+| 弹窗(手机/微、咨询选择) | 资料编辑、导师详情 | 居中圆角、输入/单选、保存/确认+取消 |
+| 底部固定按钮 | 会员落地、导师详情、资料编辑 | 宽按钮、主色填充 |
+| 数据统计卡片 | 我的、阅读进度 | 多列数字+图标+说明 |
+| 头像+昵称+标签区 | 个人资料、超级个体、导师 | 圆形头像、下方标签 |
+
+### 深色主题与色彩体系(统一约束)
+
+- **主色**:深黑/深灰背景,白/浅灰文字
+- **强调色**:绿色(主 CTA、VIP)、黄色(会员、推荐)、橙色(社交权益、部分标签)、青色(导师详情)
+- **一致性**:所有 10 张稿均为深色模式,重构时需统一定义 CSS 变量或主题配置
+
+### 待确认(页面重构相关)— 已澄清
+
+| # | 问题 | 作答 |
+|---|------|------|
+| 1 | 底部导航「找伙伴」高亮逻辑? | **不用改**,保持现状 |
+| 2 | 导师详情 v1 与 v2 弹窗关系? | **导师详情 v1** 点击下方「联系导师」按钮 → 弹出 **v2 弹窗**(选择咨询项目) |
+| 3 | 「我的」页与「个人资料展示」的跳转? | **我的**页头像资料卡片增加「编辑」图标,点击进入**个人资料展示页**(enhanced_professional_profile) |
+
+---
+
+*会议纪要由助理橙子生成 | 各角色经验已同步至 `agent/{角色}/evolution/2026-02-28.md`*
diff --git a/.cursor/meeting/2026-02-28_文章类型普通版增值版需求分析.md b/.cursor/meeting/2026-02-28_文章类型普通版增值版需求分析.md
new file mode 100644
index 0000000..78aa604
--- /dev/null
+++ b/.cursor/meeting/2026-02-28_文章类型普通版增值版需求分析.md
@@ -0,0 +1,153 @@
+# 会议纪要 - 2026-02-28 | 文章增加类型(普通版 / 增值版)需求分析
+
+> 本文件由**助理橙子**在会议结束后自动生成。
+
+---
+
+## 基本信息
+
+- **时间**:2026-02-28
+- **议题**:文章增加类型(普通版 / 增值版),增值版后 N 章额外付费、按章累加计价
+- **触发方式**:开个会议分析需求
+- **参与角色**:产品经理、后端开发、管理端开发工程师、小程序开发工程师、测试人员
+
+---
+
+## 各角色发言
+
+### 【产品经理】
+
+**需求理解**:
+1. **普通版**:全书 9.9 元,一次性买断,与现有 fullbook 逻辑一致。
+2. **增值版**:基础价 + 「后 N 章」(N 可配置,如 10)需额外付费;每多看一章,价格 = 已付金额 + 该章单价,按章累加。
+3. **两者关系**:普通版与增值版是**分开的、互斥的两套产品**,用户购买其一,非叠加关系。
+
+**待澄清**:
+- 「后 N 章」是指全书最后 N 个 section,还是最后 N 个 chapter?
+- 增值版基础价是否仍为 9.9,还是单独定价?
+
+**用户价值**:用阶梯付费降低首购门槛,提升付费转化;增值版满足深度阅读用户需求。
+
+---
+
+### 【后端开发】
+
+**现状**:
+- `chapters` 表:`id`、`part_id`、`chapter_id`、`section_title`、`price` 等;现有按 section 购买、fullbook 9.9。
+- `orders` 表:`product_type` 含 `section`、`fullbook`、`vip`;`product_id` 存 section id 或 `fullbook`。
+
+**技术方案建议**:
+1. **书/产品类型**:增加 `book_edition` 或 `product_edition` 概念:
+ - `standard`:普通版,9.9 买断
+ - `premium`:增值版,基础价 + 增值章节按章付费
+2. **配置**:`system_config` 增加 `premium_config`:
+ - `premium_base_price`:增值版基础价
+ - `premium_chapter_count`:后 N 章(section 数量或 chapter 数量需与产品约定)
+ - `premium_section_ids`:或直接配置增值章节 id 列表(灵活)
+3. **接口**:
+ - `GET /api/miniprogram/user/purchase-status`:需区分普通版 / 增值版购买态,返回 `editionType`、`premiumPurchasedSections` 等
+ - 支付:`product_type` 扩展 `section_premium` 或沿用 `section`,`product_id` 为 section id,金额按章节 price 累加
+4. **权限**:普通版权限独立;增值版权限 = 增值版基础价已购 + 该 section 已单独付费;两者互斥。
+
+---
+
+### 【管理端开发工程师】
+
+**管理端需求**:
+1. **书籍/版本配置**:支持选择「普通版 / 增值版」或为同一本书配置两种版本。
+2. **增值章节配置**:配置「后 N 章」的 N,或勾选具体 section 作为增值章节。
+3. **章节单价**:增值章节的单价在章节编辑中维护(现有 `chapters.price`)。
+4. **价格展示**:在书籍/章节管理列表中区分普通版、增值版及增值章节。
+
+**接口依赖**:需 `GET/POST /api/db/chapters` 支持 `is_premium` 或类似标记;`/api/db/config` 或 `/api/admin/settings` 支持 premium_config。
+
+---
+
+### 【小程序开发工程师】
+
+**C 端体验**:
+1. **选购**:目录/书籍页区分「普通版 9.9」与「增值版 基础价 + 增值章节按章付费」。
+2. **阅读**:进入增值章节时,未购则展示「该章需额外 ¥X.X 解锁」或类似提示,点击发起支付。
+3. **支付流程**:与现有一致,`product_type`、`product_id`、`amount` 由后端计算并返回。
+4. **权限**:依赖 `purchase-status` 返回的 `editionType`、`premiumPurchasedSections` 等判断是否可读。
+
+**接口**:需 `miniprogram` 组下的 `purchase-status`、`pay` 支持增值版逻辑。
+
+---
+
+### 【测试人员】
+
+**测试重点**:
+1. 普通版 9.9 买断,全书可读。
+2. 增值版:基础价购买后,后 N 章仍锁定;逐章购买,价格累加正确。
+3. 边界:N=0、N=全书、章节无单价时的降级逻辑。
+4. 三端:管理端配置 → API 返回 → 小程序展示、支付、阅读权限。
+
+---
+
+## 讨论过程
+
+**产品经理**:建议「后 N 章」先按 section 数量实现,便于与现有 `chapters` 结构对齐;后续可扩展为按 chapter。
+
+**后端开发**:同意;建议 `premium_chapter_count` 表示「最后 N 个 section」,section 顺序按 `sort_order` 或 `id` 排序。
+
+**管理端开发工程师**:需在章节列表中标注「是否增值章节」,并在书籍级配置中设置 N。
+
+---
+
+## 会议决议
+
+1. **版本类型**:支持「普通版」(9.9 买断)与「增值版」(基础价 + 后 N 章按章付费);**两者分开、互斥**,用户只能购买其一。
+2. **增值章节**:「后 N 章」指全书最后 N 个 **section**(与 chapters 表结构一致);N 为可配置参数。
+3. **计价规则**:增值版基础价可配置(默认建议 9.9);增值章节单价取自 `chapters.price`;每购一章,实付 = 该章 price。
+4. **订单**:`product_type` 保留 `section`、`fullbook`;普通版用 `fullbook`;增值版用 `fullbook_premium`(基础价)和 `section`(增值章);增值章节购买用 `product_type=section`、`product_id=section_id`。
+5. **待确认项**:增值版基础价是否固定 9.9;N 默认值。
+
+---
+
+## 待办事项
+
+| 责任角色 | 任务 | 优先级 | 截止建议 |
+|---------|------|--------|---------|
+| 产品经理 | 输出增值版 MRD:基础价、N 默认值、与普通版关系 | 高 | 需求定稿前 |
+| 后端开发 | 设计 premium_config、扩展 purchase-status / pay | 高 | 方案评审后 |
+| 管理端开发工程师 | 增值章节配置 UI、书籍版本选择 | 中 | 接口就绪后 |
+| 小程序开发工程师 | 增值版选购与章节解锁流程、支付衔接 | 中 | 接口就绪后 |
+| 测试人员 | 编写增值版测试用例、边界场景 | 中 | 开发完成前 |
+
+---
+
+## 问题与作答区
+
+| # | 问题 | 责任角色 | 作答 |
+|---|------|---------|------|
+| 1 | 「后 N 章」按 section 还是 chapter 计数? | 产品经理 | 决议:按 section |
+| 2 | 增值版基础价是否固定 9.9? | 产品经理 | (待补充) |
+| 3 | 普通版与增值版是否互斥?用户能否同时拥有? | 产品经理 | **已确认:分开、互斥**,用户购买其一 |
+| 4 | N 的默认值建议?(如 10) | 产品经理 | (待补充) |
+
+---
+
+## 各角色经验与业务理解更新
+
+### 产品经理
+- 文章/书籍可区分为普通版与增值版,增值版采用「基础价 + 增值章节按章付费」模式。
+
+### 后端开发
+- 增值版需新增 `premium_config`,含 `premium_chapter_count`(后 N 个 section)、`premium_base_price`;增值章节购买沿用 `section` 订单。
+
+### 管理端开发工程师
+- 需支持「增值版配置」与「增值章节」的 N、单价维护。
+
+### 小程序开发工程师
+- 增值版需在目录与阅读页区分普通/增值,未购增值章节时展示解锁与支付入口。
+
+### 测试人员
+- 增值版需覆盖:基础价购买、逐章购买、价格累加、权限边界、配置 N=0/全书的异常场景。
+
+### 团队共享
+- 增值版计价规则:基础价 + Σ(增值章节单价),按章购买、按章累加。
+
+---
+
+*会议纪要由助理橙子生成 | 各角色经验已同步至 `agent/{角色}/evolution/2026-02-28.md`*
diff --git a/.cursor/meeting/2026-03-05_分支冲突后功能完整性分析.md b/.cursor/meeting/2026-03-05_分支冲突后功能完整性分析.md
new file mode 100644
index 0000000..0ffc11c
--- /dev/null
+++ b/.cursor/meeting/2026-03-05_分支冲突后功能完整性分析.md
@@ -0,0 +1,122 @@
+# 会议纪要 - 2026-03-05 | 分支冲突后功能完整性分析
+
+> 本文件由**助理橙子**在会议结束后自动生成。
+
+---
+
+## 基本信息
+
+- **时间**:2026-03-05
+- **议题**:分支冲突后可能有功能缺失,各成员分析自身项目完整性
+- **触发方式**:开个会议
+- **参与角色**:产品经理、后端开发、管理端开发工程师、小程序开发工程师、测试人员
+
+---
+
+## 各角色发言
+
+### 【产品经理】
+
+- 需求文档(stitch_soul、个人资料页、文章类型等)已记录,需核对 `开发文档/1、需求/`、`临时需求池/` 与实现是否一致
+- 风险:分支合并后文档可能被覆盖,导致需求与实现脱节
+- 重点确认:个人资料页、增值版/普通版计价、找伙伴联系方式完善弹窗
+
+### 【后端开发】
+
+- soul-api 被 .gitignore 排除,当前仓库无法直接检查
+- 风险:`/api/miniprogram/orders`、`/api/db/distribution` 等接口是否已实现需在 soul-api 所在位置核对
+- 建议:在 soul-api 仓库/目录确认合并状态,对照「三端需求业务对齐」逐项核对接口
+
+### 【管理端开发工程师】
+
+- 21 个路由与页面一一对应,无缺失;仅调用 `/api/admin/*`、`/api/db/*`,符合边界
+- 风险:`DistributionPage` 的 `GET /api/db/distribution` 若未实现会 404
+- 建议:全功能自测,记录 404 或异常接口反馈后端
+
+### 【小程序开发工程师】
+
+- 21 个页面均有对应目录和文件,无缺失;全部 `/api/miniprogram/*`,符合边界
+- 小问题:app.json 第 21 行多页面写同一行,建议拆行
+- 风险:`/api/miniprogram/orders` 是否已实现;`完整的` 分支上的优化(如 5a5f0087 卡片边距)是否已并入 devlop
+- 建议:核心流程自测(登录→阅读→购买→提现→找伙伴→个人资料→VIP)
+
+### 【测试人员】
+
+- 三端联调需逐项验证;回归清单应覆盖登录、VIP、阅读、分销、提现、找伙伴、个人资料、导师、购买记录
+- 风险:soul-api 无法在仓库内检查;多分支(完整的、soul-content、yongpxu-soul)合并结果需确认
+- 建议:制定「分支合并后回归清单」,各端自测 + 抽检
+
+---
+
+## 讨论过程
+
+- **乘风**:reflog 显示 devlop 曾 reset 到 58d4c0b6,后又提交 7e3d36d6;`完整的` 分支有 5a5f0087 等提交,是否已并入 devlop?
+- **小程序**:需对比 devlop 与 `完整的` 的 miniprogram 差异,确认 2026-03-03 卡片边距等优化是否保留
+- **后端**:soul-api 不在本仓库,需在 soul-api 所在位置单独确认 git 状态与合并情况
+- **管理端**:结构完整,主要风险在接口可用性,需联调确认
+
+---
+
+## 会议决议
+
+1. **小程序端**:修正 app.json 第 21 行多页面拆行;核心流程自测;确认 `/api/miniprogram/orders` 是否可用
+2. **管理端**:全功能自测,记录 404/异常接口反馈后端
+3. **后端**:在 soul-api 所在仓库确认当前分支与合并状态;核对 orders、distribution 等接口是否已实现并挂载
+4. **产品**:核对 `开发文档/`、`临时需求池/` 与实现一致性;重点确认个人资料、增值版/普通版、找伙伴
+5. **测试**:制定「分支合并后回归清单」,覆盖登录、VIP、阅读、分销、提现、找伙伴、个人资料、导师、购买记录
+6. **待确认**:`完整的` 分支上的提交是否已全部并入 devlop;soul-api 的版本管理与合并策略
+
+---
+
+## 待办事项
+
+| 责任角色 | 任务 | 优先级 | 截止建议 |
+|---------|------|--------|---------|
+| 小程序开发工程师 | 修正 app.json 第 21 行;核心流程自测;确认 orders 接口 | 高 | 2026-03-06 |
+| 管理端开发工程师 | 全功能自测,记录 404/异常接口 | 高 | 2026-03-06 |
+| 后端开发 | 在 soul-api 确认合并状态;核对 orders、distribution 接口 | 高 | 2026-03-06 |
+| 产品经理 | 核对需求文档与实现一致性 | 中 | 2026-03-06 |
+| 测试人员 | 制定「分支合并后回归清单」 | 中 | 2026-03-06 |
+
+---
+
+## 问题与作答区
+
+| # | 问题 | 责任角色 | 作答 |
+|---|------|---------|------|
+| 1 | `完整的` 分支上的提交(5a5f0087 等)是否已全部并入 devlop? | 小程序开发工程师 | (待补充) |
+| 2 | soul-api 的版本管理与合并策略是什么?是否在独立仓库? | 后端开发 | (待补充) |
+| 3 | `/api/miniprogram/orders` 是否已实现并挂载到 miniprogram 组? | 后端开发 | (待补充) |
+| 4 | `/api/db/distribution` 是否已实现? | 后端开发 | (待补充) |
+
+---
+
+## 各角色经验与业务理解更新
+
+### 产品经理
+
+- 分支冲突后需优先核对需求文档与实现一致性,避免「文档在合并中丢失」导致需求脱节
+
+### 后端开发
+
+- soul-api 被 gitignore 时,需在 soul-api 所在位置单独确认合并状态;重点核对 orders、distribution 等接口是否已实现并挂载
+
+### 管理端开发工程师
+
+- 路由与页面结构完整时,主要风险在接口可用性;全功能自测可快速暴露 404 或异常接口
+
+### 小程序开发工程师
+
+- app.json 多页面配置建议拆行便于维护;分支合并后需做核心流程自测,确认 orders 等依赖接口可用
+
+### 测试人员
+
+- 分支合并后应制定「回归清单」,覆盖三端联调关键路径;soul-api 不在仓库时需与后端协作确认接口契约
+
+### 团队共享
+
+- 分支冲突后各端需做完整性自查:产品核对需求文档、后端核对接口、管理端/小程序核对页面与功能、测试制定回归清单
+
+---
+
+*会议纪要由助理橙子生成 | 各角色经验已同步至 `agent/{角色}/evolution/2026-03-05.md`*
diff --git a/.cursor/meeting/2026-03-05_文章详情@某人加好友方案讨论.md b/.cursor/meeting/2026-03-05_文章详情@某人加好友方案讨论.md
new file mode 100644
index 0000000..f81d100
--- /dev/null
+++ b/.cursor/meeting/2026-03-05_文章详情@某人加好友方案讨论.md
@@ -0,0 +1,95 @@
+# 会议纪要 - 2026-03-05 | 文章详情 @某人 高亮与一键加好友方案讨论
+
+> 本文件由**助理橙子**在会议结束后自动生成。
+
+---
+
+## 基本信息
+
+- **时间**:2026-03-05
+- **议题**:小程序文章详情中「@某人」高亮、点击添加好友;内容编辑时如何插入并存储用户信息(含用户 id),以及是否有更优实现方式
+- **触发方式**:开一个开发大会,讨论一下
+- **参与角色**:产品经理、后端开发、管理端开发工程师、小程序开发工程师、测试人员
+
+---
+
+## 各角色发言
+
+### 【产品经理】
+
+- 需求:文章中可出现「@某某人」,名称高亮,用户点击后执行添加该人为好友;添加好友能力以接口为准。
+- 用户价值:从内容到人的关系链沉淀,提升互动与转化。
+- 业务规则:仅支持 @ 已存在用户;展示名以昵称为准;点击统一为「发起添加好友」。
+- 验收标准:文章详情内 @ 名称高亮且可点击;点击后调用添加好友流程;管理端/编辑侧能插入 @用户 并落库(含用户 id)。
+
+### 【后端开发】
+
+- 添加好友接口需在 soul-api 的 **miniprogram** 路由组下提供(如 `POST /api/miniprogram/friend/add` 或 `POST /api/miniprogram/user/add-friend`),入参建议 `targetUserId`;`开发文档/api_v1.md` 当前为存客宝文档,添加好友接口应单独约定或在该文档中新增小节。
+- 内容存储推荐**方案 A**:正文存带 @ 标记的字符串,如 `@[昵称](userId)` 或 `{{@userId:昵称}}`,后端只存不解析,由前端解析;方案 B(正文 + mentions 位置表)需维护偏移量,正文变更易错位。
+- 数据模型:内容表扩展 content 存带 @ 标记的字符串即可,无需单独表。
+
+### 【管理端开发工程师】
+
+- 文章/章节编辑页增加「插入 @用户」:选择用户后插入到光标位置,保存时写入约定格式的 content;仅调 `/api/admin/*`、`/api/db/*`。
+- 管理端列表/预览可简单高亮或原样显示,与小程序约定同一 content 格式即可。
+
+### 【小程序开发工程师】
+
+- 阅读页当前为按行渲染纯文本;需将 content 解析为「段落 + 片段」(普通文本 / mention),WXML 中对 mention 渲染为可点击 `` 并高亮,点击时调用 miniprogram 添加好友接口。
+- 继续使用现有章节内容接口,保证返回的 content 含 @ 标记;添加好友调用 miniprogram 组新接口。
+
+### 【测试人员】
+
+- 用例:无 @ 行为不变;有 @ 高亮且点击调起添加好友并提示;重复点击、未登录、无权限等边界;管理端插入 @ 后保存再编辑,内容与用户 id 不错位。
+- 联调:小程序 ↔ 章节接口、添加好友接口;管理端 ↔ 内容保存与用户列表。回归:阅读页其他功能不受影响。
+
+---
+
+## 讨论过程
+
+- 一致同意采用「正文内嵌 @ 标记」方案,格式统一为 `@[昵称](userId)`(或约定等价格式)。
+- 添加好友接口不混用存客宝 api_v1,在 soul-api miniprogram 组新增并在开发文档中明确。
+- 管理端负责插入 @ 并写入同一 content 格式;小程序负责解析、展示与点击加好友。
+
+---
+
+## 会议决议
+
+1. **内容格式**:正文使用内嵌 @ 标记,格式 `@[昵称](userId)`(或团队约定的等价格式),后端/管理端/小程序统一。
+2. **小程序**:阅读页解析 content 中 @,渲染为高亮可点击;点击时调 miniprogram 添加好友接口(如 `targetUserId`),并做结果提示。
+3. **管理端**:编辑页支持「插入 @用户」,保存时写入约定格式的 content。
+4. **后端**:提供 miniprogram 添加好友接口;章节/文章接口返回的 content 支持带 @ 标记的字符串。
+5. **待确认**:添加好友接口的最终 path、入参/出参;若已有好友/关注模型需对齐。
+
+---
+
+## 待办事项
+
+| 责任角色 | 任务 | 优先级 | 截止建议 |
+|---------|------|--------|---------|
+| 后端开发 | 在 miniprogram 组新增添加好友接口并更新开发文档 | 高 | 排期后 |
+| 管理端开发工程师 | 文章/章节编辑页支持插入 @用户并保存为约定 content 格式 | 高 | 后端接口与格式确定后 |
+| 小程序开发工程师 | 阅读页解析 @ 并高亮可点击,点击调添加好友接口 | 高 | 后端接口就绪后 |
+| 产品经理 | 确认添加好友接口 path 与业务规则(已是好友/重复请求等) | 中 | 开发前 |
+| 测试人员 | 编写 @ 展示与添加好友用例及回归清单 | 中 | 联调前 |
+
+---
+
+## 问题与作答区
+
+| # | 问题 | 责任角色 | 作答 |
+|---|------|---------|------|
+| 1 | 添加好友接口的最终 path、入参(如 targetUserId)、出参及错误码? | 后端开发 | (待补充) |
+| 2 | 开发文档中添加好友接口放在 api_v1.md 新小节还是单独文档? | 后端/产品 | (待补充) |
+| 3 | 是否已有「好友/关注」表或接口需与本次对接? | 后端开发 | (待补充) |
+
+---
+
+## 各角色经验与业务理解更新
+
+- 本次会议结论已同步至各角色当日经验文件(见 `agent/{角色}/evolution/2026-03-05.md`)。
+- 团队共享:内容 @ 采用「正文内嵌 `@[昵称](userId)`」方案;添加好友接口归属 miniprogram 组,与存客宝 api_v1 分离。
+
+---
+
+*会议纪要由助理橙子生成 | 各角色经验已同步至 `agent/{角色}/evolution/2026-03-05.md`*
diff --git a/.cursor/meeting/2026-03-05_超级个体解锁眼睛需求分析.md b/.cursor/meeting/2026-03-05_超级个体解锁眼睛需求分析.md
new file mode 100644
index 0000000..449f9da
--- /dev/null
+++ b/.cursor/meeting/2026-03-05_超级个体解锁眼睛需求分析.md
@@ -0,0 +1,78 @@
+# 需求分析 - 超级个体解锁眼睛交互改造
+
+## 基本信息
+- **时间**:2026-03-05
+- **需求来源**:用户反馈
+- **涉及页面**:`miniprogram/pages/member-detail/member-detail`
+
+---
+
+## 一、需求描述
+
+超级个体详情页点击「解锁眼睛」图标时,需调整交互逻辑:
+
+| 原逻辑 | 新逻辑 |
+|--------|--------|
+| 弹窗「成为VIP会员并完成匹配后,即可查看完整联系方式」→ 确认跳转**找伙伴/匹配页** | **不跳转匹配页**;未登录先登录;已登录按权益处理 |
+
+### 新逻辑细则
+
+1. **未登录**:弹窗「请先登录」→ 确认跳转「我的」页,用户登录后再返回操作
+2. **已登录**:
+ - **VIP 会员**(`hasFullBook`):直接解锁,可无限次解锁任意超级个体
+ - **非 VIP**:每人 **1 次免费解锁**,第 2 次起弹窗「免费次数已用完,开通 VIP(¥1980/年)可无限解锁」→ 确认跳转 **VIP 会员页**(1980 付款页)
+
+---
+
+## 二、已实现修改(小程序端)
+
+### 修改文件
+- `miniprogram/pages/member-detail/member-detail.js`
+
+### 实现要点
+
+1. **解锁状态存储**(本地 `wx.setStorageSync`)
+ - Key:`member_unlocks_{userId}`
+ - 值:已解锁的 `memberId` 数组
+ - 用于判断是否已解锁、是否已用掉免费次数
+
+2. **`unlockContact()` 流程**
+ ```
+ 点击眼睛
+ → 未登录:Modal「需要登录」→ 去登录 → switchTab 我的
+ → 已登录 + VIP:直接解锁并写入存储
+ → 已登录 + 非VIP + 首次:免费解锁并写入存储
+ → 已登录 + 非VIP + 非首次:Modal「去开通」→ navigateTo VIP 页
+ ```
+
+3. **`enrichAndFormat` 中 `contactUnlocked` / `wechatUnlocked`**
+ - 原:仅 `isMatched`(匹配过的人)
+ - 现:`isMatched || localUnlocked`(本地解锁列表也视为已解锁)
+
+---
+
+## 三、后续可选优化(后端/管理端)
+
+### 1. 后端持久化(可选)
+
+当前免费次数与解锁记录存于**本地**,换设备或清缓存会丢失。若需跨设备、防作弊,可:
+
+- 新增接口:`POST /api/miniprogram/member/unlock`
+ - 入参:`memberId`、`userId`
+ - 逻辑:校验免费次数 / VIP 权益,记录解锁关系
+- 小程序改为调用该接口,成功后更新本地展示
+
+### 2. 管理端统计(可选)
+
+- 统计「超级个体联系方式解锁」次数
+- 按用户、按超级个体维度统计
+
+---
+
+## 四、验收要点
+
+- [ ] 未登录点击眼睛 → 弹窗「需要登录」→ 确认跳转「我的」
+- [ ] 已登录 + 非 VIP + 首次 → 免费解锁,展示完整联系方式
+- [ ] 已登录 + 非 VIP + 第 2 次起 → 弹窗「去开通」→ 确认跳转 VIP 页(¥1980)
+- [ ] 已登录 + VIP → 直接解锁,不限次数
+- [ ] 不跳转匹配页
diff --git a/.cursor/meeting/2026-03-09_devlop与yongxu分支差异分析会议.md b/.cursor/meeting/2026-03-09_devlop与yongxu分支差异分析会议.md
new file mode 100644
index 0000000..d7bad79
--- /dev/null
+++ b/.cursor/meeting/2026-03-09_devlop与yongxu分支差异分析会议.md
@@ -0,0 +1,157 @@
+# 会议纪要 - 2026-03-09 | devlop 与 yongxu 分支差异分析
+
+> 本文件由**助理橙子**在会议结束后自动生成。
+
+---
+
+## 基本信息
+
+- **时间**:2026-03-09
+- **议题**:分析 devlop(dev 分支 / Mycontent-temp)与 yongxu(当前分支)两个项目的区别
+- **触发方式**:开个会议
+- **参与角色**:产品经理、后端开发、管理端开发工程师、小程序开发工程师、测试人员
+
+---
+
+## 各角色发言
+
+### 【产品经理】
+
+**分支定位**:
+- **devlop**:老板的老板主改,侧重内容管理、用户管理、找伙伴等管理端能力,开发文档已删除(迁移至他处)
+- **yongxu**:你主改,侧重 C 端体验:@提及、一键收款、个人资料页、找伙伴、推荐码绑定、分享带 ref、退款等
+
+**差异要点**:
+- devlop 删除了整个 `开发文档/` 目录(约 100+ 文件),yongxu 保留
+- 需求文档(20260308 内容管理、用户管理、找伙伴)在 devlop 侧有更新,yongxu 侧沿用旧版
+- 合并后需确保:C 端功能(@提及、推荐码、一键收款)不丢,管理端能力(ContentPage、FindPartnerPage、UsersPage)不丢
+
+### 【后端开发】
+
+**devlop 独有**(yongxu 没有):
+- `admin_dashboard.go`、`admin_rfm.go`、`admin_shensheshou.go`、`admin_user_rules.go`
+- `db_book.go`、`db_person.go`(db 路由组扩展)
+- `ckb.go` 大改(存客宝扩展)
+- `match.go`、`match_records.go` 扩展
+- `book.go`、`user.go`、`cron.go` 等逻辑更新
+- `person.go`、`user_rule.go` 等 model 新增
+- 路由、数据库、配置等变更
+
+**yongxu 独有**(devlop 没有):
+- @提及相关接口、免费章节判断、存客宝限频、退款逻辑等(在共同祖先 90d32a51 之后)
+
+**共同修改文件(易冲突)**:
+- `soul-api/internal/handler/miniprogram.go`
+- `soul-api/internal/config/config.go`
+- `soul-api/internal/database/database.go`
+- `soul-api/internal/router/router.go`
+
+### 【管理端开发工程师】
+
+**devlop 独有**(yongxu 没有):
+- `ContentPage.tsx` 大改(约 1395 行变更)
+- `ChapterTree.tsx`、`ChaptersPage.tsx` 新增/重构
+- `FindPartnerPage.tsx` 及多 Tab:CKBConfigPanel、CKBStatsTab、FindPartnerTab、MatchPoolTab、MatchRecordsTab、MentorBookingTab、MentorTab、ResourceDockingTab、TeamRecruitTab
+- `RichEditor.tsx`、`UserDetailModal.tsx` 扩展
+- `UsersPage.tsx` 大改(约 1267 行)
+- `DashboardPage.tsx`、`DistributionPage.tsx`、`SettingsPage.tsx` 等更新
+- `client.ts`、`AdminLayout.tsx`、`App.tsx` 配置调整
+
+**yongxu**:管理端改动较少,主要在小程序侧
+
+**合并策略**:以 devlop 管理端为主,yongxu 若有管理端改动需手工合入
+
+### 【小程序开发工程师】
+
+**yongxu 独有**(相对共同祖先 90d32a51):
+- `app.js`:baseUrl 真实后端、goBackOrToHome、推荐码/访问记录、checkUpdate
+- `read/*`:@提及解析与高亮、mid 优先跳转
+- `chapters/*`:章节列表、分享
+- `index/*`:首页、已读/待读
+- `my/*`:个人中心、导航栏
+
+**devlop 也改了同一批文件**:
+- `miniprogram/app.js`、`app.json`
+- `miniprogram/pages/chapters/chapters.js`、`chapters.json`
+- `miniprogram/pages/index/index.js`、`index.wxml`
+- `miniprogram/pages/my/my.js`
+- `miniprogram/pages/read/read.js`、`read.wxml`、`read.wxss`
+- `miniprogram/project.private.config.json`
+- `miniprogram/utils/readingTracker.js`
+
+**合并重点**:上述文件两分支均有修改,合并时需保留 yongxu 的 @提及、推荐码、baseUrl、goBackOrToHome 等业务逻辑,同时接纳 devlop 的其它改动(若有)
+
+### 【测试人员】
+
+合并后需做:
+- 三端联调:小程序↔API、管理端↔API
+- 回归测试:@提及、推荐码、找伙伴、内容管理、用户管理、存客宝、一键收款、退款
+- 建议合并完成后拉一份回归清单,逐项验证
+
+---
+
+## 讨论过程
+
+- 用户明确:Mycontent-temp 对应 dev 分支(devlop),当前打开的是 yongxu 分支
+- 基于 `git diff`、`git log` 分析两分支自共同祖先 90d32a51 以来的差异
+- 共识:devlop 改动量大(248 文件、约 6 万行变更),yongxu 改动小(7 文件、约 366 行),合并时需分模块处理
+
+---
+
+## 会议决议
+
+1. **差异总结**:devlop 侧重管理端与脚本(内容管理、找伙伴、飞书导出、开发文档删除);yongxu 侧重 C 端(@提及、一键收款、推荐码、baseUrl 真实后端)
+2. **合并策略**:
+ - 管理端、soul-api 新增能力:以 devlop 为主
+ - 小程序:保留 yongxu 的 @提及、推荐码、goBackOrToHome、baseUrl 等,与 devlop 改动手工合并
+ - 开发文档:若需保留,从 yongxu 恢复;若已迁移他处,可沿用 devlop 的删除
+3. **待确认项**:开发文档最终保留在仓库内还是迁移到外部?合并冲突时以哪边为准(按模块已约定)
+
+---
+
+## 待办事项
+
+| 责任角色 | 任务 | 优先级 | 截止建议 |
+|---------|------|--------|---------|
+| 用户 | 确认开发文档保留策略 | 中 | 合并前 |
+| 用户 | 执行分支合并(如 git merge devlop 或 git merge yongxu) | 高 | 待用户操作 |
+| 助理橙子 | 合并时协助逐文件解决冲突 | 高 | 用户合并时 |
+| 测试人员 | 合并后回归测试 | 中 | 合并完成 |
+
+---
+
+## 问题与作答区
+
+| # | 问题 | 责任角色 | 作答 |
+|---|------|---------|------|
+| 1 | 开发文档最终保留在仓库内还是迁移到外部? | 用户 | (待补充) |
+| 2 | 合并时以 devlop 为基准合并 yongxu,还是以 yongxu 为基准合并 devlop? | 用户 | (待补充) |
+
+---
+
+## 两分支差异速查表
+
+| 维度 | devlop(dev 分支) | yongxu(当前分支) |
+|------|-------------------|---------------------|
+| 共同祖先 | 90d32a51 | 90d32a51 |
+| 独有提交数 | 约 200+ | 2 |
+| 变更文件数 | 248 | 7 |
+| 开发文档 | 已删除 | 保留 |
+| 小程序 | 有改动(与 yongxu 重叠) | @提及、推荐码、baseUrl、goBackOrToHome 等 |
+| 管理端 | ContentPage、FindPartnerPage、UsersPage 等大改 | 改动少 |
+| soul-api | admin_*、db_*、ckb、match 等扩展 | 免费章节、存客宝限频、退款等 |
+| 脚本 | 飞书导出、content_upload、Gitea 推送等 | 无 |
+| 会议纪要 | 合并策略、管理端与 API 分析等 | 代码完整性分析、各成员功能检测 |
+
+---
+
+## 各角色经验与业务理解更新
+
+### 团队共享
+
+- 分支差异分析会议:先确定共同祖先,再用 `git diff --stat`、`git log` 分模块梳理,便于制定合并策略
+- 多分支合并时按模块约定「以谁为主」:管理端/soul-api 以 devlop 为主,小程序保留 yongxu 业务逻辑
+
+---
+
+*会议纪要由助理橙子生成 | 各角色经验已同步至 `agent/{角色}/evolution/2026-03-09.md`*
diff --git a/.cursor/meeting/2026-03-09_devlop与yongxu比较及各角色边界分析.md b/.cursor/meeting/2026-03-09_devlop与yongxu比较及各角色边界分析.md
new file mode 100644
index 0000000..ca8a20c
--- /dev/null
+++ b/.cursor/meeting/2026-03-09_devlop与yongxu比较及各角色边界分析.md
@@ -0,0 +1,173 @@
+# devlop 与 yongxu 比较及各角色边界分析 - 2026-03-09
+
+> 用户已切换至 devlop 分支。本文档对比老板(devlop)与用户(yongxu)的改动,各角色分析自身边界代码。
+
+---
+
+## 一、分支对比概览
+
+| 项目 | yongxu(你的) | devlop(当前/老板的) |
+|------|----------------|----------------------|
+| 最新 commit | c3de123e | 07e8a43b |
+| 主要改动 | @提及、一键收款、goBackOrToHome、推荐码、mid 优先跳转 | 内容管理深度优化、FindPartnerPage、神射手、RFM、dashboard-stats、推荐码自绑拦截 |
+| 开发文档 | 保留 | **已删除**(07e8a43b chore) |
+
+---
+
+## 二、【小程序开发工程师】边界分析
+
+### 2.1 API 路径合规性 ✅
+
+| 检查项 | 结果 |
+|--------|------|
+| 是否仅调用 `/api/miniprogram/*` | ✅ 是 |
+| 是否调用 `/api/admin/*` 或 `/api/db/*` | ⚠️ **read.js.backup** 调用了 `/api/db/config`(备份文件,非运行代码) |
+
+**结论**:当前运行代码(read.js、app.js、my.js 等)全部使用 `/api/miniprogram/*`,符合边界。
+
+### 2.2 devlop 中老板的改动(miniprogram)
+
+| 文件 | 改动摘要 |
+|------|----------|
+| app.js | 推荐码绑定优化:不能绑定自己的推荐码;新增 `_normalizeReferralCode`;错误处理优化 |
+| app.json | 配置调整 |
+| chapters.js | 章节列表逻辑调整(约 227 行变更) |
+| index.js | 首页逻辑调整(约 31 行) |
+| my.js | 新增 `loadDashboardStats`,调用 `/api/miniprogram/user/dashboard-stats` |
+| read.js | 阅读页逻辑调整(约 270 行变更) |
+| read.wxss | 样式调整(-7 行) |
+| readingTracker.js | 3 行变更 |
+| project.private.config.json | 配置调整 |
+
+### 2.3 yongxu 独有功能(可能被 devlop 覆盖)
+
+| 功能 | 说明 | 当前 devlop 是否保留 |
+|------|------|---------------------|
+| goBackOrToHome | 集中返回逻辑 | ✅ 保留(app.js 有) |
+| baseUrl 真实后端 | soulapi.quwanzhi.com | ✅ 保留 |
+| @提及解析与高亮 | 阅读页 contentSegments、点击添加好友 | ❌ **未保留**(read.js 无 contentSegments/mention) |
+| 一键收款 | 待确认收款、confirm-received | ✅ 保留(my.js 有) |
+| mid 优先跳转 | 分享带 mid、by-mid 接口 | ⚠️ 需核对 read.js onLoad 与 getChapterUrl |
+| 推荐码 visit/bind | 访问记录、绑定 | ✅ 保留并增强(自绑拦截) |
+
+### 2.4 待办
+
+- [ ] **@提及功能缺失**:devlop 的 read.js 无 contentSegments、ckb/lead 点击逻辑,需从 yongxu 合并或重新实现
+- [ ] 核对 read.js 的 mid 支持(onLoad、getChapterUrl、by-mid)
+- [ ] 删除或归档 read.js.backup(避免误用 /api/db/config)
+
+---
+
+## 三、【管理端开发工程师】边界分析
+
+### 3.1 API 路径合规性 ✅
+
+| 检查项 | 结果 |
+|--------|------|
+| 是否仅调用 `/api/admin/*`、`/api/db/*`、`/api/orders` | ✅ 是 |
+| 是否调用 `/api/miniprogram/*` | ✅ 否(SettingsPage 注释中提及为文档说明,非调用) |
+
+### 3.2 devlop 新增/变更的接口调用
+
+| 接口 | 页面 | 后端是否注册 |
+|------|------|-------------|
+| /api/admin/dashboard/overview | DashboardPage | ✅ |
+| /api/admin/shensheshou/query | UserDetailModal | ✅ |
+| /api/admin/shensheshou/enrich | UserDetailModal | ✅ |
+| /api/admin/shensheshou/ingest | UserDetailModal | ✅ |
+| /api/db/match-records?stats=true | CKBStatsTab | ✅ |
+| /api/db/ckb-plan-stats | CKBStatsTab | ✅ |
+| /api/db/match-pool-counts | MatchPoolTab | ✅ |
+| /api/db/users/rfm | UsersPage | ✅ |
+| /api/db/users/journey-stats | UsersPage | ✅ |
+| /api/db/user-rules | UsersPage | ✅ |
+| /api/db/persons | ContentPage | ✅ |
+| /api/db/link-tags | ContentPage | ✅ |
+| /api/db/book?action=section-orders | ContentPage | ✅ |
+| /api/db/book?action=read | ContentPage | ✅ |
+| /api/db/config/full?key=article_ranking_weights 等 | ContentPage | ✅ |
+
+**结论**:管理端新增接口均在后端 router 中注册,无 404 风险。
+
+### 3.3 devlop 新增页面
+
+| 页面 | 路由 | 说明 |
+|------|------|------|
+| FindPartnerPage | /find-partner | 找伙伴管理(含 CKB 配置、匹配池、资源对接等 Tab) |
+| ChaptersPage | /chapters | 章节管理(若为新增) |
+| RichEditor | 组件 | 富文本编辑 |
+
+### 3.4 待办
+
+- [ ] 联调验证:Dashboard、FindPartner、ContentPage、UsersPage(RFM、user-rules、journey-stats)是否正常
+
+---
+
+## 四、【后端开发】边界分析
+
+### 4.1 路由分组合规性 ✅
+
+| 路由组 | 前缀 | 使用方 | 状态 |
+|--------|------|--------|------|
+| miniprogram | /api/miniprogram/* | 小程序 | ✅ |
+| admin | /api/admin/* | 管理端 | ✅ |
+| db | /api/db/* | 管理端 | ✅ |
+
+### 4.2 devlop 新增/变更的 handler
+
+| Handler | 路由 | 用途 |
+|---------|------|------|
+| AdminDashboardOverview | GET /api/admin/dashboard/overview | 仪表盘概览 |
+| AdminShensheShouQuery | GET /api/admin/shensheshou/query | 神射手查询 |
+| AdminShensheShouIngest | POST /api/admin/shensheshou/ingest | 神射手入库 |
+| AdminShensheShouEnrich | POST /api/admin/shensheshou/enrich | 神射手 enrich |
+| UserDashboardStatsGet | GET /api/miniprogram/user/dashboard-stats | 小程序「我的」阅读统计 |
+| DBBookAction | 扩展 | action=section-orders、read 等 |
+| DBPersonList/Save/Delete | /api/db/persons | 人物管理 |
+| DBLinkTagList/Save/Delete | /api/db/link-tags | 链接标签 |
+| DBUserRulesList/Action | /api/db/user-rules | 用户规则 |
+| DBUsersRFM | GET /api/db/users/rfm | RFM 估值 |
+| DBUsersJourneyStats | GET /api/db/users/journey-stats | 用户旅程统计 |
+| DBMatchPoolCounts | GET /api/db/match-pool-counts | 匹配池统计 |
+| CKBPlanStats | GET /api/db/ckb-plan-stats | CKB 计划统计 |
+
+### 4.3 待确认
+
+| 项目 | 说明 |
+|------|------|
+| /api/orders 鉴权 | 仍在 api 根下,未经过 AdminAuth,存在未授权访问风险 |
+| 开发文档删除 | 07e8a43b 删除了开发文档目录,若需保留需从 yongxu 或历史恢复 |
+
+---
+
+## 五、合并建议
+
+### 5.1 若需将 yongxu 的改动合入 devlop
+
+1. **小程序**:`git cherry-pick` 或手工合并关键提交:
+ - @提及(若 devlop 已覆盖需确认)
+ - 一键收款(若 devlop 已覆盖需确认)
+ - 其他你独有的优化
+
+2. **管理端**:devlop 已大幅领先,无需从 yongxu 合并管理端代码
+
+3. **后端**:devlop 已包含更多 handler,yongxu 若有 soul-api 独有改动需手工核对
+
+4. **开发文档**:若需保留,可从 yongxu 或历史 commit 恢复 `开发文档/` 目录
+
+### 5.2 冲突处理优先级
+
+- 冲突时以 **devlop 为主**,再手工补回 yongxu 中你确认必须保留的功能
+
+---
+
+## 六、会议决议
+
+1. **小程序**:边界合规;需核对 @提及、一键收款、mid 在 devlop 中是否完整
+2. **管理端**:边界合规;新增接口均有对应后端
+3. **后端**:路由分组正确;/api/orders 鉴权待补
+4. **开发文档**:若需保留,可从 yongxu 恢复
+
+---
+
+*报告生成时间:2026-03-09 | 基于 devlop 分支与 yongxu 比较*
diff --git a/.cursor/meeting/2026-03-09_dev分支需求分析与yongxu迁移方案.md b/.cursor/meeting/2026-03-09_dev分支需求分析与yongxu迁移方案.md
new file mode 100644
index 0000000..2e0eb21
--- /dev/null
+++ b/.cursor/meeting/2026-03-09_dev分支需求分析与yongxu迁移方案.md
@@ -0,0 +1,165 @@
+# dev 分支需求分析与 yongxu 迁移方案
+
+> 基于用户反馈:dev 分支只改了样式,未考虑后端/API/小程序三端协调,导致 bug 或功能不全;yongxu 分支功能完善。本文档分析 dev 需求、识别三端缺口,并给出「新功能迁入 yongxu + 补全功能」的执行方案。
+
+---
+
+## 一、dev 分支变更概览
+
+### 1.1 变更性质(用户结论)
+
+- **dev 侧重**:管理端 UI/样式、新页面布局
+- **问题**:未同步考虑 soul-api、miniprogram 的接口与逻辑,导致:
+ - 管理端新页面调用的接口在 yongxu 的 soul-api 中不存在或不全
+ - 小程序端与 dev 管理端配置/数据模型不一致
+ - 三端联调时出现 bug 或功能不全
+
+### 1.2 dev 相对 yongxu 的增量(按模块)
+
+| 模块 | dev 新增/改动 | 依赖的 API/能力 |
+|------|---------------|-----------------|
+| **soul-admin** | ContentPage 大改、ChapterTree、RichEditor、@提及、persons、link-tags 配置 | `/api/db/book`(含 section-orders)、`/api/db/persons`、`/api/db/link-tags`、`/api/db/config/full?key=xxx` |
+| **soul-admin** | FindPartnerPage + 多 Tab(CKBConfigPanel、CKBStatsTab、MatchPoolTab 等) | `/api/db/config/full?key=ckb_config`、`/api/db/ckb-leads` |
+| **soul-admin** | UsersPage 大改、UserDetailModal 扩展 | `/api/db/users` 已有,可能需扩展字段 |
+| **soul-admin** | ChaptersPage 新增、DashboardPage 调整 | 依赖 db/book、db/users 等 |
+| **soul-admin** | DistributionPage、SettingsPage 等样式调整 | 现有 admin 接口 |
+| **soul-api** | admin_dashboard、admin_rfm、admin_shensheshou、admin_user_rules | 新增 handler |
+| **soul-api** | db_person、db_book 扩展(section-orders、move-sections) | 扩展 db_book action |
+| **soul-api** | ckb 扩展、match/match_records 扩展 | 扩展 ckb、match handler |
+| **miniprogram** | 部分页面简化/样式调整 | 与 yongxu 的 @提及、推荐码等可能冲突 |
+
+---
+
+## 二、三端缺口分析(yongxu 当前缺失)
+
+### 2.1 soul-api 缺口(管理端新页面依赖)
+
+| 接口/能力 | dev 管理端调用 | yongxu 是否已有 | 缺口说明 |
+|-----------|---------------|-----------------|----------|
+| `GET/POST/DELETE /api/db/persons` | ContentPage @提及人物配置 | ❌ 无 | 需新增 persons 表、model、handler |
+| `GET/POST/DELETE /api/db/link-tags` | ContentPage 链接标签配置 | ❌ 无 | 需新增 link_tags 表、model、handler |
+| `GET /api/db/book?action=section-orders&id=xxx` | ContentPage 章节内排序 | ❌ 无 | 需在 db_book 中扩展 action |
+| `PUT /api/db/book` action=move-sections | ContentPage 跨章移动 | ❌ 无 | 需在 db_book 中扩展 |
+| `GET /api/db/config/full?key=article_ranking_weights` | ContentPage | ✅ 有 | DBConfigGet 已支持 |
+| `GET /api/db/config/full?key=pinned_section_ids` | ContentPage | ✅ 有 | 同上 |
+| `GET /api/db/config/full?key=unpaid_preview_percent` | ContentPage | ✅ 有 | 同上 |
+| `GET /api/db/config/full?key=ckb_config` | FindPartnerPage CKBConfigPanel | ✅ 有 | 同上 |
+| `GET /api/db/ckb-leads?mode=submitted&page=1&pageSize=50` | FindPartnerPage 存客宝线索 | ❌ 无 | 需新增 ckb_leads 相关接口 |
+| admin_dashboard、admin_rfm、admin_shensheshou、admin_user_rules | Dashboard、神奢手等 | ❌ 无 | 可选,按业务需要 |
+
+### 2.2 小程序端
+
+- **yongxu 已完善**:@提及、推荐码、baseUrl、goBackOrToHome、一键收款、免费章节、存客宝限频、退款等
+- **策略**:**保留 yongxu 小程序全部逻辑**,不引入 dev 对 miniprogram 的简化(dev 可能删减了功能)
+
+### 2.3 管理端(soul-admin)
+
+- **yongxu 已有**:ContentPage、ChapterTree、UsersPage、DashboardPage 等基础版本
+- **dev 增量**:更丰富的 ContentPage(persons、link-tags、RichEditor @提及)、FindPartnerPage 整页、UsersPage 扩展
+- **策略**:在 soul-api 补全接口后,再选择性迁入 dev 的页面组件
+
+---
+
+## 三、迁移原则
+
+1. **基准分支**:yongxu(功能完善、三端协调良好)
+2. **不引入**:dev 对 miniprogram 的改动(避免覆盖 yongxu 的 @提及、推荐码等)
+3. **分阶段**:先补 soul-api 缺口 → 再迁入 soul-admin 新页面/组件
+4. **三端协调**:每迁入一个管理端功能,必须确保 soul-api 已有对应接口,且小程序若依赖该配置则已对齐
+
+---
+
+## 四、执行方案(按优先级)
+
+### 阶段 1:soul-api 补全(必须)
+
+| 序号 | 任务 | 说明 |
+|------|------|------|
+| 1.1 | 新增 `db/persons` | model.Person、表 persons,GET/POST/DELETE,供 ContentPage @提及人物配置 |
+| 1.2 | 新增 `db/link-tags` | model.LinkTag、表 link_tags,GET/POST/DELETE,供 ContentPage 链接标签 |
+| 1.3 | 扩展 `db/book` | 增加 action=section-orders、action=move-sections(若 ContentPage 需要) |
+| 1.4 | 新增 `db/ckb-leads` | 若存在 ckb_leads 表,GET 支持 mode=submitted/contact 分页;否则先建表再实现 |
+
+### 阶段 2:soul-admin 迁入(在阶段 1 完成后)
+
+| 序号 | 任务 | 说明 |
+|------|------|------|
+| 2.1 | ContentPage 增强 | 从 dev 迁入 persons、link-tags 配置区,RichEditor @提及(需 persons 接口) |
+| 2.2 | FindPartnerPage | 从 dev 迁入整页 + Tab(CKBConfigPanel、CKBStatsTab 等),需 ckb_config、ckb-leads 接口 |
+| 2.3 | UsersPage 扩展 | 从 dev 迁入 UserDetailModal 等扩展(若字段与 yongxu 的 db/users 一致) |
+| 2.4 | 菜单与路由 | 在 AdminLayout、App.tsx 中增加 FindPartner 入口(若尚未有) |
+
+### 阶段 3:可选(按业务需要)
+
+| 序号 | 任务 | 说明 |
+|------|------|------|
+| 3.1 | admin_dashboard、admin_rfm 等 | 若 Dashboard 需要新统计维度,再补充 |
+| 3.2 | dev 的 scripts、飞书导出等 | 与三端功能无直接关系,可单独评估 |
+
+---
+
+## 五、补全功能清单(可直接用于开发)
+
+### 5.1 soul-api 必须补全
+
+```
+[ ] 1. persons 表 + model + GET/POST/DELETE /api/db/persons
+[ ] 2. link_tags 表 + model + GET/POST/DELETE /api/db/link-tags
+[ ] 3. db_book 扩展:GET ?action=section-orders&id=xxx
+[ ] 4. db_book 扩展:PUT action=move-sections(若 ContentPage 需要)
+[ ] 5. ckb_leads 表(若无)+ GET /api/db/ckb-leads?mode=xxx&page=1&pageSize=50
+```
+
+### 5.2 soul-admin 迁入(依赖 5.1)
+
+```
+[ ] 6. ContentPage:persons 配置区、link-tags 配置区
+[ ] 7. ContentPage:RichEditor @提及(调用 persons)
+[ ] 8. FindPartnerPage + Tabs(CKBConfigPanel、CKBStatsTab、FindPartnerTab 等)
+[ ] 9. AdminLayout:增加「找伙伴」菜单项(若尚无)
+[ ] 10. UsersPage:UserDetailModal 扩展(按需)
+```
+
+### 5.3 小程序
+
+```
+[ ] 无需变更,保留 yongxu 全部逻辑
+```
+
+---
+
+## 六、迁移操作建议
+
+### 6.1 不推荐直接 merge devlop
+
+- dev 与 yongxu 在 miniprogram、部分 soul-api 上存在冲突
+- 直接 merge 会覆盖 yongxu 的完善功能
+
+### 6.2 推荐方式
+
+1. **保持当前在 yongxu 分支**
+2. **按阶段 1**:在 yongxu 上直接开发 soul-api 补全(persons、link-tags、db_book 扩展、ckb-leads)
+3. **按阶段 2**:用 `git show devlop:path/to/file` 取出 dev 的 soul-admin 文件,手工合并到 yongxu,并确认调用的接口已在 soul-api 中存在
+4. **每完成一个功能**:过 soul-change-checklist,做三端联调验证
+
+### 6.3 冲突文件处理(若必须 diff 参考)
+
+- `miniprogram/*`:**以 yongxu 为准**,不采纳 dev 的改动
+- `soul-admin/*`:选择性采纳 dev 的 UI/组件,确保调用的 API 已在 yongxu 的 soul-api 中实现
+- `soul-api/*`:以 yongxu 为基础,缺什么补什么(persons、link-tags、ckb-leads 等)
+
+---
+
+## 七、总结
+
+| 项目 | 结论 |
+|------|------|
+| 基准 | yongxu |
+| dev 价值 | 管理端 UI 增强(ContentPage、FindPartnerPage、UsersPage) |
+| dev 问题 | 未配套 soul-api 的 persons、link-tags、ckb-leads 等,导致功能不全 |
+| 迁移路径 | 先补 soul-api → 再迁入 soul-admin 新页面 |
+| 小程序 | 不改动,保留 yongxu |
+
+---
+
+*文档生成:2026-03-09 | 供开发执行参考*
diff --git a/.cursor/meeting/2026-03-09_代码完整性分析与分支合并准备.md b/.cursor/meeting/2026-03-09_代码完整性分析与分支合并准备.md
new file mode 100644
index 0000000..d0b92d1
--- /dev/null
+++ b/.cursor/meeting/2026-03-09_代码完整性分析与分支合并准备.md
@@ -0,0 +1,137 @@
+# 会议纪要 - 2026-03-09 | 代码完整性分析与分支合并准备
+
+> 本文件由**助理橙子**在会议结束后自动生成。
+
+---
+
+## 基本信息
+
+- **时间**:2026-03-09
+- **议题**:分析当前代码完整性;记录你(yongxu 分支)的改动进度;待切换分支后做比较与合并
+- **触发方式**:开会
+- **参与角色**:产品经理、后端开发、管理端开发工程师、小程序开发工程师、测试人员
+
+---
+
+## 各角色发言
+
+### 【产品经理】
+
+当前 yongxu 分支已实现的功能(从提交记录推断):@提及、一键收款、个人资料页、找伙伴、推荐码绑定、分享带 ref、退款等。需要与 devlop 分支的需求文档(20260308 内容管理、用户管理、找伙伴等)做对照,确保合并后需求不遗漏。
+
+### 【后端开发】
+
+- **yongxu 独有**:@提及相关接口、免费章节判断、存客宝限频、退款逻辑等
+- **devlop 独有**:内容管理深度优化、admin_dashboard、admin_rfm、admin_shensheshou、admin_user_rules、ckb 扩展、match/match_records、db_book、db_person 等
+- **合并风险**:soul-api 多处 handler 可能冲突,需逐文件比对
+
+### 【管理端开发工程师】
+
+- **devlop 新增**:ContentPage 大改、ChapterTree、ChaptersPage、FindPartnerPage 及多 Tab、RichEditor、UserDetailModal 扩展、UsersPage 扩展等
+- **yongxu**:管理端改动较少
+- **合并策略**:devlop 管理端改动量大,建议以 devlop 为主,yongxu 若有管理端改动需手工合入
+
+### 【小程序开发工程师】
+
+- **yongxu 独有**:app.js(baseUrl 真实后端、goBackOrToHome、推荐码/访问记录)、chapters、index、my、read 等页面的 @提及、mid 优先跳转、一键收款等
+- **devlop 独有**:部分配置、脚本、文档
+- **合并重点**:miniprogram/app.js、read.js、chapters.js 等可能冲突,需保留 yongxu 的业务逻辑
+
+### 【测试人员】
+
+合并后需做:三端联调(小程序↔API、管理端↔API)、@提及、推荐码、找伙伴、内容管理、用户管理、存客宝等回归测试。建议合并完成后拉一份回归清单。
+
+---
+
+## 讨论过程
+
+- 用户明确:老板的老板在 devlop 上改了代码,用户当前在 yongxu,尚未切换分支
+- 决议:先记录 yongxu 当前状态,待用户切换分支后再执行比较与合并动作
+
+---
+
+## 会议决议
+
+1. **记录 yongxu 当前状态**:已写入本纪要下方的「yongxu 分支快照」
+2. **合并策略**:用户切换分支后,由助理执行 `git diff` 比较,并协助合并
+3. **待确认项**:用户切换到哪个分支(devlop / main)需用户明确
+
+---
+
+## 待办事项
+
+| 责任角色 | 任务 | 优先级 | 截止建议 |
+|---------|------|--------|---------|
+| 用户 | 切换分支(如 git checkout devlop) | 高 | 待用户操作 |
+| 助理橙子 | 切换后执行 diff 比较、协助合并 | 高 | 用户切换后 |
+| 测试人员 | 合并后回归测试 | 中 | 合并完成 |
+
+---
+
+## 问题与作答区
+
+| # | 问题 | 责任角色 | 作答 |
+|---|------|---------|------|
+| 1 | 用户将切换到 devlop 还是 main? | 用户 | (待补充) |
+| 2 | 合并冲突时以哪边为准? | 用户 | (待补充) |
+
+---
+
+## yongxu 分支快照(供后续比较与合并)
+
+> **重要**:以下为 2026-03-09 会议时记录,供切换分支后对比使用。
+
+### 分支与提交
+
+| 项目 | 值 |
+|------|-----|
+| 当前分支 | `yongxu` |
+| 当前 commit | `c3de123ef8b5e971888739999816d13d4f78bd4d` |
+| 工作区状态 | clean(无未提交变更) |
+| 对比目标 | `origin/devlop` (`868b0a10`) |
+
+### yongxu 独有提交(相对 origin/main,前 10 条)
+
+```
+c3de123e 1
+90d32a51 更新小程序配置,切换API基础地址至真实后端。实现@用户提及功能...
+73ecead3 更新小程序配置,切换API基础地址至本地开发环境。优化用户提交联系方式...
+68520043 实现@提及功能,允许用户在阅读页中高亮并点击提及的用户...
+9aaffd80 更新.gitignore文件...
+2af49611 新增一键收款功能...
+04b6924a 重构跨多个页面的导航逻辑,goBackOrToHome...
+3b193fb5 优化个人中心页面,调整导航栏布局...
+...
+```
+
+### 关键文件(yongxu 侧你已改动的)
+
+| 文件 | 说明 |
+|------|------|
+| miniprogram/app.js | baseUrl 真实后端、goBackOrToHome、推荐码/访问记录、checkUpdate |
+| miniprogram/pages/read/* | @提及解析与高亮、mid 优先跳转 |
+| miniprogram/pages/chapters/* | 章节列表、分享 |
+| miniprogram/pages/index/* | 首页、已读/待读 |
+| miniprogram/pages/my/* | 个人中心、导航栏 |
+| soul-api/* | 免费章节、存客宝、退款等 |
+| soul-admin/* | 若有改动需核对 |
+
+### devlop 独有(老板的老板的改动,摘要)
+
+- **soul-admin**:ContentPage、ChapterTree、FindPartnerPage、RichEditor、UsersPage、UserDetailModal 等大量改动
+- **soul-api**:admin_dashboard、admin_rfm、admin_shensheshou、ckb、match、db_book、db_person 等
+- **scripts**:飞书同步、Gitea 推送、content_upload 等
+- **开发文档**:20260308 内容管理、用户管理、找伙伴需求等
+
+---
+
+## 各角色经验与业务理解更新
+
+### 团队共享
+
+- 分支合并前先记录当前分支状态(commit、关键文件列表),便于后续 diff 与合并决策
+- 多人在不同分支开发时,合并策略需提前约定(以谁为主、冲突解决规则)
+
+---
+
+*会议纪要由助理橙子生成 | 快照供切换分支后比较与合并使用*
diff --git a/.cursor/meeting/2026-03-09_各成员功能检测报告.md b/.cursor/meeting/2026-03-09_各成员功能检测报告.md
new file mode 100644
index 0000000..ced6977
--- /dev/null
+++ b/.cursor/meeting/2026-03-09_各成员功能检测报告.md
@@ -0,0 +1,178 @@
+# 各成员功能检测报告 - 2026-03-09
+
+> 按角色检测小程序、管理端、后端的 API 调用与路由匹配、边界合规性。
+
+---
+
+## 一、小程序开发工程师(miniprogram/)
+
+### 1.1 API 路径合规性 ✅
+
+| 检查项 | 结果 |
+|--------|------|
+| 是否仅调用 `/api/miniprogram/*` | ✅ 是(除 read.js.backup 外) |
+| 是否调用 `/api/admin/*` 或 `/api/db/*` | ❌ **read.js.backup** 调用了 `/api/db/config`(边界违规) |
+
+**说明**:`read.js.backup` 为备份文件,当前运行的 `read.js` 已使用 `/api/miniprogram/*`,无违规。建议删除或重命名 `.backup` 文件,避免误用。
+
+### 1.2 小程序调用的接口 vs 后端路由
+
+| 接口路径 | 后端是否注册 | 说明 |
+|----------|-------------|------|
+| /api/miniprogram/config | ✅ | GetPublicDBConfig |
+| /api/miniprogram/login | ✅ | MiniprogramLogin |
+| /api/miniprogram/phone-login | ✅ | WechatPhoneLogin |
+| /api/miniprogram/book/all-chapters | ✅ | |
+| /api/miniprogram/book/chapter/:id | ✅ | |
+| /api/miniprogram/book/chapter/by-mid/:mid | ✅ | |
+| /api/miniprogram/book/hot | ✅ | |
+| /api/miniprogram/book/recommended | ✅ | |
+| /api/miniprogram/book/latest-chapters | ✅ | |
+| /api/miniprogram/book/search | ✅ | |
+| /api/miniprogram/book/stats | ✅ | |
+| /api/miniprogram/referral/visit | ✅ | |
+| /api/miniprogram/referral/bind | ✅ | |
+| /api/miniprogram/referral/data | ✅ | |
+| /api/miniprogram/earnings | ✅ | MyEarnings |
+| /api/miniprogram/match/config | ✅ | |
+| /api/miniprogram/match/users | ✅ | |
+| /api/miniprogram/ckb/join | ✅ | |
+| /api/miniprogram/ckb/match | ✅ | |
+| /api/miniprogram/ckb/lead | ✅ | |
+| /api/miniprogram/upload | ✅ | |
+| /api/miniprogram/user/* | ✅ | profile、addresses、check-purchased、purchase-status、reading-progress、update |
+| /api/miniprogram/withdraw/* | ✅ | withdraw、records、pending-confirm、confirm-received、confirm-info |
+| /api/miniprogram/vip/* | ✅ | status、profile、members |
+| /api/miniprogram/users | ✅ | MiniprogramUsers |
+| /api/miniprogram/orders | ✅ | MiniprogramOrders |
+| /api/miniprogram/mentors | ✅ | |
+| /api/miniprogram/mentors/:id | ✅ | |
+| /api/miniprogram/mentors/:id/book | ✅ | |
+| /api/miniprogram/about/author | ✅ | |
+| /api/miniprogram/pay | ✅ | |
+| /api/miniprogram/qrcode | ✅ | |
+| /api/miniprogram/phone | ✅ | |
+
+**结论**:小程序调用的接口均在后端路由中注册,无 404 风险。
+
+### 1.3 其他问题
+
+| 问题 | 建议 |
+|------|------|
+| app.json 第 19 行多页面写同一行 | 建议拆行便于维护(2026-03-05 会议已建议) |
+| read.js.backup 调用 /api/db/config | 删除或归档该备份文件 |
+
+---
+
+## 二、管理端开发工程师(soul-admin/)
+
+### 2.1 API 路径合规性 ✅
+
+| 检查项 | 结果 |
+|--------|------|
+| 是否仅调用 `/api/admin/*`、`/api/db/*`、`/api/orders` 等管理端接口 | ✅ 是 |
+| 是否调用 `/api/miniprogram/*` | ✅ 否 |
+
+### 2.2 管理端调用的接口 vs 后端路由
+
+| 接口路径 | 后端是否注册 | 页面 |
+|----------|-------------|------|
+| /api/admin/logout | ✅ | AdminLayout |
+| /api/admin/referral-settings | ✅ | ReferralSettingsPage |
+| /api/admin/withdrawals | ✅ | WithdrawalsPage、DistributionPage |
+| /api/admin/orders/refund | ✅ | OrdersPage、DistributionPage |
+| /api/admin/distribution/overview | ✅ | DistributionPage |
+| /api/admin/author-settings | ✅ | AuthorSettingsPage |
+| /api/admin/settings | ✅ | SettingsPage |
+| /api/admin/users | ✅ | AdminUsersPage |
+| /api/db/users | ✅ | UsersPage、DistributionPage、OrdersPage、UserDetailModal、SetVipModal |
+| /api/db/users/referrals | ✅ | UsersPage、UserDetailModal |
+| /api/db/book | ✅ | ContentPage |
+| /api/db/config | ✅ | PaymentPage、SitePage、QRCodesPage、MatchPage |
+| /api/db/config/full | ✅ | MatchPage |
+| /api/db/vip-roles | ✅ | VipRolesPage、SetVipModal |
+| /api/db/mentors | ✅ | MentorsPage |
+| /api/db/match-records | ✅ | MatchRecordsPage |
+| /api/db/mentor-consultations | ✅ | MentorConsultationsPage |
+| /api/orders | ✅ | OrdersPage |
+
+**结论**:管理端调用的接口均在后端路由中注册,无 404 风险。
+
+### 2.3 路由与页面对应
+
+| 路由 | 页面 | 状态 |
+|------|------|------|
+| /dashboard | DashboardPage | ✅ |
+| /orders | OrdersPage | ✅ |
+| /users | UsersPage | ✅ |
+| /distribution | DistributionPage | ✅ |
+| /withdrawals | WithdrawalsPage | ✅ |
+| /content | ContentPage | ✅ |
+| /referral-settings | ReferralSettingsPage | ✅ |
+| /author-settings | AuthorSettingsPage | ✅ |
+| /vip-roles | VipRolesPage | ✅ |
+| /mentors | MentorsPage | ✅ |
+| /mentor-consultations | MentorConsultationsPage | ✅ |
+| /admin-users | AdminUsersPage | ✅ |
+| /settings | SettingsPage | ✅ |
+| /payment | PaymentPage | ✅ |
+| /site | SitePage | ✅ |
+| /qrcodes | QRCodesPage | ✅ |
+| /match | MatchPage | ✅ |
+| /match-records | MatchRecordsPage | ✅ |
+| /api-doc | ApiDocPage | ✅ |
+
+**结论**:21 个路由与页面一一对应,无缺失。
+
+---
+
+## 三、后端开发(soul-api/)
+
+### 3.1 路由分组
+
+| 路由组 | 前缀 | 使用方 | 状态 |
+|--------|------|--------|------|
+| miniprogram | /api/miniprogram/* | 小程序 | ✅ |
+| admin | /api/admin/* | 管理端 | ✅ |
+| db | /api/db/* | 管理端 | ✅ |
+| 支付回调 | /api/payment/*、/api/miniprogram/pay/notify | 微信/支付宝 | ✅ |
+
+### 3.2 待确认项
+
+| 项目 | 说明 |
+|------|------|
+| /api/orders 鉴权 | 该接口在 api 根下直接挂载,**未经过 AdminAuth**。OrdersList handler 未做鉴权校验,存在未授权访问风险。建议将 /api/orders 移入 admin 组或单独加 AdminAuth |
+| soul-api 版本管理 | 若 soul-api 在独立仓库或 .gitignore 排除,合并后需在 soul-api 所在位置单独确认 |
+
+---
+
+## 四、测试人员
+
+### 4.1 建议回归清单
+
+| 场景 | 验证点 |
+|------|--------|
+| 小程序登录 | 微信登录、手机号、token 持久化 |
+| 购买与支付 | 下单、微信支付、回调更新、购买状态 |
+| 推荐与分润 | 扫码/分享带 ref、绑定、分润计算 |
+| VIP 功能 | 开通、资料填写、头像上传、保存、排行展示 |
+| 管理端 CRUD | 列表、搜索、分页、新增、编辑、删除 |
+| 提现 | 申请、审核、状态流转、到账确认 |
+| 找伙伴 | match/config、ckb/join、ckb/lead |
+| @提及 | 阅读页高亮、点击添加好友 |
+
+---
+
+## 五、总结
+
+| 角色 | 功能完整性 | 发现问题 |
+|------|-----------|----------|
+| 小程序开发工程师 | ✅ 正常 | 1. read.js.backup 边界违规(可忽略) 2. app.json 格式建议 |
+| 管理端开发工程师 | ✅ 正常 | 无 |
+| 后端开发 | ✅ 正常 | /api/orders 鉴权待确认 |
+| 产品经理 | - | 需核对需求文档与实现一致性 |
+| 测试人员 | - | 建议制定合并后回归清单 |
+
+---
+
+*报告生成时间:2026-03-09 | 基于 yongxu 分支*
diff --git a/.cursor/meeting/2026-03-09_合并策略与执行清单.md b/.cursor/meeting/2026-03-09_合并策略与执行清单.md
new file mode 100644
index 0000000..3666cd5
--- /dev/null
+++ b/.cursor/meeting/2026-03-09_合并策略与执行清单.md
@@ -0,0 +1,73 @@
+# devlop + yongxu 合并策略 - 2026-03-09
+
+> 以 devlop 为基准,补入 yongxu 小程序侧缺失功能。管理端、后端以 devlop 为主(已领先)。
+
+---
+
+## 一、合并原则
+
+| 原则 | 说明 |
+|------|------|
+| 以 devlop 为基准 | 老板的改动(内容管理、FindPartner、神射手、RFM、dashboard-stats 等)全部保留 |
+| 补入 yongxu 独有 | 小程序侧 devlop 缺失的功能从 yongxu 合并 |
+| 保留 devlop 优化 | app.js 的推荐码自绑拦截、_normalizeReferralCode 等保留 |
+| 不恢复开发文档 | 开发文档已删除,暂不恢复(可按需从 yongxu 单独拷贝) |
+
+---
+
+## 二、小程序合并清单
+
+### 2.1 app.js ✅ 保留 devlop
+
+- devlop 已有:推荐码自绑拦截、_normalizeReferralCode
+- yongxu 无额外独有改动
+- **操作**:不修改
+
+### 2.2 read.js + read.wxml + read.wxss ⬅️ 补入 @提及
+
+| 项目 | 说明 |
+|------|------|
+| parseLineToSegments | 解析 `{{@userId:昵称}}` 为 segments |
+| contentSegments | 每行 `[{type:'text'\|'mention', text?, userId?, nickname?}]` |
+| onMentionTap | 点击 @ 触发确认弹窗 |
+| _doMentionAddFriend | 登录/资料校验 → POST ckb/lead |
+| read.wxml | 用 contentSegments 渲染,mention 可点击 |
+| read.wxss | 新增 `.mention` 样式 |
+
+**操作**:已执行合并(见下方实施记录)
+
+### 2.3 chapters.js、index.js、my.js
+
+| 文件 | devlop 状态 | yongxu 独有 | 建议 |
+|------|-------------|-------------|------|
+| chapters.js | 227 行差异 | 待核对 | 若 yongxu 有重要优化可手工对比 |
+| index.js | 31 行差异 | ckb/lead 等 | devlop 已有 ckb/lead,基本一致 |
+| my.js | 56 行差异 | 一键收款、dashboard-stats | devlop 已有一键收款 + dashboard-stats |
+
+**操作**:暂不合并,以 devlop 为准。若有具体问题再逐项对比。
+
+### 2.4 其他
+
+| 项目 | 建议 |
+|------|------|
+| read.js.backup | 删除或移出(含 /api/db/config 边界违规) |
+| app.json 拆行 | 可选,第 19 行多页面拆行便于维护 |
+
+---
+
+## 三、管理端、后端
+
+- **管理端**:devlop 已大幅领先,不合并 yongxu。详见 [2026-03-09_管理端与API合并分析.md](2026-03-09_管理端与API合并分析.md)
+- **后端**:devlop 已包含全部新 handler,不合并 yongxu。部署需配置 DB_DSN;CkbLeadRecord 迁移移除需与老板确认
+
+---
+
+## 四、实施记录
+
+- [x] 2026-03-09:合并 read.js 的 @提及(parseLineToSegments、contentSegments、onMentionTap、_doMentionAddFriend)
+- [x] 2026-03-09:合并 read.wxml 的 contentSegments 展示
+- [x] 2026-03-09:补充 read.wxss 的 .mention 样式
+
+---
+
+*策略制定:2026-03-09*
diff --git a/.cursor/meeting/2026-03-09_管理端与API合并分析.md b/.cursor/meeting/2026-03-09_管理端与API合并分析.md
new file mode 100644
index 0000000..b651018
--- /dev/null
+++ b/.cursor/meeting/2026-03-09_管理端与API合并分析.md
@@ -0,0 +1,166 @@
+# 管理端与 API 合并分析 - 2026-03-09
+
+> devlop vs yongxu 在 soul-admin、soul-api 的差异分析,边界合规性,合并建议。
+
+---
+
+## 一、管理端(soul-admin)
+
+### 1.1 差异概览
+
+| 方向 | 说明 |
+|------|------|
+| **devlop 新增** | FindPartnerPage、ChaptersPage、RichEditor、ContentPage 深度优化、Dashboard 概览、UserDetailModal 神射手、UsersPage RFM/用户规则/旅程统计 |
+| **yongxu** | 管理端无独有改动(yongxu 主要在小程序侧) |
+
+**结论**:管理端以 devlop 为准,无需从 yongxu 合并。
+
+### 1.2 devlop 新增/变更的页面与接口
+
+| 页面/组件 | 路由 | 主要接口 | 后端是否注册 |
+|-----------|------|----------|-------------|
+| FindPartnerPage | /find-partner | /api/db/match-records、ckb-plan-stats、match-pool-counts、ckb-leads、config/full?key=ckb_config | ✅ |
+| ChaptersPage | /chapters | /api/admin/chapters | ✅ |
+| ContentPage | /content | /api/db/book、persons、link-tags、config | ✅ |
+| DashboardPage | /dashboard | /api/admin/dashboard/overview | ✅ |
+| UserDetailModal | - | /api/admin/shensheshou/query、enrich、ingest | ✅ |
+| UsersPage | /users | /api/db/users/rfm、user-rules、users/journey-stats | ✅ |
+| RichEditor | 组件 | - | - |
+
+### 1.3 管理端 API 调用清单(devlop 当前)
+
+| 接口 | 使用页面 | 说明 |
+|------|----------|------|
+| /api/admin/dashboard/overview | DashboardPage | 数据概览 |
+| /api/admin/chapters | ChaptersPage | 章节树 CRUD |
+| /api/admin/shensheshou/* | UserDetailModal | 神射手查询/入库/enrich |
+| /api/admin/distribution/overview | DistributionPage | 分销概览 |
+| /api/admin/withdrawals | WithdrawalsPage、DistributionPage | 提现审核 |
+| /api/admin/orders/refund | OrdersPage、DistributionPage | 订单退款 |
+| /api/admin/referral-settings | ReferralSettingsPage | 推广设置 |
+| /api/admin/settings | SettingsPage | 系统设置 |
+| /api/admin/author-settings | AuthorSettingsPage | 作者设置 |
+| /api/admin/users | AdminUsersPage | 管理员用户 |
+| /api/admin/logout | AdminLayout | 登出 |
+| /api/db/users | 多页 | 用户 CRUD |
+| /api/db/users/rfm | UsersPage | RFM 估值 |
+| /api/db/users/referrals | UsersPage、UserDetailModal | 推荐关系 |
+| /api/db/users/journey-stats | UsersPage | 用户旅程统计 |
+| /api/db/user-rules | UsersPage | 用户规则 |
+| /api/db/book | ContentPage | 内容/章节 CRUD |
+| /api/db/persons | ContentPage | 人物管理 |
+| /api/db/link-tags | ContentPage | 链接标签 |
+| /api/db/config、config/full | 多页 | 配置 |
+| /api/db/match-records | FindPartner、MatchRecords | 匹配记录 |
+| /api/db/match-pool-counts | MatchPoolTab | 匹配池统计 |
+| /api/db/ckb-plan-stats | CKBStatsTab | CKB 计划统计 |
+| /api/db/ckb-leads | CKBConfigPanel | CKB 线索明细 |
+| /api/db/vip-roles | VipRolesPage、SetVipModal | VIP 角色 |
+| /api/db/mentors | MentorsPage | 导师 |
+| /api/db/mentor-consultations | MentorConsultationsPage | 导师预约 |
+| /api/orders | OrdersPage | 订单列表 |
+
+### 1.4 边界合规性 ✅
+
+- 仅调用 `/api/admin/*`、`/api/db/*`、`/api/orders`
+- 未调用 `/api/miniprogram/*`
+
+### 1.5 合并建议
+
+- **不合并**:管理端以 devlop 为准
+- **联调验证**:Dashboard、FindPartner、ContentPage、UsersPage(RFM、user-rules、journey-stats)与后端接口联调
+
+---
+
+## 二、API 后端(soul-api)
+
+### 2.1 差异概览
+
+| 项目 | yongxu | devlop |
+|------|--------|--------|
+| config | CkbLeadAPIKey、DB_DSN 默认本地、SyncOrdersInterval 默认 0 | 移除 CkbLeadAPIKey、DB_DSN 必填(Fatal)、SyncOrdersInterval 默认 5 |
+| database | 迁移 CkbSubmitRecord、CkbLeadRecord | 迁移 UserRule、Person、LinkTag、Chapter;移除 CkbSubmitRecord、CkbLeadRecord 迁移;seedDefaultRules |
+| handler | 基础 handler | 新增 admin_dashboard、admin_shensheshou、db_book 扩展、db_person、db_link_tag、db_user_rules、db_users_rfm、db_users_journey_stats、db_match_pool_counts、db_ckb_plan_stats、db_ckb_leads 等 |
+| router | 基础路由 | 新增上述路由 |
+
+### 2.2 devlop 配置变更(需关注)
+
+| 配置项 | yongxu | devlop | 影响 |
+|--------|--------|--------|------|
+| DB_DSN | 默认 `user:pass@tcp(127.0.0.1:3306)/soul?...` | 未配置则 Fatal 退出 | 部署需显式配置 DB_DSN |
+| CkbLeadAPIKey | 从环境变量读取 | 已移除 | ckb.go 使用硬编码 ckbAPIKey,不影响运行 |
+| SyncOrdersIntervalMinutes | 默认 0 | 默认 5 | 订单对账定时任务每 5 分钟执行 |
+
+### 2.3 devlop 数据库迁移变更
+
+| 项目 | yongxu | devlop |
+|------|--------|--------|
+| CkbSubmitRecord | 迁移 | 移除迁移 |
+| CkbLeadRecord | 迁移 | 移除迁移 |
+| UserRule | - | 新增迁移 + seedDefaultRules |
+| Person | - | 新增迁移 |
+| LinkTag | - | 新增迁移 |
+| Chapter | - | 新增迁移(hot_score_override) |
+
+**说明**:CkbLeadRecord、CkbSubmitRecord 迁移被移除,但 model 与 handler 仍存在。DBCKBLeadList、CKBLead 可能依赖存客宝 API 或本地表——若本地表仍在使用,需确认迁移策略。
+
+### 2.4 路由分组合规性 ✅
+
+| 路由组 | 前缀 | 使用方 | 状态 |
+|--------|------|--------|------|
+| miniprogram | /api/miniprogram/* | 小程序 | ✅ |
+| admin | /api/admin/* | 管理端 | ✅ |
+| db | /api/db/* | 管理端 | ✅ |
+
+### 2.5 待确认项
+
+| 项目 | 说明 |
+|------|------|
+| /api/orders 鉴权 | 仍在 api 根下,未经过 AdminAuth |
+| CkbLeadRecord 表 | 迁移已移除,若 DBCKBLeadList 或 CKBLead 仍写本地表,需恢复迁移或改实现 |
+| router.go 格式 | 第 51 行 admin.GET("/distribution/overview") 缩进异常,建议修正 |
+
+### 2.6 合并建议
+
+- **以 devlop 为准**:API 侧 devlop 已大幅领先
+- **yongxu 无独有 API 改动**:无需从 yongxu 合并
+- **部署注意**:devlop 要求 DB_DSN 必填,需在 .env 中配置
+- **可选恢复**:若 CKB 线索需落本地表,可考虑恢复 CkbLeadRecord 迁移(与老板确认)
+
+---
+
+## 三、管理端 ↔ API 接口对应检查
+
+| 管理端调用 | soul-api 路由 | 状态 |
+|------------|---------------|------|
+| /api/admin/dashboard/overview | admin.GET("/dashboard/overview") | ✅ |
+| /api/admin/chapters | admin.GET/POST/PUT/DELETE("/chapters") | ✅ |
+| /api/admin/shensheshou/query | admin.GET("/shensheshou/query") | ✅ |
+| /api/admin/shensheshou/enrich | admin.POST("/shensheshou/enrich") | ✅ |
+| /api/admin/shensheshou/ingest | admin.POST("/shensheshou/ingest") | ✅ |
+| /api/db/ckb-leads | db.GET("/ckb-leads") | ✅ |
+| /api/db/ckb-plan-stats | db.GET("/ckb-plan-stats") | ✅ |
+| /api/db/match-pool-counts | db.GET("/match-pool-counts") | ✅ |
+| /api/db/users/rfm | db.GET("/users/rfm") | ✅ |
+| /api/db/users/journey-stats | db.GET("/users/journey-stats") | ✅ |
+| /api/db/user-rules | db.GET/POST/PUT/DELETE("/user-rules") | ✅ |
+| /api/db/persons | db.GET/POST/DELETE("/persons") | ✅ |
+| /api/db/link-tags | db.GET/POST/DELETE("/link-tags") | ✅ |
+| /api/db/book?action=section-orders | db.GET("/book") | ✅ |
+| 其他 | 见 router.go | ✅ |
+
+**结论**:管理端调用的接口均在 soul-api 中注册,无 404 风险。
+
+---
+
+## 四、总结
+
+| 端 | 合并策略 | 备注 |
+|----|----------|------|
+| 管理端 | 以 devlop 为准,不合并 | yongxu 无管理端独有改动 |
+| API | 以 devlop 为准,不合并 | 部署需配置 DB_DSN;CkbLeadRecord 迁移移除需确认 |
+| 小程序 | 已补入 @提及 | 见 2026-03-09_合并策略与执行清单.md |
+
+---
+
+*分析完成时间:2026-03-09*
diff --git a/.cursor/meeting/2026-03-10_Toast通知系统全局落地.md b/.cursor/meeting/2026-03-10_Toast通知系统全局落地.md
new file mode 100644
index 0000000..6089db4
--- /dev/null
+++ b/.cursor/meeting/2026-03-10_Toast通知系统全局落地.md
@@ -0,0 +1,103 @@
+# 会议纪要 - 2026-03-10 | Toast 通知系统全局落地 & hot_score 数据库迁移
+
+> 本文件由**助理橙子**在会议结束后自动生成。
+
+---
+
+## 基本信息
+
+- **时间**:2026-03-10
+- **议题**:1) 文章保存后添加成功提示;2) 数据库缺失 hot_score 字段报错修复;3) 全系统 alert 替换为 toast 组件
+- **触发方式**:用户反馈 → 逐步扩展到全系统改造
+- **参与角色**:管理端开发工程师、后端开发、助理橙子
+
+---
+
+## 各角色发言
+
+### 【管理端开发工程师】
+
+当前管理端所有操作反馈都使用原生 `alert()`,体验差,阻断操作流程,用户必须手动点 OK 才能继续。需要一个统一的 toast 通知系统:非阻塞、自动消失、视觉语义化(绿色=成功、红色=错误、蓝色=信息)。
+
+考虑到项目没有现成 toast 依赖(sonner 安装失败),选择纯原生 DOM 实现 `src/utils/toast.ts`,无需第三方库,全量兼容现有项目。
+
+### 【后端开发】
+
+保存文章时报 `Error 1054 (42S22): Unknown column 'hot_score' in 'field list'`,原因是前端 `ContentPage.tsx` 已在 `handleSaveSection` 中传递 `hotScore` 字段,但后端 `Chapter` model 缺少该字段,且数据库 `chapters` 表也未建列。
+
+需要两步修复:① ALTER TABLE 新增列;② 同步 model struct。
+
+### 【助理橙子】
+
+全系统 18 个文件约 90 处 alert 经 PowerShell 脚本批量替换。替换规则:
+- 含"失败/错误/请填/不一致/必填"→ `toast.error()`
+- 含"成功/已保存/已删除/已创建"→ `toast.success()`
+- 其余→ `toast.info()`
+
+事后人工复查,修正 5 处语义误判(info 改 error)。
+
+---
+
+## 讨论过程
+
+1. 用户提出"文章保存了要提示保存成功"
+2. 管理端工程师创建 `src/utils/toast.ts`(纯原生,无依赖),替换 `ContentPage.tsx` 的 alert
+3. 用户截图报错 `Unknown column 'hot_score'`
+4. 后端工程师执行 `ALTER TABLE chapters ADD COLUMN hot_score INT NOT NULL DEFAULT 0`,更新 `chapter.go` model
+5. 用户提出"整个系统的 alert 都可以改为 toast"
+6. 助理橙子编写 PowerShell 批量替换脚本,处理 18 个文件
+7. 人工复查 `toast.info()` 调用,将 5 处验证提示修正为 `toast.error()`
+
+---
+
+## 会议决议
+
+1. **Toast 系统统一规范**:管理端所有用户反馈统一使用 `@/utils/toast`,禁止使用 `alert()`
+2. **toast 类型语义**:
+ - `toast.success()` → 操作成功、保存成功、删除成功
+ - `toast.error()` → 操作失败、表单验证不通过、接口报错
+ - `toast.info()` → 中性提示(无数据、当前状态说明)
+3. **hot_score 字段**:`chapters` 表已新增,`Chapter` model 已同步,热度分功能可正常保存
+4. **数据库变更流程**:model 字段与 DB 列必须同步维护,ALTER TABLE 后立即更新 model struct
+
+---
+
+## 待办事项
+
+| 责任角色 | 任务 | 优先级 | 截止建议 |
+|---------|------|--------|---------|
+| 管理端开发工程师 | 新增页面/组件时使用 toast 而非 alert(已有规范) | 高 | 持续 |
+| 后端开发 | hot_score 排名算法接口(若有)按此字段排序 | 低 | 待需求 |
+
+---
+
+## 问题与作答区
+
+| # | 问题 | 责任角色 | 作答 |
+|---|------|---------|------|
+| 1 | hot_score 热度分的计算逻辑和更新时机是什么? | 产品经理/后端开发 | (待补充) |
+| 2 | toast 是否需要支持持久化(不自动消失)的场景? | 管理端开发工程师 | (待补充) |
+
+---
+
+## 各角色经验与业务理解更新
+
+### 后端开发
+
+- `chapters` 表新增 `hot_score INT NOT NULL DEFAULT 0`,用于热度排名算法
+- model struct 字段须与 DB 列同步,否则 GORM 写入报 1054 错误
+
+### 管理端开发工程师
+
+- 创建 `src/utils/toast.ts`:纯原生 DOM toast,无第三方依赖,支持 success/error/info
+- 全系统 18 个文件约 90 处 `alert()` 已替换;替换按语义分类,info 类需人工复查
+- 新规范:管理端禁用 `alert()`,统一使用 `toast.*`
+
+### 团队共享
+
+- **Toast 替换脚本方法论**:用 PowerShell 正则 + 语义关键词批量替换,替换后人工复查 `toast.info()` 是否应为 `toast.error()`(验证提示类常被误判)
+- **DB 变更 SOP**:前端传新字段 → 后端先执行 ALTER TABLE → 再更新 model → 重启服务
+
+---
+
+*会议纪要由助理橙子生成 | 各角色经验已同步至 `agent/{角色}/evolution/2026-03-10.md`*
diff --git a/.cursor/meeting/2026-03-10_小程序新旧版对比与dashboard接口新增.md b/.cursor/meeting/2026-03-10_小程序新旧版对比与dashboard接口新增.md
new file mode 100644
index 0000000..0a6b1b4
--- /dev/null
+++ b/.cursor/meeting/2026-03-10_小程序新旧版对比与dashboard接口新增.md
@@ -0,0 +1,108 @@
+# 会议纪要 - 2026-03-10 | 小程序新旧版对比分析 & dashboard-stats 接口新增
+
+## 基本信息
+
+- **时间**:2026-03-10
+- **议题**:Mycontent-temp vs miniprogram 小程序新旧版功能/样式对比;loadDashboardStats 移植;后端新增聚合接口
+- **参与角色**:小程序开发工程师、后端工程师、团队(乘风)
+
+---
+
+## 讨论过程
+
+### 一、新旧版小程序对比分析
+
+用户指出 `Mycontent-temp/miniprogram` 为新版(仅供预览),`miniprogram` 为旧版(线上正确版本)。
+
+**功能差异**(旧版反而功能更完整):
+
+| 功能 | 新版 (Mycontent-temp) | 旧版 (miniprogram) |
+|-----|------|------|
+| 首页「我的阅读」进度卡 | ❌ 缺失 | ✅ 有(已读/待读/篇章/章节四统计) |
+| 首页章数徽章/Banner篇章名 | ❌ 缺失 | ✅ 有 |
+| 最新新增日期/描述 | ❌ 缺失 | ✅ 有 |
+| 目录页 VIP 权限 | ❌ 无 isVip | ✅ 支持 isVip + isPremium 增值章节 |
+| VIP 全局状态管理 | ❌ 不写 globalData | ✅ isVip/vipExpireDate 同步到 globalData + Storage |
+| 阅读页 contentParagraphs fallback | ❌ 无 | ✅ 有降级渲染 |
+| my.js 阅读统计 | ✅ loadDashboardStats(后端接口)| ❌ 本地缓存占位(随机时间/标题占位) |
+
+**样式差异**(仅 2 处,全局 app.wxss 完全相同):
+1. `chapters.wxss`:旧版多 `.tag-vip`(金色增值标签样式)
+2. `read.wxss`:旧版 `.paragraph .mention` 多 `padding: 0 4rpx`
+
+**结论**:新版的亮点仅为 `loadDashboardStats` 后端接口调用,其余功能旧版更完整。
+
+### 二、my.js loadDashboardStats 移植
+
+将 Mycontent-temp 中的 `loadDashboardStats()` 移植到旧版 `miniprogram/pages/my/my.js`:
+
+**改动:**
+1. `initUserStatus()` 改造:去掉本地缓存占位(随机时间、标题 `章节 ${id}`),初始化清零后调 `loadDashboardStats()`
+2. 新增 `loadDashboardStats()` 方法:调用 `/api/miniprogram/user/dashboard-stats?userId=xxx`,同步 `readSectionIds` 到 globalData 和 Storage,获取真实 `recentChapters`、`readCount`、`totalReadMinutes`、`matchHistory`
+
+### 三、后端 dashboard-stats 接口新增
+
+在 `soul-api/internal/handler/user.go` 新增 `UserDashboardStats`,路由注册:`GET /api/miniprogram/user/dashboard-stats`。
+
+参考 Mycontent-temp 实现并优化了 3 处 bug:
+1. **去重**:`seenRecent` map 防止同一章节重复出现在「最近阅读」
+2. **最小 1 分钟**:阅读不足 60 秒时显示 1 分钟而非 0
+3. **错误状态码**:DB 失败返回 500 而非 200+`success:false`
+
+编译通过,数据来源:
+- `readSectionIds`/`readCount` → `reading_progress` 表
+- `totalReadMinutes` → `duration` ÷ 60(秒转分)
+- `recentChapters` → `reading_progress` JOIN `chapters`(最近 5 条去重)
+- `matchHistory` → `match_records` 计数
+
+### 四、富文本渲染现状分析(未实施,待办)
+
+两版均为纯文本渲染,TipTap HTML 格式(粗体、标题、列表、引用等)被 `contentParser.js` 全部剥除。
+
+建议方案:用 `` 组件渲染 HTML,@mention 替换为带颜色 span,通过外层 bindtap + dataset 实现点击。**本次未实施。**
+
+---
+
+## 会议决议
+
+1. ✅ **旧版(miniprogram)为线上正确版本**,新版仅供样式预览,不反向同步功能
+2. ✅ **loadDashboardStats 已移植**到旧版 my.js,阅读统计改为后端真实数据
+3. ✅ **后端 dashboard-stats 接口已实现并编译通过**,可直接上线使用
+4. ⬜ **富文本渲染**待后续迭代:用 `` 替换当前纯文本渲染
+
+---
+
+## 待办事项
+
+| 责任角色 | 任务 | 状态 |
+|---------|------|------|
+| 小程序开发工程师 | 富文本渲染升级(rich-text 组件 + mention 处理) | 待实施 |
+| 后端工程师 | dashboard-stats 接口上线验证 | 待验证 |
+| 小程序开发工程师 | 首页阅读进度卡确认是否需要(新版没有,旧版有) | 待确认 |
+
+---
+
+## 问题与作答区
+
+| 编号 | 问题 | 作答 |
+|------|------|------|
+| Q1 | 内容在 DB 中是纯文本还是 TipTap HTML?富文本渲染是否紧迫? | (待确认) |
+| Q2 | 首页「我的阅读」进度卡是否保留?(新版已去掉) | (待确认) |
+| Q3 | dashboard-stats 接口是否需要加缓存(高频调用场景)? | (待确认) |
+
+---
+
+## 各角色经验与业务理解更新
+
+### 小程序开发工程师
+- `my.js` 阅读统计来源改为后端接口,不再用本地缓存随机占位
+- 富文本渲染为当前技术债,`contentParser.js` 仅剥 HTML 无格式保留
+- 新旧版对比方法论:批量 WXSS/JS 文件 diff 精确定位差异
+
+### 后端工程师
+- 新增聚合接口时,优先参考已有实现版本,对比后修复 bug(去重、min值、状态码)
+- `dashboard-stats` 数据模型:reading_progress JOIN chapters + match_records count
+
+### 团队
+- Mycontent-temp 是预览分支,功能不及主线,不作为迁移基准
+- 新旧版并存时,以功能更完整的主线版本为准,按需吸收新版的接口优化
diff --git a/.cursor/meeting/2026-03-10_文章详情三端功能对齐与开发.md b/.cursor/meeting/2026-03-10_文章详情三端功能对齐与开发.md
new file mode 100644
index 0000000..b438919
--- /dev/null
+++ b/.cursor/meeting/2026-03-10_文章详情三端功能对齐与开发.md
@@ -0,0 +1,82 @@
+# 会议纪要 - 2026-03-10 | 文章详情三端功能对齐与开发
+
+## 基本信息
+- **时间**:2026-03-10
+- **议题**:文章详情 @某人/@linkTag/图片 三端功能对齐,发现 Bug,完成开发
+- **参与角色**:产品经理、后端开发、管理端开发工程师、小程序开发工程师
+
+---
+
+## 现状摸底(开发前)
+
+| 功能 | 后端 | 管理端 | 小程序 | 状态 |
+|---|---|---|---|---|
+| @mention 存储格式 | content 字段存 TipTap HTML | RichEditor 插入 `` | contentParser 解析 ✅ | ✅ 正常 |
+| @mention 点击加好友 | CKBLead 接口 **只用全局 Key** | — | 已传 targetUserId,后端接不住 | ❌ 缺密钥路由 |
+| #linkTag 存储格式 | content 字段存 `` | insertLinkTag 插入标准 `` | **未解析,直接被剥离** | ❌ 不可点 |
+| 图片展示 | 存 `
` | 编辑器支持上传插图 | **未解析,被剥离** | ❌ 不显示 |
+| loadContent 重复定义 | — | — | 两个同名函数,旧版覆盖新版 | ❌ Bug |
+
+---
+
+## 本次完成的开发
+
+### 后端(soul-api)
+1. **`model/person.go`** 加 `CkbApiKey` 字段(VARCHAR 100)
+2. **`handler/db_person.go`** `DBPersonSave` 接收并存储 `ckbApiKey`
+3. **`model/ckb_lead.go`** 加 `TargetPersonID`、`Source` 字段落库
+4. **`handler/ckb.go` `CKBLead`** 接收 `targetUserId`/`targetNickname`/`source`,查 `persons.ckb_api_key`,有则用,无则 fallback 全局 Key;成功文案动态化("提交成功,XXX 会尽快联系您")
+5. **`scripts/add-persons-ckb-api-key.sql`** 手动迁移备用脚本
+
+### 管理端(soul-admin)
+1. **`components/RichEditor.tsx`** `PersonItem` 接口加 `ckbApiKey?: string`
+2. **`pages/content/ContentPage.tsx`**
+ - `loadPersons` 映射 `ckbApiKey`
+ - `newPerson` state 加 `ckbApiKey`
+ - Person 配置卡片加「存客宝密钥」输入框(新建时可填)
+ - Person 列表每行加铅笔编辑按钮,展开内联输入框可直接修改密钥
+ - 密钥状态 badge:有密钥显示绿色 `密钥 ✓`,无则灰色 `用默认密钥`
+
+### 小程序(miniprogram)
+1. **`utils/contentParser.js`** 全面重写:
+ - 新增 `parseBlockToSegments`:统一处理 mention / linkTag(span) / linkTag(a) / image 四种内联元素
+ - 新增解码工具函数 `decodeEntities`
+ - 纯图片行独立成段,不与文字混排
+2. **`pages/read/read.js`**
+ - 删除重复的旧版 `loadContent`(Bug:覆盖新版)
+ - 新版 `loadContent` 补全:成功后写本地缓存,缓存降级时恢复 partTitle/chapterTitle
+ - 新增 `onLinkTagTap`:内页路径直接 `navigateTo`,外链复制到剪贴板并提示
+ - 新增 `onImageTap`:点击图片全屏预览(`wx.previewImage`)
+3. **`pages/read/read.wxml`** 段落块新增渲染分支:
+ - `linkTag` → `#{{label}}`
+ - `image` → ``
+4. **`pages/read/read.wxss`** 新增 `.link-tag`(金色 #FFD700)、`.content-image`(宽度铺满)样式
+
+---
+
+## 完整加好友链路
+
+```
+管理端:Person 配置填写存客宝密钥(ckbApiKey)
+ ↓
+文章写入 content → "@[卡若](karuo)" / "#[标签名](url)" /
+ ↓
+小程序 contentParser 解析 → segments
+ ↓ 点击 @卡若
+POST /api/miniprogram/ckb/lead
+ { targetUserId: "karuo", targetNickname: "卡若", source: "article_mention" }
+ ↓
+后端查 persons WHERE person_id='karuo' → 取 ckb_api_key
+ → 有:用该人专属密钥推存客宝
+ → 无:fallback 全局 CKB_LEAD_API_KEY
+ ↓
+落库 ckb_lead_records(target_person_id, source)
+```
+
+---
+
+## 待办
+| 角色 | 任务 |
+|---|---|
+| 产品经理 | 确认 #linkTag 外链的交互体验(复制 vs 打开 webview)是否符合预期 |
+| 测试人员 | 联调:@某人点击 → 存客宝各渠道收线索;#标签点击 → 复制/跳转;图片点击 → 全屏预览 |
diff --git a/.cursor/meeting/2026-03-10_管理端迁移Mycontent-temp菜单布局讨论.md b/.cursor/meeting/2026-03-10_管理端迁移Mycontent-temp菜单布局讨论.md
new file mode 100644
index 0000000..8313fcf
--- /dev/null
+++ b/.cursor/meeting/2026-03-10_管理端迁移Mycontent-temp菜单布局讨论.md
@@ -0,0 +1,131 @@
+# 会议纪要 - 2026-03-10 | 管理端迁移 Mycontent-temp 菜单/布局讨论
+
+> 本文件由**助理橙子**在会议结束后自动生成。
+
+---
+
+## 基本信息
+
+- **时间**:2026-03-10 15:10
+- **议题**:管理端改为使用 `Mycontent-temp/soul-admin` 这套“新菜单 + 新布局”规范;基于现有 `soul-admin` 功能,明确菜单/布局改造方式与功能适配点
+- **触发方式**:开个会议研究下
+- **参与角色**:产品经理、后端开发、管理端开发工程师、小程序开发工程师、测试人员
+
+---
+
+## 各角色发言
+
+### 【产品经理】
+
+- **目标**:后台的导航与信息架构要“更运营化”:核心入口更少、更聚焦;次要功能不消失但不占主导航。
+- **新规范**(以 `Mycontent-temp/soul-admin/src/layouts/AdminLayout.tsx` 为准):
+ - 侧栏主菜单平铺 5 个:**数据概览 / 内容管理 / 用户管理 / 找伙伴 / 推广中心**
+ - **系统设置**固定在侧栏下方
+ - 原「更多」折叠的部分要么隐藏入口(从概览/页面内跳转进入),要么并入系统设置 Tab
+- **验收**:菜单一致;旧功能可达;用户操作路径更短(尤其内容/找伙伴/推广)。
+
+### 【后端开发】
+
+- 管理端迁移/重构不改变接口边界:只允许 `/api/admin/*`、`/api/db/*`、`/api/orders`。
+- 路由与菜单调整不需要新增后端接口;若概览页需要聚合接口(如 `/api/admin/dashboard/overview`)可作为“优化项”,同时保留降级方案(用现有 users/orders 拼)。
+- 需要注意“路由别名/跳转”不应影响鉴权:`GET /api/admin` 校验逻辑保持不变。
+
+### 【管理端开发工程师】
+
+- 新工程的关键差异点:
+ - `AdminLayout`:取消「更多」折叠,主菜单平铺;`/settings` 永远在底部。
+ - 路由:保留历史页面路由,但**不一定在菜单出现**;`/author-settings`、`/admin-users` 变为 `Navigate` 到 `/settings?tab=author|admin`(页面承载搬到 Settings Tab)。
+- 迁移策略建议:
+ - **以 `Mycontent-temp/soul-admin` 为“样板/目标态”**,把现有 `soul-admin` 中已实现的页面与功能对齐过去(或反向:把目标态布局/菜单 port 回现有项目)。
+ - 保持路由路径尽量不变(避免大量链接/收藏失效),通过菜单“入口收敛”达成产品目标。
+
+### 【小程序开发工程师】
+
+- 小程序侧只关心“内容编辑产物能否稳定下发/解析”,管理端菜单迁移不应改变内容接口或字段。
+- 若管理端页面拆分导致内容结构改动(例如富文本 HTML、mention/tag 的数据结构),必须提前同步小程序解析策略并回归阅读页兼容。
+
+### 【测试人员】
+
+- 重点回归点:
+ - **路由可达性**:菜单入口虽减少,但旧页面必须仍能通过路由访问(尤其订单/提现/推广设置/导师等)。
+ - **鉴权**:任意页面刷新后均能正确校验 token 并跳转登录(`GET /api/admin`)。
+ - **信息架构一致**:侧栏 5 项 + 系统设置固定位置;`author/admin` 设置从 `/settings` 的 tab 进入。
+
+---
+
+## 讨论过程
+
+- 对照了两套工程:
+ - 旧 `soul-admin`:主菜单 3 项 + 「更多」折叠(VIP角色/作者详情/管理员/导师/导师预约/推广中心/找伙伴/匹配记录/推广设置)
+ - 新 `Mycontent-temp/soul-admin`:主菜单 5 项平铺(概览/内容/用户/找伙伴/推广),系统设置固定;作者/管理员并入 Settings Tabs;其余页面保持路由但不占侧栏入口
+- 达成一致:**以新工程布局/菜单为准**,旧功能以“路由可达 + 概览/页面内跳转”方式保留。
+
+---
+
+## 会议决议
+
+1. **目标态以 `Mycontent-temp/soul-admin` 为准**:菜单与布局“照新不照旧”,旧工程如需改造则对齐该实现。
+2. **侧栏信息架构**:
+ - 主菜单固定 5 项:数据概览、内容管理、用户管理、找伙伴、推广中心
+ - 系统设置固定在侧栏底部
+ - 取消「更多」折叠入口
+3. **功能入口收敛规则**:
+ - `author-settings`、`admin-users` 不再作为独立菜单项,统一并入 `/settings?tab=author|admin`
+ - 订单/提现/推广设置/VIP角色/导师等页面:**保留路由**,但入口不进入侧栏主菜单(可由概览卡片、页面内按钮或系统设置进入)
+4. **接口与边界不变**:管理端继续只调用 `/api/admin/*`、`/api/db/*`、`/api/orders`,不得引入 `/api/miniprogram/*`。
+5. **待确认项**:
+ - 哪些“非主菜单页面”需要在概览页提供快捷入口(订单、提现、推广设置等)的优先级排序。
+
+---
+
+## 待办事项
+
+| 责任角色 | 任务 | 优先级 | 截止建议 |
+|---------|------|--------|---------|
+| 管理端开发工程师 | 基于 `Mycontent-temp/soul-admin` 梳理:侧栏主菜单 5 项、Settings Tab 承载 author/admin、其余页面入口方案(概览卡片/页面内跳转) | 高 | 2026-03-11 |
+| 产品经理 | 给出“非主菜单页面”的入口优先级(概览要露出哪些快捷卡片/按钮) | 中 | 2026-03-11 |
+| 后端开发 | 确认概览聚合接口 `/api/admin/dashboard/overview` 是否作为正式接口上线;若不上线,确认降级方案字段口径 | 中 | 2026-03-12 |
+| 测试人员 | 输出菜单/路由/鉴权回归清单(含隐藏路由可达性) | 中 | 2026-03-12 |
+| 小程序开发工程师 | 关注内容编辑产物格式是否变化;若变更,补充阅读页兼容用例 | 低 | 持续 |
+
+---
+
+## 问题与作答区
+
+| # | 问题 | 责任角色 | 作答 |
+|---|------|---------|------|
+| 1 | 非主菜单页面(订单/提现/推广设置/VIP角色/导师等)哪些必须在概览页提供快捷入口? | 产品经理 | (待补充) |
+| 2 | `Mycontent-temp/soul-admin` 是否作为线上唯一管理端工程(替换旧 `soul-admin`),还是旧工程按新规范改造? | 团队 | (待补充) |
+
+---
+
+## 各角色经验与业务理解更新
+
+### 产品经理
+
+- 菜单信息架构收敛:主导航只保留运营主链路入口,次要功能“可达但不抢入口”。
+
+### 后端开发
+
+- 概览聚合接口可以作为优化项,但必须保留降级(users+orders)确保不阻塞前端迁移。
+
+### 管理端开发工程师
+
+- 迁移以 `Mycontent-temp/soul-admin` 为目标态;作者/管理员并入 Settings Tab;取消“更多”折叠。
+
+### 小程序开发工程师
+
+- 管理端迁移不应影响小程序接口边界;若内容格式变更需及时同步解析策略并回归。
+
+### 测试人员
+
+- 菜单减少不等于功能减少:必须覆盖“隐藏路由可达性 + 鉴权跳转 + 新侧栏一致性”。
+
+### 团队共享
+
+- 统一以 `Mycontent-temp/soul-admin` 的 `AdminLayout`/`SettingsPage` 为“新规范基线”,后续所有菜单/布局调整按该基线执行,避免两套后台并行发散。
+
+---
+
+*会议纪要由助理橙子生成 | 各角色经验已同步至 `agent/{角色}/evolution/2026-03-10.md`*
+
diff --git a/.cursor/meeting/2026-03-11_开发团队对齐业务逻辑与以界面定需求会议收尾.md b/.cursor/meeting/2026-03-11_开发团队对齐业务逻辑与以界面定需求会议收尾.md
new file mode 100644
index 0000000..265db5f
--- /dev/null
+++ b/.cursor/meeting/2026-03-11_开发团队对齐业务逻辑与以界面定需求会议收尾.md
@@ -0,0 +1,83 @@
+# 会议纪要 - 2026-03-11 | 开发团队对齐业务逻辑与以界面定需求·会议收尾
+
+> 本文件由**助理橙子**在会议结束后自动生成。
+
+---
+
+## 基本信息
+
+- **时间**:2026-03-11
+- **议题**:开发团队对齐业务逻辑,以界面定需求,并更新开发文档与需求文档;用户提出「结束会议」后执行收尾。
+- **触发方式**:结束会议
+- **参与角色**:产品经理、后端开发、管理端开发工程师、小程序开发工程师、团队(跨角色)
+
+---
+
+## 各角色发言
+
+> 本次为收尾整理,无逐角色发言环节;结论来自本会话已完成的文档与代码变更。
+
+### 【产品经理】
+
+需求基准明确为《以界面定需求》;需求汇总以该文档为准,新增/变更功能先对齐界面再落需求清单。
+
+### 【后端开发】
+
+users 表迁移仅保留 VIP 身份/状态字段(is_vip、vip_expire_date、vip_activated_at、vip_sort、vip_role),不再新增 vip_name/vip_avatar 等资料列;chapters 表补 hot_score;sync-users-vip-and-schema.sql 与 README-schema-sync 已更新。
+
+### 【管理端开发工程师】
+
+管理端界面清单已纳入《以界面定需求》,路由与主要接口与 App.tsx/AdminLayout 一致,作为验收基准。
+
+### 【小程序开发工程师】
+
+小程序界面清单已纳入《以界面定需求》,页面与主要 /api/miniprogram/* 接口一致;展示以用户资料为准,不再依赖单独 VIP 资料列。
+
+### 【团队共享】
+
+以界面定需求、三端路由隔离、用户/VIP 展示以用户资料为准,已写入《以界面定需求》及运营与变更第九部分。
+
+---
+
+## 讨论过程
+
+(本次为收尾流程,无额外讨论节点。)
+
+---
+
+## 会议决议
+
+1. **以界面定需求**:需求基准文档已建立,小程序与管理端界面清单、主要接口、业务逻辑对齐(三端路由、VIP 资料以用户资料为准等)已落档。
+2. **开发文档联动**:README 增加《以界面定需求》链接;需求汇总增加「需求基准(必读)」;运营与变更增加第九部分记录本次对齐。
+3. **数据库迁移**:users 仅同步 VIP 身份/状态五字段;chapters 同步 hot_score;不再新增 VIP 资料列。
+4. **待确认项**:无。
+
+---
+
+## 待办事项
+
+| 责任角色 | 任务 | 优先级 | 截止建议 |
+|---------|------|--------|---------|
+| — | 无新增待办 | — | — |
+
+---
+
+## 问题与作答区
+
+| # | 问题 | 责任角色 | 作答 |
+|---|------|---------|------|
+| — | 无待确认问题 | — | — |
+
+---
+
+## 各角色经验与业务理解更新
+
+- **产品经理**:需求以《以界面定需求》为准,验收与需求清单与之保持一致。
+- **后端开发**:users 表迁移仅补 VIP 身份/状态字段;chapters 补 hot_score;README-schema-sync 已区分「身份字段」与「不再新增的资料列」。
+- **管理端开发工程师**:管理端界面清单与路由/接口已作为需求与验收基准写入《以界面定需求》。
+- **小程序开发工程师**:小程序界面清单与接口已作为需求与验收基准;展示优先用户资料。
+- **团队共享**:以界面定需求、三端路由隔离、用户/VIP 资料展示规则已沉淀至《以界面定需求》与运营与变更。
+
+---
+
+*会议纪要由助理橙子生成 | 各角色经验已同步至 `agent/{角色}/evolution/2026-03-11.md`*
diff --git a/.cursor/meeting/2026-03-16_new-soul新需求与当前项目差异分析.md b/.cursor/meeting/2026-03-16_new-soul新需求与当前项目差异分析.md
new file mode 100644
index 0000000..0c10c9b
--- /dev/null
+++ b/.cursor/meeting/2026-03-16_new-soul新需求与当前项目差异分析.md
@@ -0,0 +1,123 @@
+# 会议纪要 - 2026-03-16 | new-soul 新需求与当前项目差异分析
+
+> 本文件由**助理橙子**在会议结束后自动生成。
+
+---
+
+## 基本信息
+
+- **时间**:2026-03-16
+- **议题**:new-soul 新需求分析与当前项目 Mycontent 的差异
+- **触发方式**:开会,所有人都参加,@new-soul 这是新需求
+- **参与角色**:产品经理、后端开发、管理端开发工程师、小程序开发工程师、测试人员
+
+---
+
+## 各角色发言
+
+### 【产品经理】
+
+**定位差异**:
+- **new-soul 派对AI**:内容运营侧 AI 助手,服务于《一场soul的创业实验》的派对→录屏→剪辑→成片→分发→文章→小程序全链路。
+- **当前 Mycontent**:产品侧,面向创业者的社区/工具型小程序,核心是内容→会员→导师变现、存客宝对接、分销等。
+
+**业务范围**:
+- 派对AI 覆盖:运营报表、飞书管理、文章写作、视频剪辑、素材库上传、多平台分发、Soul 账号注册、小程序站管理(9 技能 5 组)。
+- 当前项目:三端代码 + 存客宝、链接人与事、VIP、分销等业务功能。
+
+**结论**:两者是同一业务的不同层面(运营 vs 产品),非替代关系。
+
+### 【后端开发】
+
+**技术差异**:
+- 派对AI 依赖 `content_upload.py` 直连腾讯云 MySQL 写入 `soul_miniprogram.chapters`;路径为 Mac(`/Users/karuo/...`)。
+- 当前项目:soul-api(Go/Gin/GORM),通过 `/api/miniprogram/*`、`/api/admin/*`、`/api/db/*` 提供接口。
+
+**重叠点**:小程序站管理技能引用的 soul-api、soul-admin、miniprogram 与当前三端一致;数据库同为 `soul_miniprogram`。
+
+**风险**:`content_upload.py` 直连 DB 与 soul-api 并存,存在双写风险;路径为 Mac,当前环境为 Windows,需统一或适配。
+
+### 【管理端开发工程师】
+
+派对AI 未单独定义管理端功能,仅通过「小程序站管理」引用 soul-admin。当前项目管理端(链接人与事、存客宝、VIP、推广中心等)为完整实现。**结论**:派对AI 不新增管理端需求。
+
+### 【小程序开发工程师】
+
+派对AI 的小程序站管理覆盖上传、部署、版本管理,与当前 miniprogram 一致。当前项目 C 端功能(文章阅读、@某人、存客宝留资、VIP、分销等)已实现。**结论**:派对AI 不改变小程序功能,仅涉及发布流程。
+
+### 【测试人员】
+
+1. **环境**:派对AI 为 Mac 路径,当前为 Windows,需确认是否同一代码库不同环境。
+2. **数据流**:`content_upload.py` 直写 DB vs soul-api 接口,需避免冲突。
+3. **回归**:若引入派对AI 流程,需回归文章上传、飞书推送、小程序展示。
+
+---
+
+## 讨论过程
+
+- **产品经理 → 后端**:`content_upload.py` 是否应逐步迁移到 soul-api 接口?
+- **后端**:短期可保留直连,中长期规划为通过 soul-api admin/db 接口写入,统一数据入口。
+- **小程序 → 产品**:需确认 chapters 表结构与 `content_upload.py` 写入格式一致。
+- **后端**:需核对 `content_upload.py` 字段与 chapters 表、soul-api 模型一致性。
+
+---
+
+## 会议决议
+
+1. **定位**:new-soul 派对AI 为运营侧 AI 助手,当前 Mycontent 为产品侧三端项目,二者互补,非替代。
+2. **三端**:小程序站管理技能与 soul-api、soul-admin、miniprogram 一致,无需调整三端代码。
+3. **路径**:派对AI 使用 Mac 路径,当前为 Windows;需在文档或配置中说明环境差异或提供路径映射。
+4. **数据**:`content_upload.py` 直连 DB 与 soul-api 并存,需核对 chapters 表结构与字段一致性。
+5. **待确认**:`content_upload.py` 与 soul-api 的 chapters 模型是否完全一致?是否规划将文章上传迁移到 soul-api 接口?
+
+---
+
+## 待办事项
+
+| 责任角色 | 任务 | 优先级 | 截止建议 |
+|---------|------|--------|---------|
+| 后端开发 | 核对 content_upload.py 与 chapters 表/soul-api 模型一致性 | 中 | 2026-03-20 |
+| 产品经理 | 确认文章上传是否规划迁移到 soul-api 接口 | 低 | 待定 |
+| 助理橙子 | 在开发文档中补充 new-soul 与 Mycontent 关系说明 | 低 | 2026-03-18 |
+
+---
+
+## 问题与作答区
+
+| # | 问题 | 责任角色 | 作答 |
+|---|------|---------|------|
+| 1 | content_upload.py 与 soul-api 的 chapters 模型是否完全一致? | 后端开发 | (待补充) |
+| 2 | 是否规划将文章上传迁移到 soul-api 接口? | 产品经理 | (待补充) |
+| 3 | new-soul 派对AI 与 Mycontent 是否为同一代码库、不同环境(Mac vs Windows)? | 产品经理 | (待补充) |
+
+---
+
+## 各角色经验与业务理解更新
+
+### 产品经理
+
+- new-soul 派对AI 与 Mycontent 为同一业务的不同层面:运营侧 vs 产品侧,互补非替代。
+
+### 后端开发
+
+- content_upload.py 直连 DB 与 soul-api 并存,需核对 chapters 表结构;中长期可规划迁移到 soul-api 接口。
+
+### 管理端开发工程师
+
+- 派对AI 不新增管理端需求,管理端以当前项目为准。
+
+### 小程序开发工程师
+
+- 派对AI 的小程序站管理与当前 miniprogram 一致,仅涉及发布流程。
+
+### 测试人员
+
+- 引入派对AI 流程时需回归:文章上传、飞书推送、小程序展示;关注 content_upload.py 与 soul-api 数据流一致性。
+
+### 团队共享
+
+- new-soul 派对AI(魂AI)9 技能 5 组:魂资/魂流/魂产/魂码/魂质;与 Mycontent 三端(soul-api、soul-admin、miniprogram)为同一业务不同层面,路径差异(Mac vs Windows)需在文档中说明。
+
+---
+
+*会议纪要由助理橙子生成 | 各角色经验已同步至 `agent/{角色}/evolution/2026-03-16.md`*
diff --git a/.cursor/meeting/2026-03-16_链接人与事与存客宝对接优化.md b/.cursor/meeting/2026-03-16_链接人与事与存客宝对接优化.md
new file mode 100644
index 0000000..58f0b23
--- /dev/null
+++ b/.cursor/meeting/2026-03-16_链接人与事与存客宝对接优化.md
@@ -0,0 +1,85 @@
+# 会议纪要 - 2026-03-16 | 链接人与事与存客宝对接优化
+
+> 本文件由**助理橙子**在会议结束后自动生成。
+
+---
+
+## 基本信息
+
+- **时间**:2026-03-16
+- **议题**:链接人与事列表优化、存客宝对接参数、@mention 显示异常修复
+- **触发方式**:会议结束,总结会议
+- **参与角色**:管理端开发工程师、后端工程师、团队
+
+---
+
+## 各角色发言
+
+> 本次为开发会话总结,非正式多角色会议。按完成事项归类。
+
+### 【管理端开发工程师】
+
+- 链接人与事列表改为 `` 布局,解决列头与数据对齐问题
+- 新增 planId、apiKey 两列展示
+- apiKey 列增加复制图标,点击复制到剪贴板
+- 删除操作改为 Dialog 二次确认弹窗,替代原生 confirm
+- 弹窗尺寸优化:max-w-md、p-4、gap-3,避免过大
+
+### 【后端工程师】
+
+- 存客宝创建计划参数调整:planType=1、sceneId=9、scenario=9、status=1
+- ParseAutoLinkContent 输出 mention span 时增加 `data-label`,修复 TipTap 显示 token 而非名字的问题
+- 已损坏内容(span 内为 token)自动修复:用 token 查 persons 取真实名字补回 data-label
+
+### 【团队共享】
+
+- TipTap Mention 需 `data-label` 属性:仅从 data-label 解析显示名,缺则回退显示 data-id(token)
+
+---
+
+## 讨论过程
+
+(开发会话,用户逐项提出需求并实现)
+
+---
+
+## 会议决议
+
+1. **链接人与事列表**:使用 table 布局,展示 token、@的人、获客计划活动名、planId、apiKey、操作
+2. **存客宝创建计划**:planType=1、sceneId=9、status=1
+3. **@mention 存储格式**:span 必须含 `data-label` 存显示名,否则 TipTap 会显示 token
+
+---
+
+## 待办事项
+
+(无待办,原待确认项已确认为代码 bug 并修复)
+
+---
+
+## 问题与作答区
+
+| # | 问题 | 责任角色 | 作答 |
+|---|------|---------|------|
+| 1 | CKBLead 接口 401 无效的 apiKey | 后端 | **已修复**(代码 bug,通过 planType/sceneId/status 等参数修正) |
+| 2 | sceneId=9 与 planType=1 在存客宝业务含义 | 后端 | **已修复**(代码已按 planType=1、sceneId=9、status=1 实现) |
+
+---
+
+## 各角色经验与业务理解更新
+
+### 管理端开发工程师
+
+- 链接人与事列表用 table 布局;apiKey 列需复制图标;删除用 Dialog 二次确认
+
+### 后端工程师
+
+- ParseAutoLinkContent 输出 mention 必须含 data-label;存客宝 create 需 planType=1 sceneId=9 status=1;已损坏 mention 可查 persons 补回 label
+
+### 团队共享
+
+- TipTap Mention 的 label 仅从 data-label 解析,不解析 span 内文本
+
+---
+
+*会议纪要由助理橙子生成 | 各角色经验已同步至 `agent/{角色}/evolution/2026-03-16.md`*
diff --git a/.cursor/meeting/2026-03-17_会议收尾-源码优化完成与测试流程定稿.md b/.cursor/meeting/2026-03-17_会议收尾-源码优化完成与测试流程定稿.md
new file mode 100644
index 0000000..9c16986
--- /dev/null
+++ b/.cursor/meeting/2026-03-17_会议收尾-源码优化完成与测试流程定稿.md
@@ -0,0 +1,90 @@
+# 会议收尾 - 2026-03-17 | 源码优化完成与测试流程定稿
+
+> 本文件由**助理橙子**在会议结束后自动生成。对应会议:2026-03-17 稳定版源码质量优化方案讨论与开发安排。
+
+---
+
+## 基本信息
+
+- **时间**:2026-03-17
+- **议题**:会议收尾 — 源码质量优化完成、开发环境测试、功能测试流程定稿、开发文档同步
+- **触发方式**:结束会议、沉淀经验、开发部门同步需求
+- **参与角色**:产品经理、后端开发、管理端开发工程师、小程序开发工程师、测试人员、助理橙子
+
+---
+
+## 收尾结论
+
+### 1. 源码质量优化(10 项全部完成)
+
+| 端 | 任务 | 状态 |
+|----|------|------|
+| 后端 | 敏感配置生产环境强制校验(config.go) | ☑️ 已完成 |
+| 后端 | 新增 GET /api/admin/user/track + AdminAuth | ☑️ 已完成 |
+| 后端 | AdminWithdrawTest 环境限制 | ☑️ 已完成 |
+| 管理端 | UserDetailModal 改为 /api/admin/user/track | ☑️ 已完成 |
+| 管理端 | RichEditor name/label HTML 转义 | ☑️ 已完成 |
+| 小程序 | 删除 payment.js | ☑️ 已完成 |
+| 小程序 | 删除 goToMatch 重复定义 | ☑️ 已完成 |
+| 小程序 | 删除 read.js.backup、referral.wxss.backup | ☑️ 已完成 |
+| 小程序 | appId 等从 config 读取 | ☑️ 已完成 |
+| 小程序 | totalSections 动态获取 | ☑️ 已完成 |
+
+### 2. 开发环境测试
+
+- **环境**:local (http://localhost:8080)
+- **结果**:10 通过、2 跳过、0 失败
+- **跳过**:test_dev_login_as(需 SOUL_MINIPROGRAM_DEV_USER_ID)、test_backfill_persons_ckb_api_key(需 CKB 配置)
+
+### 3. 测试流程与文档
+
+| 产出 | 路径 | 说明 |
+|------|------|------|
+| 功能测试流程 | scripts/test/功能测试流程.md | 环境准备→自动化→手工验证→问题汇总→报告;成功 ☑️,失败列问题 |
+| 测试报告模板 | scripts/test/测试报告-环境与用例清单.md | 环境、用例清单、结果记录、归档说明 |
+
+### 4. 开发文档同步
+
+- **运营与变更**:新增第十七部分「源码优化完成与测试流程定稿」
+- **需求汇总**:源码优化为内部质量项,无新增需求条目
+
+---
+
+## 问题与作答区
+
+| # | 问题 | 责任角色 | 作答 |
+|---|------|---------|------|
+| 1 | 生产环境判断:是否以 MODE=release 为准? | 后端开发 | (待补充) |
+| 2 | dev/login-as 后端限制方案:环境变量 or IP 白名单? | 后端开发 | (待补充) |
+
+---
+
+## 各角色经验与业务理解更新
+
+### 产品经理
+
+- 源码质量优化验收:10 项全部完成,功能不变;测试流程与报告模板已定稿
+
+### 后端开发
+
+- 源码优化已落地:config 生产校验、admin/user/track、AdminWithdrawTest 环境限制
+
+### 管理端开发工程师
+
+- UserDetailModal、RichEditor 优化已落地;开发环境测试通过
+
+### 小程序开发工程师
+
+- payment.js 删除、goToMatch 去重、备份清理、config 读取、totalSections 动态化已落地
+
+### 测试人员
+
+- 功能测试流程定稿:☑️ 成功、失败列问题、最终报告;开发环境 10 通过 2 跳过
+
+### 助理橙子
+
+- 会议收尾:纪要、经验入库、项目索引、会议索引、开发文档同步
+
+---
+
+*会议收尾由助理橙子执行 | 各角色经验已同步至 agent/{角色}/evolution/2026-03-17.md*
diff --git a/.cursor/meeting/2026-03-17_性能优化与Redis缓存方案落地.md b/.cursor/meeting/2026-03-17_性能优化与Redis缓存方案落地.md
new file mode 100644
index 0000000..6217d7d
--- /dev/null
+++ b/.cursor/meeting/2026-03-17_性能优化与Redis缓存方案落地.md
@@ -0,0 +1,101 @@
+# 会议纪要 - 2026-03-17 | 性能优化与 Redis 缓存方案落地
+
+> 本文件由**助理橙子**在会议结束后自动生成。
+
+---
+
+## 基本信息
+
+- **时间**:2026-03-17
+- **议题**:三端性能优化、Redis 缓存接入、OSS 上传、健康检查增强
+- **参与角色**:后端开发、管理端开发工程师、小程序开发工程师、测试人员、助理橙子
+
+---
+
+## 会议决议
+
+### 1. Redis 缓存接入(已完成)
+
+| 接口 | 路由 | TTL | 失效触发 |
+|------|------|-----|----------|
+| 目录 | `/api/miniprogram/book/parts` | 10min | 章节增删改 |
+| 热门 | `/api/miniprogram/book/hot` | 5min | 章节更新 |
+| 推荐 | `/api/miniprogram/book/recommended` | 5min | 章节更新 |
+| 统计 | `/api/miniprogram/book/stats` | 5min | 章节更新 |
+| 配置 | `/api/miniprogram/config` | 10min | 配置变更 |
+| 正文 | `/api/book/chapter/by-mid/:mid` | 30min | 章节内容更新 |
+
+### 2. Redis 容灾
+
+- Redis 未配置或连接失败时自动回退 DB,不阻塞业务
+- 读写失败仅打日志,不向上抛出
+
+### 3. OSS 上传接入(已完成)
+
+- 管理端图片上传支持阿里云 OSS
+- 未配置或失败时回退本地磁盘
+- 删除支持 OSS URL 与本地路径
+
+### 4. /health 接口增强(已完成)
+
+- 返回 `database`、`redis` 连接状态(ok / disconnected / disabled)
+
+### 5. Redis 配置
+
+- `.env.production`、`.env.development` 增加 REDIS_URL
+- 服务器 Redis:端口 6379,密码 ckb@!(URL 编码 ckb%40%21)
+
+### 6. 迁移功能变更清单(2026-03-17)
+
+1. Redis 缓存接入:目录、热门/推荐/统计、config、章节正文
+2. Redis 容灾:未配置或失败时回退 DB
+3. OSS 上传接入:管理端图片支持阿里云 OSS,容灾回退本地
+4. /health 接口增强:返回 database、redis 连接状态
+5. Redis 配置:.env 增加 REDIS_URL,服务器密码 ckb@!
+6. 文件上传测试:新增 test_upload.py 共 6 个用例
+7. 缓存失效策略:章节/内容/配置变更时自动失效对应 Redis 缓存
+
+---
+
+## 待办事项
+
+| 责任角色 | 任务 | 优先级 | 截止建议 |
+|---------|------|--------|---------|
+| 后端开发 | 部署后验证 Redis 连接(/health 显示 redis: ok) | 中 | 部署后 |
+| 测试人员 | 部署后回归缓存接口(parts、hot、config、章节阅读) | 中 | 部署后 |
+
+---
+
+## 问题与作答区
+
+| # | 问题 | 责任角色 | 作答 |
+|---|------|---------|------|
+| 1 | Redis 与 soul-api 跨机部署时,REDIS_URL 中 host 填 Redis 服务器 IP 是否已文档化? | 后端开发 | (待补充) |
+
+---
+
+## 各角色经验与业务理解更新
+
+### 后端开发
+
+- internal/cache 包:Get/Set/Del、GetString/SetString;KeyBookParts、KeyChapterContent 等
+- 缓存失效:InvalidateBookParts、InvalidateBookCache、InvalidateConfig、InvalidateChapterContent
+- 章节正文缓存:先查元数据(不含 content),再取 content(Redis 或 DB)
+
+### 管理端开发工程师
+
+- OSS 配置在系统设置保存后,上传接口自动优先 OSS;无需前端改动
+
+### 测试人员
+
+- test_upload.py:6 个用例覆盖上传成功、鉴权、校验、删除
+- /health 可验证 database、redis 连接状态
+
+### 团队共享
+
+- Redis 容灾约定:不可用时回退 DB,不阻塞业务
+- 缓存 key 规范:soul:{业务}:{标识}
+
+---
+
+*会议纪要由助理橙子生成 | 各角色经验已同步至 `agent/{角色}/evolution/2026-03-17.md`*
diff --git a/.cursor/meeting/2026-03-17_新版管理端迁移到稳定版实施方案确认.md b/.cursor/meeting/2026-03-17_新版管理端迁移到稳定版实施方案确认.md
new file mode 100644
index 0000000..a840f8f
--- /dev/null
+++ b/.cursor/meeting/2026-03-17_新版管理端迁移到稳定版实施方案确认.md
@@ -0,0 +1,106 @@
+# 会议纪要 - 2026-03-17 | 新版管理端迁移到稳定版实施方案确认
+
+> 本文件由**助理橙子**在会议结束后自动生成。
+
+---
+
+## 基本信息
+
+- **时间**:2026-03-17
+- **议题**:新版管理端迁移到稳定版 - 确认实施方案
+- **触发方式**:乘风调动开发人员开会,并确认实施方案
+- **参与角色**:产品经理、后端开发、管理端开发工程师、小程序开发工程师、测试人员
+
+---
+
+## 各角色发言
+
+### 【产品经理】
+
+需求基准以稳定版小程序为准;内容管理以稳定版为主;验收标准见《需求评估》§七;RFM、journey、神射手是否保留需运营确认。
+
+### 【后端开发】
+
+以稳定版为主,后端无需新增接口;若保留 RFM/journey/神射手需补 5 个 router;OSS 需确认 /api/admin/settings 是否支持 ossConfig;建议先补 router 再迁移。
+
+### 【管理端开发工程师】
+
+内容管理以稳定版为准不覆盖;必须保留用户详情余额、订单支付方式/代付、RechargeAlert、LinkedMp 等;可吸纳编辑禁用、鉴权、API 文档、OSS;按模块分批合并。
+
+### 【小程序开发工程师】
+
+本次主要影响管理端,小程序无需改动;迁移后做三端联调验证用户规则、订单、余额。
+
+### 【测试人员】
+
+按《需求评估》§七验收;回归提现、分销、找伙伴等;合并时避免误覆盖稳定版独有逻辑。
+
+---
+
+## 讨论过程
+
+- 乘风确认:后端 router 补齐建议迁移前完成
+- 产品经理:OSS 按实际部署需求,用户决议「新版有的就迁移」→ OSS 纳入
+- 管理端:同意 OSS 纳入,新版独有能力全部吸纳
+
+---
+
+## 会议决议
+
+1. **新版有的就迁移**:API 文档 Tab、api-docs 独立页、OSS 配置、编辑时手机号禁用、鉴权逻辑优化,全部吸纳到稳定版
+2. **内容管理**:以稳定版为主,不采纳新版
+3. **后端 router**:迁移前补齐 users/rfm、users/journey-stats、shensheshou 共 5 个路由(若运营使用)
+4. **实施顺序**:Phase 0 后端补 router → Phase 1 基础模块 → Phase 2 业务模块 → Phase 3 内容保持 → Phase 4 验收
+
+---
+
+## 待办事项(乘风指派)
+
+| 责任角色 | 任务 | 优先级 | 截止建议 |
+|---------|------|--------|---------|
+| 后端开发 | soul-api router 注册 users/rfm、users/journey-stats、shensheshou 共 5 个路由 | 高 | 迁移前 |
+| 后端开发 | 确认 /api/admin/settings 是否支持 ossConfig,若不支持则补充 | 中 | 迁移前 |
+| 管理端开发工程师 | 从 new-soul/soul-admin 迁移:ApiDocsPage、OSS 配置、api-docs 路由、编辑时手机号禁用、鉴权逻辑 | 高 | - |
+| 管理端开发工程师 | 以稳定版为基准合并,内容管理不覆盖,其他模块选择性合并 | 高 | - |
+| 测试人员 | 迁移完成后按《需求评估》§七执行验收,三端联调 | 中 | 迁移后 |
+
+---
+
+## 问题与作答区
+
+| # | 问题 | 责任角色 | 作答 |
+|---|------|---------|------|
+| 1 | RFM、用户旅程、神射手是否继续使用? | 产品/运营 | (待补充) |
+| 2 | /api/admin/settings 是否已支持 ossConfig? | 后端开发 | (待补充) |
+
+---
+
+## 各角色经验与业务理解更新
+
+### 产品经理
+
+- 新版管理端迁移以稳定版为基准,内容管理以稳定版为主;新版独有能力(API 文档、OSS、编辑禁用、鉴权)全部吸纳
+
+### 后端开发
+
+- 迁移前需补 router:users/rfm、users/journey-stats、shensheshou 共 5 个;需确认 settings 的 ossConfig 支持
+
+### 管理端开发工程师
+
+- 迁移策略:内容管理不覆盖;其他模块以稳定版为主;吸纳新版 ApiDocsPage、OSS、编辑禁用、鉴权;按模块分批合并
+
+### 小程序开发工程师
+
+- 管理端迁移不影响小程序;迁移后做三端联调验证
+
+### 测试人员
+
+- 验收按《需求评估》§七;合并时需 diff 核对避免误覆盖
+
+### 团队共享
+
+- 新版管理端迁移到稳定版:内容管理以稳定版为主,新版独有能力全部吸纳;详见 agent/团队/evolution/2026-03-17.md
+
+---
+
+*会议纪要由助理橙子生成 | 各角色经验已同步至 `agent/{角色}/evolution/2026-03-17.md`*
diff --git a/.cursor/meeting/2026-03-17_稳定版源码质量优化方案讨论与开发安排.md b/.cursor/meeting/2026-03-17_稳定版源码质量优化方案讨论与开发安排.md
new file mode 100644
index 0000000..8ec9c16
--- /dev/null
+++ b/.cursor/meeting/2026-03-17_稳定版源码质量优化方案讨论与开发安排.md
@@ -0,0 +1,113 @@
+# 会议纪要 - 2026-03-17 | 稳定版源码质量优化方案讨论与开发安排
+
+> 本文件由**助理橙子**在会议结束后自动生成。
+
+---
+
+## 基本信息
+
+- **时间**:2026-03-17
+- **议题**:稳定版源码质量优化方案讨论与开发安排(基于源码质量分析报告,不影响现有功能)
+- **触发方式**:开会讨论
+- **参与角色**:产品经理、后端开发、管理端开发工程师、小程序开发工程师、测试人员
+
+---
+
+## 各角色发言
+
+### 【产品经理】
+
+从需求与业务角度,本次优化聚焦**安全与可维护性**,不涉及新功能,用户无感知。建议按优先级分批处理:高优(安全)→ 中优(代码质量)→ 低优(性能/结构)。验收标准:优化后现有功能行为不变,三端联调通过。
+
+### 【后端开发】
+
+高优:1)敏感配置在 `config.go` 中,生产环境(MODE=release)强制校验,缺则 Fatal;2)新增 `GET /api/admin/user/track` 并加 AdminAuth,原 `/api/user/track` 保留给小程序 POST 埋点。中优:AdminWithdrawTest 加环境限制。低优:DBUsersList 拆分可后续做。
+
+### 【管理端开发工程师】
+
+中优:1)后端提供 `/api/admin/user/track` 后,UserDetailModal 改为调用新路径;2)RichEditor 对 name/label 做 HTML 转义防 XSS。低优:上传逻辑抽公共方法可后续做。
+
+### 【小程序开发工程师】
+
+高优:1)payment.js 确认无引用可删除;2)goToMatch 重复定义删除一个;3)删除 read.js.backup、referral.wxss.backup。中优:appId 等从 config 读取、totalSections 动态获取。dev/login-as 由后端限制即可。
+
+### 【测试人员】
+
+回归重点:支付流程、管理端用户详情行为轨迹、我的页找伙伴/推广/搜索、首页目录搜索。建议每项完成后小回归,全部完成后完整三端联调。
+
+---
+
+## 讨论过程
+
+- 后端确认 `/api/admin/user/track` 参数与现 `/api/user/track` 一致(userId、phone、limit),管理端仅改 URL 即可
+- 小程序确认 payment.js 无 require 引用,可安全删除
+- 产品确认按优先级分批,低优可放入后续迭代
+
+---
+
+## 会议决议
+
+1. **高优(本周完成)**:后端敏感配置生产强制校验、新增 `/api/admin/user/track`;管理端 UserDetailModal 改路径;小程序删除 payment.js、goToMatch 重复、备份文件
+2. **中优(下周完成)**:后端 AdminWithdrawTest 环境限制;管理端 RichEditor 转义;小程序 config 读取、totalSections 动态化
+3. **低优(后续迭代)**:DBUsersList 拆分、上传逻辑抽公共、config 缓存
+4. **原则**:所有改动为增量修复,不改现有功能逻辑;每项完成后小回归,全部完成后完整联调
+
+---
+
+## 待办事项
+
+| 责任角色 | 任务 | 优先级 | 截止建议 |
+|---------|------|--------|---------|
+| 后端开发 | 敏感配置生产环境强制校验(config.go) | 高 | 本周 |
+| 后端开发 | 新增 GET /api/admin/user/track + AdminAuth | 高 | 本周 |
+| 后端开发 | AdminWithdrawTest 环境限制 | 中 | 下周 |
+| 管理端开发工程师 | UserDetailModal 改为 /api/admin/user/track | 高 | 本周 |
+| 管理端开发工程师 | RichEditor name/label HTML 转义 | 中 | 下周 |
+| 小程序开发工程师 | 删除 payment.js | 高 | 本周 |
+| 小程序开发工程师 | 删除 goToMatch 重复定义 | 高 | 本周 |
+| 小程序开发工程师 | 删除 read.js.backup、referral.wxss.backup | 高 | 本周 |
+| 小程序开发工程师 | appId 等从 config 读取 | 中 | 下周 |
+| 小程序开发工程师 | totalSections 动态获取 | 中 | 下周 |
+| 测试人员 | 每项完成后小回归 | - | 持续 |
+| 测试人员 | 全部完成后三端联调 | - | 下周 |
+
+---
+
+## 问题与作答区
+
+| # | 问题 | 责任角色 | 作答 |
+|---|------|---------|------|
+| 1 | 生产环境判断:是否以 MODE=release 为准? | 后端开发 | (待补充) |
+| 2 | dev/login-as 后端限制方案:环境变量 or IP 白名单? | 后端开发 | (待补充) |
+
+---
+
+## 各角色经验与业务理解更新
+
+### 产品经理
+
+- 源码质量优化按安全→可维护→性能分批,验收标准为功能不变、三端联调通过
+
+### 后端开发
+
+- 敏感配置生产环境缺则 Fatal;user/track 查询迁至 admin 组并加鉴权;AdminWithdrawTest 非 develop 拒绝
+
+### 管理端开发工程师
+
+- UserDetailModal 调用 /api/admin/user/track;RichEditor @mention 需对 name/label 做 HTML 转义防 XSS
+
+### 小程序开发工程师
+
+- payment.js 废弃可删;goToMatch 去重;备份文件清理;appId 等优先从 config 读取;totalSections 动态获取
+
+### 测试人员
+
+- 源码优化类改动需每项小回归 + 全部完成后完整三端联调,重点覆盖支付、用户详情、我的页、搜索
+
+### 团队共享
+
+- 源码质量优化原则:增量修复、不改功能逻辑;高优安全项优先,中优可维护项次之,低优可后续迭代
+
+---
+
+*会议纪要由助理橙子生成 | 各角色经验已同步至 `agent/{角色}/evolution/2026-03-17.md`*
diff --git a/.cursor/meeting/2026-03-18_超级个体开通后自动创建@人与资料引导.md b/.cursor/meeting/2026-03-18_超级个体开通后自动创建@人与资料引导.md
new file mode 100644
index 0000000..b22c576
--- /dev/null
+++ b/.cursor/meeting/2026-03-18_超级个体开通后自动创建@人与资料引导.md
@@ -0,0 +1,130 @@
+# 会议纪要 - 2026-03-18 | 超级个体开通后自动创建@人与资料引导
+
+> 本文件由**助理橙子**在会议结束后自动生成。
+
+---
+
+## 基本信息
+
+- **时间**:2026-03-18 (记录时间以当天为准)
+- **议题**:
+ - 小程序用户开通「超级个体」后,管理端「链接人与事」需自动创建一个与用户昵称一致的 `@人`
+ - 小程序新增校验:用户昵称/头像为默认值时,跳转到仅修改头像+昵称的引导页;且在支付超级个体之前也必须先完成此检查
+- **触发方式**:开个会
+- **参与角色**:产品经理、后端开发、管理端开发工程师、小程序开发工程师、测试人员
+
+---
+
+## 各角色发言
+
+### 【产品经理】
+
+- **目标**:让超级个体开通后立刻具备「可被内容 @」的入口,形成“内容→人→转化”的闭环;同时在付费前强制用户完善头像昵称,避免默认资料影响信任与转化。
+- **业务规则**:
+ - 自动创建的 `@人` 展示名 **必须与当前用户昵称一致**(后续昵称变更需要同步策略)。
+ - 资料引导页只包含头像+昵称两项,用户完成后回到原流程继续支付/使用。
+- **验收**:
+ - 开通成功后,管理端「链接人与事」列表出现该用户昵称对应的记录;且可在内容编辑中被搜索/插入为 mention。
+ - 支付超级个体前若昵称/头像为默认值,必须跳转引导页并阻断支付。
+
+### 【后端开发】
+
+- **建议实现点**:在“超级个体开通成功”的后端闭环(支付回调/开通接口最终落库点)触发一次“确保 Person 存在”的逻辑。
+- **数据与幂等**:
+ - 仅用 `name=nickname` 作为唯一键会遇到重名与改名问题;建议在 `persons` 增加 `user_id`(或等价字段)作为绑定关系,做到幂等与可追溯。
+ - 若暂不加字段,也至少保证:同名已存在则复用,不重复创建。
+- **接口契约方向**:
+ - 小程序资料默认判定若后端统一输出更稳:建议在用户资料接口/登录态接口返回 `profileNeedComplete` / `isDefaultAvatar` / `isDefaultNickname`,小程序只负责跳转与阻断支付。
+
+### 【管理端开发工程师】
+
+- 「链接人与事」现有列表与 Person CRUD 已具备承载自动创建记录的能力;若增加 `user_id` 字段,可在列表中加一列“来源/绑定用户”便于运营排查。
+- 内容编辑插入 mention 已依赖 `data-label` 显示名规则:只要后端/存储保证 `data-label=昵称`,即可正确展示。
+
+### 【小程序开发工程师】
+
+- 工程上已有独立页 `pages/avatar-nickname/avatar-nickname`(仅头像+昵称),可复用。
+- 需要补两处拦截:
+ - **支付前**:在“去支付/确认支付”按钮入口做一次默认资料校验,未通过则跳转引导页并 return。
+ - **开通后**:在超级个体权益页/开通成功回调后再做一次校验,若仍为默认则跳引导页(兼容用户开通后才意识到资料不完善)。
+- 默认判定若只靠前端字符串规则易漂移,建议后端提供明确布尔值;前端可做兜底规则(空昵称、昵称以“微信用户”开头、头像为空/命中默认头像域名等)。
+
+### 【测试人员】
+
+- 新增用例需覆盖:
+ - 资料默认 → 触发引导页 → 保存成功 → 回到支付流程继续完成支付
+ - 资料已完善 → 不拦截支付
+ - 开通成功后自动创建 Person:重复支付回调/重复点击导致的幂等(不应创建多条)
+ - 昵称变更后:自动创建记录是否更新展示名(按决议验收)
+
+---
+
+## 讨论过程
+
+- 讨论了 Person 的现有语义(用于文章 mention 与 CKB 线索承接),并确认“超级个体开通后自动创建 @人”应落在同一条 Person 体系内,避免另起一套表导致前后端两套 mention 逻辑分裂。
+- 对“唯一键”的讨论:仅用昵称无法保证幂等与长期一致性,因此倾向新增 `persons.user_id` 绑定用户;并在昵称变化时做同步更新策略。
+- 对“默认资料判定”的讨论:前端硬编码规则不稳,后端输出明确 flags 最稳;前端保留兜底。
+
+---
+
+## 会议决议
+
+1. **自动创建 @人(Person)触发点**:在“超级个体开通成功”的后端落库闭环触发 `ensurePersonForUser(userId)`;幂等保证不重复创建。
+2. **Person 与用户绑定**:优先方案为 `persons` 增加 `user_id` 字段(并加唯一约束/索引),以 `user_id` 为幂等键;`name` 作为展示名,与用户昵称保持同步策略。
+3. **小程序资料引导**:复用 `pages/avatar-nickname`,在“支付前入口”与“开通后进入权益页/成功回调”两处增加默认资料校验与跳转。
+4. **默认资料判定口径**:后端优先提供明确 flags(如 `profileNeedComplete` / `isDefaultNickname` / `isDefaultAvatar`),小程序仅消费;前端可保留兜底规则。
+
+---
+
+## 待办事项
+
+| 责任角色 | 任务 | 优先级 | 截止建议 |
+|---------|------|--------|---------|
+| 后端开发 | 在超级个体开通成功链路增加 `ensurePersonForUser`;为 `persons` 增加 `user_id` 并做幂等;必要时补“昵称变更同步” | 高 | 2026-03-20 |
+| 管理端开发工程师 | 链接人与事列表可选增加 `userId/来源` 展示与筛选;确认插入 mention 时 `data-label` 始终为昵称 | 中 | 2026-03-21 |
+| 小程序开发工程师 | 支付前与开通后两处增加资料默认校验;不通过则跳转 `avatar-nickname` 引导页并阻断后续动作 | 高 | 2026-03-20 |
+| 产品经理 | 明确“昵称变更后的同步规则/是否允许重名冲突显示”;补充验收标准文案 | 中 | 2026-03-19 |
+| 测试人员 | 补充用例:资料引导阻断支付 + 幂等创建 Person + 昵称变更同步回归 | 中 | 联调前 |
+
+---
+
+## 问题与作答区
+
+| # | 问题 | 责任角色 | 作答 |
+|---|------|---------|------|
+| 1 | `persons.user_id` 是否需要唯一索引?重名用户在列表展示如何区分? | 后端/产品 | (待补充) |
+| 2 | 昵称变更是否必须同步更新 Person.name?若同步,是否保留历史别名? | 产品/后端 | (待补充) |
+| 3 | 默认头像/昵称判定的最终口径(后端 flags vs 前端兜底规则)以哪一套为准? | 后端/小程序 | (待补充) |
+
+---
+
+## 各角色经验与业务理解更新
+
+### 产品经理
+
+- 超级个体开通后要立刻具备“可被内容 @”入口;支付前资料完善是转化关键拦截点(头像/昵称)。
+
+### 后端开发
+
+- 自动创建 Person 必须做幂等,建议以 `user_id` 绑定,避免仅靠昵称造成重名/改名混乱;默认资料判定尽量由后端输出 flags,前端只消费。
+
+### 管理端开发工程师
+
+- Person 体系可承载自动创建记录;mention 显示依赖 `data-label`,需确保展示名与数据一致。
+
+### 小程序开发工程师
+
+- 复用 `avatar-nickname` 引导页,在支付前/开通后两处做资料校验与跳转;后端 flags 优先,前端规则兜底。
+
+### 测试人员
+
+- 新增强约束:资料未完善必须阻断支付;自动创建 Person 要验幂等与昵称变更同步。
+
+### 团队共享
+
+- “可被 @ 的人”统一走 Person 体系;幂等键优先绑定业务主键(userId),展示名同步为昵称;默认资料判定由后端输出布尔 flags 更稳定。
+
+---
+
+*会议纪要由助理橙子生成 | 各角色经验已同步至 `agent/{角色}/evolution/2026-03-18.md`*
+
diff --git a/.cursor/meeting/2026-03-24_开发进度同步会议.md b/.cursor/meeting/2026-03-24_开发进度同步会议.md
new file mode 100644
index 0000000..42b647c
--- /dev/null
+++ b/.cursor/meeting/2026-03-24_开发进度同步会议.md
@@ -0,0 +1,114 @@
+# 会议纪要 - 2026-03-24 | 开发进度同步会议
+
+> 本文件由**助理橙子**在会议结束后自动生成。
+
+---
+
+## 基本信息
+
+- **时间**:2026-03-24
+- **议题**:全员查看自己的代码与开发文档,同步开发进度
+- **触发方式**:开个会议,所有的人查看自己的代码和@开发文档同步开发进度
+- **参与角色**:产品经理、后端开发、管理端开发工程师、小程序开发工程师、测试人员
+
+---
+
+## 各角色发言
+
+### 【产品经理】
+
+- 2026-03-20 需求(提现审批、我的收益、推广设置等)已与实现对齐;小程序手机号登录变更已写入运营与变更。
+- 项目索引、主需求、落地推进表「最后更新」停在 2026-03-18,缺 2026-03-19、2026-03-20 及之后的记录。
+- 待办:补充项目索引近期决议;链接人与事置顶按《链接人与事-置顶与超级个体对应设计》为准。
+
+### 【后端开发】
+
+- 提现审批逻辑、referral_config(withdrawFee、enableAutoWithdraw)、admin_withdrawals 落库与文档一致。
+- 后端项目索引最后更新 2026-03-18,未记录 2026-03-20 提现相关变更。
+- 待办:项目索引补充 2026-03-20;router 补齐(users/rfm、journey-stats、shensheshou)、ossConfig 确认。
+
+### 【管理端开发工程师】
+
+- 提现审核备注列、自动审批开关、推广设置提现手续费与文档一致。
+- 项目索引停在 2026-03-18,未记录 2026-03-20 变更。
+- 待办:项目索引补充;DistributionPage Order.description 类型错误待修。
+
+### 【小程序开发工程师】
+
+- 手机号一键登录、login-modal 公用组件、我的收益 availableEarnings 与文档一致;经验已入库 2026-03-19、2026-03-20。
+- 项目索引停在 2026-03-18,未体现 2026-03-19、2026-03-20 改动。
+- 待办:项目索引补充;富文本渲染(rich-text)为技术债。
+
+### 【测试人员】
+
+- 功能测试流程与报告模板已定稿。
+- 提现、登录、收益、推广设置等需补充/更新用例;singlePage、getPhoneNumber 隐私边界用例待补。
+- 项目索引停在 2026-03-18。
+
+---
+
+## 讨论过程
+
+- 乘风汇总:各端项目索引普遍停在 2026-03-18,实现已到 2026-03-20,需补齐。
+- 产品确认:需求清单、运营与变更已基本同步,主要是项目索引滞后。
+- 橙子建议:按角色补充索引后,统一标注「最后更新:2026-03-24」。
+
+---
+
+## 会议决议
+
+1. **项目索引补齐**:产品、后端、管理端、小程序、测试五份索引补充 2026-03-19、2026-03-20 及之后变更,最后更新统一为 2026-03-24。
+2. **文档同步原则**:实现变更后同步更新《需求汇总》《运营与变更》及对应角色项目索引。
+3. **原待办保留**:router/ossConfig 迁移、DistributionPage 类型错误、富文本渲染、测试用例补充按原计划推进。
+
+---
+
+## 待办事项
+
+| 责任角色 | 任务 | 优先级 | 截止建议 |
+|---------|------|--------|---------|
+| 后端开发 | router 补齐 users/rfm、journey-stats、shensheshou;确认 ossConfig | 中 | 按迁移清单 |
+| 管理端开发工程师 | DistributionPage Order.description 类型错误修复 | 低 | 有空即修 |
+| 小程序开发工程师 | 富文本渲染(rich-text)技术债 | 低 | 待定 |
+| 测试人员 | 提现/登录/收益/推广设置用例补充;singlePage、getPhoneNumber 隐私边界 | 中 | 下次回归前 |
+
+---
+
+## 问题与作答区
+
+| # | 问题 | 责任角色 | 作答 |
+|---|------|---------|------|
+| 1 | `/api/admin/settings` 是否支持 ossConfig? | 后端开发 | (待补充) |
+| 2 | 富文本 content 数据库格式确认后,rich-text 实施方案? | 产品/小程序 | (待补充) |
+
+---
+
+## 各角色经验与业务理解更新
+
+### 产品经理
+
+- 项目索引与开发文档需定期同步,实现变更后应同步更新索引日期。
+
+### 后端开发
+
+- 2026-03-20 提现相关变更(审批逻辑、fail_reason/error_message、referral_config)已与文档一致;项目索引需及时跟进。
+
+### 管理端开发工程师
+
+- 提现审核备注列、自动审批开关与推广设置已与文档一致;项目索引需及时跟进。
+
+### 小程序开发工程师
+
+- 手机号登录、login-modal、我的收益 availableEarnings 已与文档一致;项目索引需及时跟进。
+
+### 测试人员
+
+- 每次功能变更(提现、登录、推广设置等)需同步更新回归用例;singlePage、getPhoneNumber 为必测边界。
+
+### 团队共享
+
+- 文档同步原则:实现变更后同步更新需求汇总、运营与变更及对应角色项目索引,最后更新日期统一标注。
+
+---
+
+*会议纪要由助理橙子生成 | 各角色经验已同步至 `agent/{角色}/evolution/2026-03-24.md`*
diff --git a/.cursor/meeting/2026-03-24_需求与进度及三端闭环评审.md b/.cursor/meeting/2026-03-24_需求与进度及三端闭环评审.md
new file mode 100644
index 0000000..74c145a
--- /dev/null
+++ b/.cursor/meeting/2026-03-24_需求与进度及三端闭环评审.md
@@ -0,0 +1,117 @@
+# 会议纪要 - 2026-03-24 | 需求与进度及三端闭环评审
+
+> 本文件由**助理橙子**在会议结束后自动生成。
+
+---
+
+## 基本信息
+
+- **时间**:2026-03-24
+- **议题**:重新评估需求与开发进度;业务流程是否闭环;核心基础功能是否完整、有无被破坏;小程序与管理端是否配套齐全
+- **触发方式**:开个会议 + 分析报告
+- **参与角色**:产品经理、后端开发、管理端开发工程师、小程序开发工程师、测试人员
+- **主持人**:乘风(老板分身)
+
+---
+
+## 各角色发言
+
+### 【产品经理】
+
+- **需求基线**:《以界面定需求》《项目落地推进表》与代码大体一致;里程碑上阅读付费、代付、分销提现、分润配置等标记为已完成,与当前推进表描述一致。
+- **用户价值闭环**:C 端「看内容—买章节/VIP—推广收益—提现」主链路在文档层面已闭环;**风控与规则闭环**仍有缺口(见《产品意图与功能闭环分析》):提现微信号、资源对接购买校验、匹配发起者资料等依赖后端与请求层配合,否则存在「前端拦得住、接口拦不住」的灰区。
+- **待产品拍板**:2026-03-18 会议曾提「VIP 支付前需完善头像昵称」与当前 `vip.js`「购买前不拦截」不一致,需明确最终规则并同步三端。
+
+### 【后端开发】
+
+- **路由与分组**:`/api/miniprogram/*`、`/api/admin/*`、`/api/db/*` 分工清晰;导师、余额、提现确认收款、存客宝线索、dashboard 等能力在代码注释与 handler 中可核对,**三端隔离未被破坏**。
+- **闭环缺口(实现层)**:`WithdrawPost` 未强制校验 `wechat_id`;`CKBJoin`(investor)未校验付费;`MatchUsers` 未强制发起者联系方式;与《需求未补齐清单》《产品意图与功能闭环分析》一致。
+- **建议**:优先 P0 提现校验 + **统一失败体**(`errorCode` / `needBindWechat`),便于小程序在 `app.request` reject 后仍能从 `err.response` 分支引导(见文档 6.1 节)。
+
+### 【管理端开发工程师】
+
+- **配套情况**:`App.tsx` 路由覆盖 dashboard、orders、users、distribution、withdrawals、content、referral-settings、vip-roles、mentors、mentor-consultations、settings、payment、find-partner、match、match-records 等,与小程序 Tab(首页/目录/找伙伴/我的)及子业务(VIP、导师、代付、推广、提现审核)**总体配套齐全**。
+- **运营侧闭环**:提现审核、推广设置、内容/用户/订单管理支撑 C 端主链路;存客宝相关能力在管理端有配置入口(如 find-partner / CKB)。
+- **已知技术债**:索引中曾记 `DistributionPage Order.description` 类型问题,与本次「功能被破坏」无直接等价关系,但影响类型安全与维护。
+
+### 【小程序开发工程师】
+
+- **页面与能力**:`app.json` 注册页覆盖阅读、章节、找伙伴、我的、推广、代付、VIP、导师、资料、钱包、提现记录等,与后端 miniprogram 接口及管理端运营能力**方向一致**。
+- **闭环断裂点(工程层)**:`app.request` 在 `success: false` 时直接 reject,导致 `needBindWechat`、`errorCode` 等字段在部分页面成为**死分支**;`my` 与 `referral` 提现前对微信号要求不一致。
+- **技术债**:TipTap HTML → `rich-text` 与 @mention 仍为待确认/待实现项(需求未补齐清单)。
+
+### 【测试人员】
+
+- **回归重点**:在补齐后端校验与 `err.response` 后,需增加「绕过前端直接调 API」的负例;提现(含我的页与推广页两条入口)、资源对接、找伙伴提交各一条闭环用例。
+- **三端联调**:管理端改推广/手续费/自动提现开关后,小程序侧分润展示与提现流程需对照 `scripts/test/功能测试流程.md` 做一轮回归。
+
+---
+
+## 讨论过程
+
+- **【管理端开发工程师】回复【产品经理】**:管理端菜单已覆盖审核与配置,不单独挡 C 端绕过接口的风险;规则闭环必须后端 + 小程序请求层一致。
+- **【后端开发】回复【小程序开发工程师】**:仅加后端校验不够,若 reject 不携带 body,前端无法引导;建议约定 `err.response` 与文档 6.1~6.4 的调整一致后再测。
+- **【产品经理】回复【全员】**:「核心基础完整」指主链路可用;「未被破坏」指三端边界与里程碑仍在;当前缺口属**加固与一致性**,不推翻已完成里程碑,但应列入下一迭代 P0/P1。
+
+---
+
+## 会议决议
+
+1. **总评**:核心业务闭环在「产品—运营—用户主路径」上成立;**安全与规则闭环**存在已知缺口,以《产品意图与功能闭环分析》《需求未补齐清单》为执行清单,**不构成「核心功能整段被删」类事故**。
+2. **三端配套**:小程序主要场景与管理端审核/配置模块**整体配套**;后端 miniprogram/admin/db 分组保持,**无互窜型破坏**。
+3. **优先动作**:P0 提现 `wechat_id` 后端校验 + 小程序 `app.request` 失败时挂载 `err.response`;P1 CKBJoin / MatchUsers 校验与 `errorCode`;产品确认 VIP 支付前是否拦截头像昵称。
+4. **文档**:本评审结论与同日《开发进度同步会议》互补;详细技术项仍以 `开发文档/10、项目管理/` 下两份分析文档为准。
+
+---
+
+## 待办事项
+
+| 责任角色 | 任务 | 优先级 | 截止建议 |
+|---------|------|--------|---------|
+| 后端开发 | WithdrawPost 无 wechat_id 返回 needBindWechat;CKBJoin/MatchUsers 按文档补校验与 errorCode | P0/P1 | 下一迭代首周 |
+| 小程序开发工程师 | app.request reject 前设置 err.response;统一 my/referral 提现微信号与 catch 分支 | P0 | 与后端同学对齐后 |
+| 产品经理 | 确认 VIP 支付前是否必须完善头像昵称,并更新需求口径 | 中 | 本周 |
+| 测试人员 | 补齐绕过前端的 API 负例与双入口提现用例 | 中 | 联调后 |
+| 管理端开发工程师 | DistributionPage 类型债择机修复 | 低 | 排期内 |
+
+---
+
+## 问题与作答区
+
+| # | 问题 | 责任角色 | 作答 |
+|---|------|---------|------|
+| 1 | VIP(超级个体)支付前是否强制完善头像+昵称? | 产品经理 | (待补充) |
+| 2 | 富文本正文在 DB 中的存储格式是否已冻结(HTML 片段 / JSON)? | 产品经理 + 后端开发 | (待补充) |
+| 3 | ossConfig、router 扩展项是否仍阻塞环境切换? | 后端开发 | (待补充) |
+
+---
+
+## 各角色经验与业务理解更新
+
+### 产品经理
+
+- 闭环分两层:用户动线闭环 vs 规则/风控闭环;当前缺口集中在后者,应用清单文档驱动排期。
+
+### 后端开发
+
+- 失败响应需兼顾「HTTP 层」与「业务体字段」,便于小程序在统一 request 封装下做引导。
+
+### 管理端开发工程师
+
+- 配套评估以路由与业务域对照小程序页面域,存客宝/找伙伴/匹配记录等已覆盖运营侧。
+
+### 小程序开发工程师
+
+- reject 若不携带 body,所有「按字段分支」的后端设计都会落空;与后端约定同一错误契约。
+
+### 测试人员
+
+- 闭环测试需覆盖「仅调 API」场景,与纯 UI 点击路径同等重要。
+
+### 团队共享
+
+- 三端边界未被破坏;当前工作重点是**契约加固**(校验 + errorCode + err.response)与产品规则二选一(VIP 前资料)。
+
+---
+
+*会议纪要由助理橙子生成 | 各角色经验已同步至 `agent/{角色}/evolution/2026-03-24.md`*
diff --git a/.cursor/meeting/2026-03-30_文章详情跳转MBTI小程序传手机号.md b/.cursor/meeting/2026-03-30_文章详情跳转MBTI小程序传手机号.md
new file mode 100644
index 0000000..a352991
--- /dev/null
+++ b/.cursor/meeting/2026-03-30_文章详情跳转MBTI小程序传手机号.md
@@ -0,0 +1,68 @@
+# 会议纪要 - 2026-03-30 | 文章详情 # 标签跳转 MBTI 小程序并传手机号
+
+> 本文件由**助理橙子**在会议结束后整理。
+
+---
+
+## 基本信息
+
+- **时间**:2026-03-30
+- **议题**:阅读页通过正文 `#` 链接标签跳转 MBTI 等关联小程序时,将当前用户手机号通过 URL 查询参数带给目标小程序;目标侧(MBTI)在 `onLoad` 解析 `phone`。
+- **触发方式**:开个会
+- **参与角色**:产品经理、后端开发、管理端开发工程师、小程序开发工程师、测试人员
+
+---
+
+## 各角色发言
+
+### 【产品经理】
+
+需求明确:从文章详情点击配置好的 `#` 标签进入 MBTI 小程序时,需要携带本端已绑定手机号,便于 MBTI 侧识别用户或做承接。未登录或未绑定手机号时仍可跳转,但不带 `phone` 参数(由 MBTI 侧引导登录或留资)。验收:已登录且资料有手机号时,目标小程序启动路径含 `phone` 参数且值正确。
+
+### 【后端开发】
+
+本次无需新增接口。手机号已在登录/资料接口中写入 `userInfo.phone` 与本地缓存;阅读页扩展 `read-extras` 中 `linkedMiniprograms` 与 `linkTags` 配置不变。若未来需服务端校验或脱敏,可再评估专用 token 跳转,当前按产品要求用 URL 传参。
+
+### 【管理端开发工程师】
+
+链接标签「类型=小程序」、`mpKey` 与 `page_path` 继续在内容管理里配置;无需改管理端字段。运营需确保 MBTI 小程序已配置在 `linked_miniprograms` 且 `app.json` 白名单含目标 AppID。
+
+### 【小程序开发工程师】
+
+在 `read.js` 的 `onLinkTagTap` 中,`tagType === 'miniprogram'` 且 `wx.navigateToMiniProgram` 时,若本地存在手机号(`globalData.userInfo.phone` 或 `wx.getStorageSync('user_phone')`),则在 `path` 上追加 `phone=encodeURIComponent(手机号)`,兼容已有 `?` 查询串。参数名:`phone`。
+
+### 【测试人员】
+
+用例:① 已登录有手机号 → 跳转路径含 `phone`;② 未绑定手机号 → 跳转无 `phone` 或仍成功打开;③ `page_path` 已带 `?a=1` → 追加 `&phone=`;④ 真机从阅读页点 `#MBTI`(或配置标签)验证 MBTI 侧 `onLoad(options)` 能收到 `options.phone`。
+
+---
+
+## 会议决议
+
+1. **Soul 小程序阅读页**:跳转其他小程序(`navigateToMiniProgram`)时,若当前用户有手机号,则向 `path` 追加查询参数 `phone`。
+2. **MBTI 小程序**:在目标页面 `onLoad` 中读取 `options.phone`(需自行对接与合规,如仅用于展示/关联,勿明文日志)。
+3. **无需**后端与管理端接口变更;配置与现网「链接标签 + 关联小程序」一致。
+
+---
+
+## 待办事项
+
+| 责任角色 | 任务 | 截止 |
+|----------|------|------|
+| MBTI 小程序侧 | 目标页解析 `phone` 并完成业务闭环 | 待排期 |
+| 测试 | 真机联调验证带参跳转 | 发版前 |
+
+---
+
+## 问题与作答区
+
+| 问题 | 责任角色 | 作答 |
+|------|----------|------|
+| 参数名是否固定为 `phone`? | 小程序 | 已约定为 `phone`,与 MBTI 对齐即可 |
+| 是否只对 MBTI 传参? | 产品 | 当前实现为**所有**关联小程序跳转均带手机号(若本地有);若仅 MBTI 需传,可后续加配置开关 |
+
+---
+
+## 各角色经验与业务理解更新
+
+- **小程序**:`miniprogram/pages/read/read.js` 已实现 `appendQueryToPath` + `getLoggedInUserPhone`,见代码注释。
diff --git a/.cursor/meeting/2026-03-31_超级个体与@列表融合.md b/.cursor/meeting/2026-03-31_超级个体与@列表融合.md
new file mode 100644
index 0000000..1e2cfe9
--- /dev/null
+++ b/.cursor/meeting/2026-03-31_超级个体与@列表融合.md
@@ -0,0 +1,112 @@
+# 会议纪要 - 2026-03-31 | 超级个体列表与 @ 列表融合方案
+
+> 本文件由**助理橙子**在会议结束后生成。
+
+---
+
+## 基本信息
+
+- **时间**:2026-03-31(会议即时)
+- **议题**:将「超级个体列表」与「可 @ 的人物列表(@列表)」在管理与认知上融合,减少双入口维护、统一运营视角
+- **触发方式**:开个会 / 方案讨论
+- **参与角色**:产品经理、后端开发、管理端开发工程师、小程序开发工程师、测试人员
+
+---
+
+## 各角色发言
+
+### 【产品经理】
+
+当前存在两条线:**用户管理里的「超级个体列表」**(VIP 排序、获客统计、Webhook)与 **内容/人物侧的「可 @ 人物」**(Person、token、阅读页 mention)。业务上应是同一批「派对超级个体」:开通 VIP 且资料合规者既可上首页横滑,又应可被正文 @。融合的价值是:运营只维护一处心智——「谁是超级个体、是否已绑定 @ 人、排序是否生效」。验收上需明确:列表合并后仍能完成「调排序、看点击/获客、配飞书、删/建 @ 人」全流程,且不与「导师绑定用户」等场景混淆。
+
+### 【后端开发】
+
+数据上 **users(VIP 字段)** 与 **persons(@ 侧)** 已通过 user_id / 绑定关系关联;`/api/db/vip-members` 已聚合 click、lead、webhook。融合不必先造新表,优先 **聚合查询或扩展现有接口字段**(例如在 vip-members 响应中附带 `personId`、`hasMentionToken`、`inPool` 等,字段名与小程序 read-extras 对齐)。新路由若需要,仍挂在 **db 或 admin 组**,避免小程序直接访问 db。性能上列表 limit 已存在上限,批量补 persons 信息时注意 N+1,沿用现有 batch 模式。
+
+### 【管理端开发工程师】
+
+现状:**用户管理 → 超级个体列表** 与 **内容管理里的人物/@ 相关区块** 分属不同页面。融合路径可分档:**短期**在超级个体表格增加「@ 人物状态」列与跳转内容页锚点(或 Drawer 快捷查看 token);**中期**抽「超级个体中心」单页,左侧 VIP 列表右侧 Person 详情,或保留两页但统一数据源与面包屑。**禁止**在未统一接口前两处各写一套排序逻辑。组件上可复用 UsersPage 已有表格与 ContentPage 已有 person 卡片模式。
+
+### 【小程序开发工程师】
+
+C 端已分离:**首页** `/api/miniprogram/vip/members`,**阅读页 @** 来自 `read-extras` 的 mentionPersons。管理端融合 **不强制改小程序**;若后端在 vip/members 增加展示用字段,需评估包体与缓存。回归点:首页超级个体横滑、阅读页 @ 高亮与跳转 member-detail **行为不变**。
+
+### 【测试人员】
+
+回归清单:**VIP 排序**(管理端改 vip_sort → 首页顺序)、**@ 解析**(正文 @ 人物跳转与 inPool)、**绑定/解绑** 超级个体与 Person、**获客/Webhook** 推送。边界:无 Person 的 VIP、有 Person 但 VIP 过期的展示策略需与产品一致。
+
+---
+
+## 讨论过程
+
+- **【管理端开发工程师】回复【产品经理】**:完全合并成一个页面开发量较大,建议先「数据同源 + 界面互链」,再视反馈做单页。
+- **【后端开发】回复【管理端开发工程师】**:扩字段比新开「大一统」接口风险小;若要做统一 API,命名需与现有 `vip-members`、人物接口文档同步,避免管理端调两套。
+- **【小程序开发工程师】回复【后端开发】**:小程序侧保持 miniprogram 路由不变即可,融合主要是管理端与数据层体验。
+
+---
+
+## 会议决议
+
+1. **概念对齐**:以 **VIP 用户(超级个体)为主实体**,Person 为内容侧 @ 扩展;「融合」优先指 **管理端统一展示与互链**,而非强行合并数据库表。
+2. **实施顺序**:**第一阶段** — `vip-members`(或等价 admin/db 接口)增加与 @ 人物相关的 **只读关联字段**,超级个体列表展示绑定状态并提供 **跳转内容/人物管理** 的入口;**第二阶段** — 评估是否整合为单一「超级个体中心」页面或保留双入口强绑定数据源。
+3. **待确认项**:产品拍板「无 @ 人物的有效 VIP」在列表中的文案与是否强提示补全;是否需要在内容页反向高亮「对应 VIP 用户」。
+
+### 补充说明(业务方澄清)
+
+- **@ 列表本身已包含「超级个体」与人物的绑定关系**;目标融合不是再叠一层「超级个体绑定」,而是 **合并后不再单独存在「超级个体绑定」这一与 @ 并列的操作/心智**。
+- **统一口径**:以 **@ 人物 ↔ 用户** 为内容侧与运营侧的真实绑定;首页排序、获客、Webhook 等 **运营字段** 仍挂在用户维度展示,但 **「谁在内容里代表该超级个体」只认 @ 列表里的那条 Person 关联**,避免出现「两处各绑一次」的双轨维护。
+
+---
+
+## 待办事项
+
+| 责任角色 | 任务 | 优先级 | 截止建议 |
+|---------|------|--------|---------|
+| 产品经理 | 确认「融合」一期验收:必显字段、跳转路径、无 Person 时的运营提示文案 | 高 | 迭代规划前 |
+| 后端开发 | 评估并在 `vip-members`(或文档约定接口)增加 person 关联字段;注意批量查询性能 | 高 | 与前端联调前 |
+| 管理端开发工程师 | 超级个体列表增加 @ 状态列与跳转;与 ContentPage 人物区行为对齐 | 高 | 同上 |
+| 小程序开发工程师 | 接口不变则仅做回归;若字段下沉到 C 端再评估 | 中 | 联调时 |
+| 测试人员 | 按回归清单执行;记录边界用例 | 中 | 提测阶段 |
+
+---
+
+## 问题与作答区
+
+| # | 问题 | 责任角色 | 作答 |
+|---|------|---------|------|
+| 1 | 一期是否要求「仅互链」不合并页面? | 产品经理 | (待补充) |
+| 2 | `vip-members` 增加哪些字段名与 read-extras 完全一致? | 后端开发 | (待补充) |
+| 3 | 内容页是否增加「对应超级个体」反向入口? | 产品经理、管理端开发工程师 | (待补充) |
+
+---
+
+## 各角色经验与业务理解更新
+
+### 产品经理
+
+- 超级个体与 @ 人物是同一业务的两面:对外曝光(首页)与内容引用(@),融合先从运营视角统一,再考虑 UI 合并。
+
+### 后端开发
+
+- 列表融合优先 **扩展现有聚合接口**,避免小程序与管理端各维护一套「谁算超级个体」的规则。
+
+### 管理端开发工程师
+
+- 用户页超级个体 tab 与内容人物管理应 **共享数据源展示字段**,短期用跳转与列补齐降低重复配置。
+
+### 小程序开发工程师
+
+- 首页 VIP 列表与阅读 @ 数据源可继续分离;管理端融合对 C 端默认 **零改动**。
+
+### 测试人员
+
+- 融合类需求重点测 **排序、绑定状态、过期 VIP** 三类交叉场景。
+
+### 团队共享
+
+- 决议:**先数据与入口融合,再视需要做单页**;架构上保持 `/api/miniprogram/*` 与 `/api/db/*` 边界不变。
+- **会后修订**:合并后以 **@ 列表为绑定真源**,**取消独立的「超级个体绑定」概念**,避免与 @ 侧既有关系重复。
+
+---
+
+*会议纪要由助理橙子生成 | 各角色经验已同步至 `agent/{角色}/evolution/2026-03-31.md`*
diff --git a/.cursor/meeting/2026-04-02_工作进度与需求同步会.md b/.cursor/meeting/2026-04-02_工作进度与需求同步会.md
new file mode 100644
index 0000000..08652ee
--- /dev/null
+++ b/.cursor/meeting/2026-04-02_工作进度与需求同步会.md
@@ -0,0 +1,116 @@
+# 会议纪要 - 2026-04-02 | 工作进度与需求同步会
+
+> 本文件由**助理橙子**在会议结束后自动生成。
+
+---
+
+## 基本信息
+
+- **时间**:2026-04-02
+- **议题**:同步各端工作进度与需求口径,衔接 2026-03-31 以来待办
+- **触发方式**:开个会议,同步大家工作进度和需求
+- **参与角色**:产品经理、后端开发、管理端开发工程师、小程序开发工程师、测试人员、团队(乘风主持)
+
+---
+
+## 各角色发言
+
+### 【产品经理】
+
+- 自 [2026-03-31 超级个体与 @ 列表融合](2026-03-31_超级个体与@列表融合.md) 后,**一期验收口径**(UsersPage @ 状态展示、vip-members 与 Person 关联展示边界)仍需与业务方确认后写入需求汇总/运营与变更。
+- 《以界面定需求》仍为验收基准;若 4 月有新入口或列表合并,需补界面清单与优先级。
+- 建议各端在索引中标注「3/31 融合方案」相关项的**完成度**(未开始/开发中/已联调)。
+
+### 【后端开发】
+
+- 当前仓库工作区显示 **`soul-api/deploy/`**(Dockerfile、docker-compose.yml、README)有未提交修改,属**部署与运维文档**迭代,合并前应在目标环境或本地 compose 跑通并核对环境变量说明。
+- 3/31 纪要待续:**vip-members 聚合 Person 关联字段**优先于管理端表格增强;历史待办 **router 补齐、ossConfig 确认**仍建议按迁移清单闭环,避免预发与生产配置漂移。
+- 路由分组原则不变:小程序仅 `miniprogram`,管理端 `admin`/`db`,禁止混挂。
+
+### 【管理端开发工程师】
+
+- 3/31 待续:**UsersPage** 表格增加 @ 状态与跳转内容页依赖后端 vip-members(或等价接口)字段稳定后再收尾联调。
+- 若 deploy 文档更新涉及管理端访问地址或 CORS/网关,与后端对齐 README 中的示例 URL。
+
+### 【小程序开发工程师】
+
+- 融合方案约定:**C 端默认不改接口、以回归为主**;若 `app.js` 或全局 `app.request` 有变更,需同步走提现/登录/阅读主路径冒烟。
+- 文章详情、`#` 跳转关联小程序传 `phone` 等已落地能力纳入常规回归,避免大文件改动时漏测。
+
+### 【测试人员】
+
+- 3/31 会议已列回归关注点:**排序、@ 展示、绑定、Webhook 边界**;待后端与管理端联调就绪后执行一轮三端联调。
+- **部署变更**合并后建议增加:**compose 起服务、`/health`、核心下单/阅读** 的轻量 smoke,防止仅文档改动的 PR 遗漏镜像/端口问题。
+
+---
+
+## 讨论过程
+
+- **【乘风】**:衔接 2026-03-24 同步会与 2026-03-31 融合会,4 月初重点是把「融合方案待续」从纸面推进到**可测状态**,同时不阻塞部署侧改进。
+- **【后端开发】回复【产品经理】**:vip-members 扩展字段确定后,可在接口文档或 OpenAPI 注释中标明,便于产品写验收点。
+- **【测试人员】回复【后端开发】**:deploy 变更若影响数据库或 Redis 连接串示例,请在 README 标明「必填/可选」,减少环境误配。
+
+---
+
+## 会议决议
+
+1. **进度同步机制**:自 2026-04-02 起,各角色在实现跨 3/31 边界的改动时,**当日或次日**在对应 `项目索引/*.md` 追加一行进度;产品侧大块需求变更同步《需求汇总》或《运营与变更》。
+2. **部署变更**:`soul-api/deploy/*` 合并前完成 **compose(或等价)验证**,README 与 compose 中镜像名、端口、环境变量保持一致。
+3. **超级个体与 @ 列表融合**:执行顺序为 **后端 vip-members 聚合字段 → 管理端 UsersPage 联调 → 测试按 3/31 清单回归**;小程序按原约定以回归为主。
+4. **待确认项**:一期验收口径(@ 状态定义、列表合并后是否隐藏旧入口)由产品在本周内澄清并回写「问题与作答区」。
+
+---
+
+## 待办事项
+
+| 责任角色 | 任务 | 优先级 | 截止建议 |
+|---------|------|--------|---------|
+| 产品经理 | 澄清一期融合验收口径并更新需求/运营与变更 | 高 | 2026-04-09 前 |
+| 后端开发 | vip-members 聚合 Person 关联;deploy 改动验证后提交 | 高 | 按迭代节奏 |
+| 管理端开发工程师 | UsersPage @ 状态与跳转,待接口字段就绪后联调 | 中 | 接口就绪后 3 个工作日内 |
+| 小程序开发工程师 | 全局请求/登录相关改动后主路径冒烟 | 中 | 每次相关 PR 前 |
+| 测试人员 | 融合清单回归 + deploy 合并后 smoke | 中 | 联调就绪后 |
+
+---
+
+## 问题与作答区
+
+> 会议中提出的待确认问题在此列出;作答区域供后续补充答案,便于追溯闭环。
+
+| # | 问题 | 责任角色 | 作答 |
+|---|------|---------|------|
+| 1 | 超级个体与 @ 列表融合「一期」验收:UsersPage @ 状态的具体展示规则(文案/图标/空态)是否已定? | 产品经理 | (待补充) |
+| 2 | soul-api deploy 修改是否计划同步到预发/生产流水线(CI 变量、镜像仓库)?由谁 owner? | 后端开发 / 团队 | (待补充) |
+| 3 | router 补齐(users/rfm、journey-stats、shensheshou)与 ossConfig 当前环境是否已全部一致? | 后端开发 | (待补充) |
+
+---
+
+## 各角色经验与业务理解更新
+
+### 产品经理
+
+- 大方案(如列表融合)会后需**显式一期验收口径**,避免开发按技术合并做完后产品口径滞后。
+
+### 后端开发
+
+- **Deploy 与业务代码**同仓时,PR 应区分「纯部署」与「接口变更」,便于测试选 smoke 范围。
+
+### 管理端开发工程师
+
+- 表格增强依赖聚合接口时,先锁定**字段契约**再改列与跳转,减少联调往返。
+
+### 小程序开发工程师
+
+- 全局 `app.js` 改动影响面大,变更说明中写明**必测页面列表**。
+
+### 测试人员
+
+- 部署类 PR 默认增加 **compose + health + 主路径** 最小验证集。
+
+### 团队共享
+
+- **融合方案执行顺序**:后端数据与接口 → 管理端展示 → 小程序回归 → 全量回归清单;三端路由隔离不变。
+
+---
+
+*会议纪要由助理橙子生成 | 各角色经验已同步至 `agent/{角色}/evolution/2026-04-02.md`*
diff --git a/.cursor/meeting/2026-04-13_按功能同步开发文档.md b/.cursor/meeting/2026-04-13_按功能同步开发文档.md
new file mode 100644
index 0000000..262e0c6
--- /dev/null
+++ b/.cursor/meeting/2026-04-13_按功能同步开发文档.md
@@ -0,0 +1,108 @@
+# 会议纪要 - 2026-04-13 | 按功能同步开发文档
+
+> 本文件由**助理橙子**在会议结束后自动生成。
+
+---
+
+## 基本信息
+
+- **时间**:2026-04-13 10:00
+- **议题**:各角色依据本人负责的开发功能,同步开发文档(需求汇总、运营与变更、项目索引、临时需求池与接口契约说明)
+- **触发方式**:开个会 / 团队会议(文档同步专项)
+- **参与角色**:产品经理、后端开发、管理端开发工程师、小程序开发工程师、测试人员、乘风(主持)、助理橙子(书记)
+
+---
+
+## 各角色发言
+
+### 【产品经理】
+
+需求侧以 `开发文档/1、需求/` 主需求与《以界面定需求》为验收基准。各端每完成一块可交付功能,须在 **需求清单/验收口径** 中对应更新状态;跨端规则(如 VIP、提现、@ 人)变更必须写清业务规则与优先级,避免「代码已上、文档仍旧」。
+
+### 【后端开发】
+
+每新增或变更接口,须在实现合并前同步:`路由组(miniprogram/admin/db)`、路径、请求/响应关键字段、错误码约定;涉及表结构或迁移的,在 `开发文档/8、部署/` 或临时需求池技术分析中留痕。历史待办(如 router/ossConfig、vip-members 聚合)在文档中标注 **P0/P1** 与依赖方。
+
+### 【管理端开发工程师】
+
+管理端功能以页面/菜单为粒度,在 `开发文档/10、项目管理/运营与变更.md` 或需求汇总中追加「做了什么、调了哪些 `/api/admin/*` `/api/db/*`」;与小程序字段对齐处(列表、详情、配置项)写明字段来源,便于测试写用例。
+
+### 【小程序开发工程师】
+
+C 端以页面与核心流程为粒度同步:涉及 `/api/miniprogram/*` 的变更、scene、支付/登录分支、分享 singlePage 行为,须在开发文档或角色项目索引中可追溯;仅改 UI 也需注明「无接口变更」以便测试裁剪回归范围。
+
+### 【测试人员】
+
+文档同步后,测试以 **文档与索引为真源** 更新回归清单与联调顺序;三端契约变更若文档未更新,测试有权将项标为「阻塞—待文档」并在问题与作答区登记。
+
+---
+
+## 讨论过程
+
+- **【管理端开发工程师】回复【产品经理】**:界面定需求已存在,建议「功能完成」定义包含:代码合并 + 文档/索引一行摘要,避免遗漏。
+- **【后端开发】回复【小程序开发工程师】**:miniprogram 与 admin 路由隔离不变;文档中写清「仅供某端调用」可避免联调误用。
+- **【测试人员】回复【全员】**:deploy/health/smoke 与关键路径用例在 `开发文档/10、项目管理` 与测试索引中保持交叉引用,便于发版前自检。
+
+---
+
+## 会议决议
+
+1. **按功能粒度同步**:以「可验收功能点」为最小单位,责任角色在合并前或合并当日更新:对应 `开发文档` 章节、`.cursor/agent/开发助理/项目索引/{角色}.md` 开发进度表一行(**必须含日期**)。
+2. **跨端变更双写**:影响两端的字段或接口,除本角色文档外,须在 `agent/团队/evolution/` 或会议纪要中留架构/契约摘要,并在相关角色索引中各记一行。
+3. **待确认项进表**:接口路径、错误码、验收口径未定的,一律落入本次会议「问题与作答区」,由责任角色补充作答后闭环。
+
+---
+
+## 待办事项
+
+| 责任角色 | 任务 | 优先级 | 截止建议 |
+|---------|------|--------|---------|
+| 产品经理 | 核对主需求与《以界面定需求》与各端近期实现差异,更新需求汇总状态 | 高 | 2026-04-20 |
+| 后端开发 | 将 router/ossConfig、vip-members 等待办在文档中标注优先级与依赖 | 中 | 2026-04-20 |
+| 管理端开发工程师 | soul-admin 近期页面/接口变更写入运营与变更或索引 | 中 | 2026-04-20 |
+| 小程序开发工程师 | 阅读/支付/分享等主路径若有未记文档的改动,补索引与说明 | 中 | 2026-04-20 |
+| 测试人员 | 依据更新后的文档刷新融合与主路径回归清单 | 中 | 2026-04-20 |
+
+---
+
+## 问题与作答区
+
+> 会议中提出的待确认问题在此列出;作答区域供后续补充答案,便于追溯闭环。
+
+| # | 问题 | 责任角色 | 作答 |
+|---|------|---------|------|
+| 1 | 各端「最近一次合并到主分支」的功能清单是否与项目索引 2026-04-02 之后实际代码一致? | 各角色负责人 | (待补充) |
+| 2 | vip-members 聚合与超级个体/@ 融合一期验收口径是否已在需求文档中单点定稿? | 产品经理 | (待补充) |
+| 3 | DistributionPage `Order.description` 类型债是否纳入本迭代文档与修复排期? | 管理端开发工程师 | (待补充) |
+
+---
+
+## 各角色经验与业务理解更新
+
+### 产品经理
+
+- 文档同步与验收基准绑定:功能完成 = 实现 + 文档/索引可追溯。
+
+### 后端开发
+
+- 接口与迁移变更必须标明路由组与优先级,避免管理端/小程序误调。
+
+### 管理端开发工程师
+
+- 以页面为粒度记录 admin/db 依赖,字段与小程序对齐处显式写出。
+
+### 小程序开发工程师
+
+- 无接口变更的纯 UI 迭代也应在索引中标注,便于测试裁剪范围。
+
+### 测试人员
+
+- 以更新后的文档与索引为真源维护回归清单;文档缺失可阻塞验收。
+
+### 团队共享
+
+- 延续 2026-03-24 文档同步原则:实现变更后同步需求、运营与变更、各角色项目索引;跨端契约写入团队 evolution 或会议纪要。
+
+---
+
+*会议纪要由助理橙子生成 | 各角色经验已同步至 `agent/{角色}/evolution/2026-04-13.md`*
diff --git a/.cursor/meeting/README.md b/.cursor/meeting/README.md
new file mode 100644
index 0000000..aa1caef
--- /dev/null
+++ b/.cursor/meeting/README.md
@@ -0,0 +1,88 @@
+# Soul 创业派对 - 会议记录
+
+> 每次多角色会议后,由**橙子**生成独立会议纪要文件,按日期+主题命名。
+
+---
+
+## 命名规则
+
+```
+YYYY-MM-DD_会议主题.md
+```
+
+示例:
+- `2026-02-27_提现流程优化讨论.md`
+- `2026-02-27_分销功能需求评审.md`
+- `2026-03-01_会员分润方案讨论.md`
+
+---
+
+## 会议纪要结构
+
+每份会议纪要包含以下标准结构:
+
+```markdown
+# 会议纪要 - YYYY-MM-DD | 会议主题
+
+## 基本信息
+- **时间**:YYYY-MM-DD HH:mm
+- **议题**:xxx
+- **参与角色**:产品经理、后端开发、管理端开发工程师、小程序开发工程师、测试人员
+
+## 各角色发言
+### 【产品经理】...
+### 【后端开发】...
+### 【管理端开发工程师】...
+### 【小程序开发工程师】...
+### 【测试人员】...(验收/测试相关)
+
+## 讨论过程
+(关键讨论节点记录)
+
+## 会议决议
+(达成共识的结论,可直接用于开发)
+
+## 待办事项
+| 责任角色 | 任务 | 截止 |
+
+## 问题与作答区
+(待确认问题列表 + 作答列供后续补充,便于追溯闭环)
+
+## 各角色经验与业务理解更新
+### 产品经理 / 后端开发 / 管理端开发工程师 / 小程序开发工程师
+```
+
+---
+
+## 索引
+
+| 日期 | 主题 | 参与角色 | 文件 |
+|------|------|---------|------|
+| 2026-02-27 | 开发进度同步会议 | 产品、后端、管理端、小程序 | [2026-02-27_开发进度同步会议.md](2026-02-27_开发进度同步会议.md) |
+| 2026-02-28 | 临时需求池 stitch_soul 需求评审(含页面重构专项·10 张图全覆盖) | 产品、后端、管理端、小程序、测试 | [2026-02-28_临时需求池stitch_soul需求评审.md](2026-02-28_临时需求池stitch_soul需求评审.md) |
+| 2026-02-28 | 个人资料页实现评估(profile-show / profile-edit) | 产品、后端、管理端、小程序、测试 | [2026-02-28_个人资料页实现评估.md](2026-02-28_个人资料页实现评估.md) |
+| 2026-02-28 | 文章类型(普通版/增值版)需求分析 | 产品、后端、管理端、小程序、测试 | [2026-02-28_文章类型普通版增值版需求分析.md](2026-02-28_文章类型普通版增值版需求分析.md) |
+| 2026-03-05 | 分支冲突后功能完整性分析 | 产品、后端、管理端、小程序、测试 | [2026-03-05_分支冲突后功能完整性分析.md](2026-03-05_分支冲突后功能完整性分析.md) |
+| 2026-03-05 | 超级个体解锁眼睛需求分析 | 产品、小程序 | [2026-03-05_超级个体解锁眼睛需求分析.md](2026-03-05_超级个体解锁眼睛需求分析.md) |
+| 2026-03-05 | 文章详情 @某人 高亮与一键加好友方案讨论 | 产品、后端、管理端、小程序、测试 | [2026-03-05_文章详情@某人加好友方案讨论.md](2026-03-05_文章详情@某人加好友方案讨论.md) |
+| 2026-03-09 | 代码完整性分析与分支合并准备 | 产品、后端、管理端、小程序、测试 | [2026-03-09_代码完整性分析与分支合并准备.md](2026-03-09_代码完整性分析与分支合并准备.md) |
+| 2026-03-09 | devlop 与 yongxu 分支差异分析 | 产品、后端、管理端、小程序、测试 | [2026-03-09_devlop与yongxu分支差异分析会议.md](2026-03-09_devlop与yongxu分支差异分析会议.md) |
+| 2026-03-09 | dev 分支需求分析与 yongxu 迁移方案 | 产品、后端、管理端、小程序 | [2026-03-09_dev分支需求分析与yongxu迁移方案.md](2026-03-09_dev分支需求分析与yongxu迁移方案.md) |
+| 2026-03-10 | 管理端迁移 Mycontent-temp 菜单/布局讨论 | 产品、后端、管理端、小程序、测试 | [2026-03-10_管理端迁移Mycontent-temp菜单布局讨论.md](2026-03-10_管理端迁移Mycontent-temp菜单布局讨论.md) |
+| 2026-03-10 | 小程序新旧版对比分析与 dashboard-stats 接口新增 | 小程序、后端、团队 | [2026-03-10_小程序新旧版对比与dashboard接口新增.md](2026-03-10_小程序新旧版对比与dashboard接口新增.md) |
+| 2026-03-10 | 文章详情三端功能对齐与开发(@mention/#linkTag/图片) | 产品、后端、管理端、小程序 | [2026-03-10_文章详情三端功能对齐与开发.md](2026-03-10_文章详情三端功能对齐与开发.md) |
+| 2026-03-10 | Toast 通知系统全局落地 & hot_score 数据库迁移 | 管理端、后端、团队 | [2026-03-10_Toast通知系统全局落地.md](2026-03-10_Toast通知系统全局落地.md) |
+| 2026-03-11 | 开发团队对齐业务逻辑与以界面定需求·会议收尾 | 产品、后端、管理端、小程序、团队 | [2026-03-11_开发团队对齐业务逻辑与以界面定需求会议收尾.md](2026-03-11_开发团队对齐业务逻辑与以界面定需求会议收尾.md) |
+| 2026-03-16 | 链接人与事与存客宝对接优化 | 管理端、后端、团队 | [2026-03-16_链接人与事与存客宝对接优化.md](2026-03-16_链接人与事与存客宝对接优化.md) |
+| 2026-03-16 | new-soul 新需求与当前项目差异分析 | 产品、后端、管理端、小程序、测试 | [2026-03-16_new-soul新需求与当前项目差异分析.md](2026-03-16_new-soul新需求与当前项目差异分析.md) |
+| 2026-03-17 | 新版管理端迁移到稳定版实施方案确认 | 产品、后端、管理端、小程序、测试 | [2026-03-17_新版管理端迁移到稳定版实施方案确认.md](2026-03-17_新版管理端迁移到稳定版实施方案确认.md) |
+| 2026-03-17 | 稳定版源码质量优化方案讨论与开发安排 | 产品、后端、管理端、小程序、测试 | [2026-03-17_稳定版源码质量优化方案讨论与开发安排.md](2026-03-17_稳定版源码质量优化方案讨论与开发安排.md) |
+| 2026-03-17 | 会议收尾:源码优化完成与测试流程定稿 | 产品、后端、管理端、小程序、测试、助理橙子 | [2026-03-17_会议收尾-源码优化完成与测试流程定稿.md](2026-03-17_会议收尾-源码优化完成与测试流程定稿.md) |
+| 2026-03-17 | 性能优化与 Redis 缓存方案落地 | 后端、管理端、小程序、测试、助理橙子 | [2026-03-17_性能优化与Redis缓存方案落地.md](2026-03-17_性能优化与Redis缓存方案落地.md) |
+| 2026-03-18 | 超级个体开通后自动创建@人与资料引导 | 产品、后端、管理端、小程序、测试 | [2026-03-18_超级个体开通后自动创建@人与资料引导.md](2026-03-18_超级个体开通后自动创建@人与资料引导.md) |
+| 2026-03-24 | 开发进度同步会议(查看代码与开发文档对齐) | 产品、后端、管理端、小程序、测试 | [2026-03-24_开发进度同步会议.md](2026-03-24_开发进度同步会议.md) |
+| 2026-03-24 | 需求与进度及三端闭环评审(业务闭环、核心功能、小程序与管理端配套) | 产品、后端、管理端、小程序、测试 | [2026-03-24_需求与进度及三端闭环评审.md](2026-03-24_需求与进度及三端闭环评审.md) |
+| 2026-03-30 | 文章详情 # 标签跳转 MBTI 小程序传手机号 | 产品、后端、管理端、小程序、测试 | [2026-03-30_文章详情跳转MBTI小程序传手机号.md](2026-03-30_文章详情跳转MBTI小程序传手机号.md) |
+| 2026-03-31 | 超级个体列表与 @ 列表融合方案 | 产品、后端、管理端、小程序、测试、团队 | [2026-03-31_超级个体与@列表融合.md](2026-03-31_超级个体与@列表融合.md) |
+| 2026-04-02 | 工作进度与需求同步会 | 产品、后端、管理端、小程序、测试、团队 | [2026-04-02_工作进度与需求同步会.md](2026-04-02_工作进度与需求同步会.md) |
+| 2026-04-13 | 按功能同步开发文档 | 产品、后端、管理端、小程序、测试、团队 | [2026-04-13_按功能同步开发文档.md](2026-04-13_按功能同步开发文档.md) |
diff --git a/.cursor/meeting/_模板.md b/.cursor/meeting/_模板.md
new file mode 100644
index 0000000..0426c8f
--- /dev/null
+++ b/.cursor/meeting/_模板.md
@@ -0,0 +1,111 @@
+# 会议纪要 - YYYY-MM-DD | {会议主题}
+
+> 本文件由**助理橙子**在会议结束后自动生成。
+
+---
+
+## 基本信息
+
+- **时间**:YYYY-MM-DD HH:mm
+- **议题**:{从用户消息提取}
+- **触发方式**:{语义化触发词}
+- **参与角色**:{根据议题自动判断:产品经理、后端开发、管理端开发工程师、小程序开发工程师、测试人员}
+
+---
+
+## 各角色发言
+
+> 各角色从自身专业视角对议题发表意见。
+
+### 【产品经理】
+
+(从需求、用户价值、业务规则、验收标准角度发言)
+
+### 【后端开发】
+
+(从接口设计、路由组归属、数据模型、技术可行性角度发言)
+
+### 【管理端开发工程师】
+
+(从管理端功能需求、审核/配置/统计、接口依赖角度发言)
+
+### 【小程序开发工程师】
+
+(从 C 端用户体验、交互流程、接口依赖角度发言)
+
+### 【测试人员】(验收/测试相关会议时)
+
+(从测试用例、三端联调、回归清单、已知风险角度发言)
+
+---
+
+## 讨论过程
+
+(关键交流节点,角色间的问答和碰撞)
+
+---
+
+## 会议决议
+
+> 所有角色达成共识的结论,可直接指导开发。
+
+1. **{决议点1}**:{说明}
+2. **{决议点2}**:{说明}
+3. **待确认项**:{需进一步调研或确认的内容}
+
+---
+
+## 待办事项
+
+| 责任角色 | 任务 | 优先级 | 截止建议 |
+|---------|------|--------|---------|
+| 后端开发 | {任务} | 高/中/低 | {日期} |
+| 管理端开发工程师 | {任务} | 高/中/低 | {日期} |
+| 小程序开发工程师 | {任务} | 高/中/低 | {日期} |
+| 产品经理 | {任务} | 高/中/低 | {日期} |
+| 测试人员 | {任务} | 高/中/低 | {日期} |
+
+---
+
+## 问题与作答区
+
+> 会议中提出的待确认问题在此列出;作答区域供后续补充答案,便于追溯闭环。
+
+| # | 问题 | 责任角色 | 作答 |
+|---|------|---------|------|
+| 1 | {待确认问题1} | {谁负责回答} | (待补充) |
+| 2 | {待确认问题2} | {谁负责回答} | (待补充) |
+
+---
+
+## 各角色经验与业务理解更新
+
+> 本次会议结束后,各角色基于讨论结果生成的经验,已同步写入各自的日期经验文件。
+
+### 产品经理
+
+- {从本次会议提炼的业务理解}
+
+### 后端开发
+
+- {从本次会议提炼的后端相关经验}
+
+### 管理端开发工程师
+
+- {从本次会议提炼的管理端相关经验}
+
+### 小程序开发工程师
+
+- {从本次会议提炼的小程序相关经验}
+
+### 测试人员
+
+- {从本次会议提炼的测试相关经验}
+
+### 团队共享
+
+- {跨角色共享的架构决策或业务规则}
+
+---
+
+*会议纪要由助理橙子生成 | 各角色经验已同步至 `agent/{角色}/evolution/YYYY-MM-DD.md`*
diff --git a/.cursor/plans/vip-members-person-文章绑定超级个体.md b/.cursor/plans/vip-members-person-文章绑定超级个体.md
new file mode 100644
index 0000000..e8f0764
--- /dev/null
+++ b/.cursor/plans/vip-members-person-文章绑定超级个体.md
@@ -0,0 +1,26 @@
+---
+name: vip-members Person 聚合 + 文章绑定超级个体
+overview: 见 Cursor 计划「vip-members_person_聚合」;含文章绑定超级个体与白名单 Webhook。执行前以本文件与 Cursor plan 同步为准。
+---
+
+完整内容与 todos 见 IDE 内计划:`vip-members_person_聚合_2b9cac41.plan.md`(或从命令面板打开 Current Plan)。
+
+要点摘要:
+
+1. **原有三项**:vip-members 聚合 Person、UsersPage @列、ContentPage `?tab=link-person`。
+2. **新增**:`chapters.bound_super_user_id`;管理端章节绑定超级个体;读文接口返回绑定信息。
+3. **Webhook**:仅**白名单**埋点(分享/朋友圈/购买/单页解锁引导 + 补 `view_chapter`);`MiniprogramTrackPost` 后推送,使用 `loadLeadWebhookURL`,**独立去重**勿与获客日去重混用。
+4. **留资**:`ckb/lead` 按章节绑定合并 `TargetMemberID`,新增来源文案。
+
+用户已选推送策略:**白名单关键事件**(非全量节流、非定时摘要)。
+
+---
+
+## 补充:找伙伴(终身 1 次免费 + ¥9.9 划线 / ¥1)
+
+- **规则与 UI**:后端 `match.go` 与小程序 `match` 页已基本实现「终身 1 次免费、不按天重置」及划线原价 + 现价展示。
+- **支付对齐**:`getStandardPrice(match)` 当前误读 `chapter_config` 且兜底 **¥68**,需改为读 **`match_config.matchPrice`**、兜底 **¥1**(详见 Cursor 主计划文末章节与 todo `match-quota-pay-align`)。
+
+- **#标签·内部跳转**:与现有 `type=internal`(管理端现名「本小程序页面」)为同一能力;执行时可将文案统一为「内部跳转」(todo `linktag-internal-label`)。
+
+- **行为/Webhook 中文化**:飞书「最近行为」里英文 action、英文 module、技术 target 串,需在 `user.go` 补映射与人话化 target(todo `track-webhook-cn-labels`)。
diff --git a/.cursor/process/README.md b/.cursor/process/README.md
new file mode 100644
index 0000000..352245b
--- /dev/null
+++ b/.cursor/process/README.md
@@ -0,0 +1,3 @@
+# 工作流
+
+存放项目工作流、流程图等文档。
diff --git a/.cursor/rules/assistant-xiaofeng.mdc b/.cursor/rules/assistant-xiaofeng.mdc
new file mode 100644
index 0000000..ea3a39e
--- /dev/null
+++ b/.cursor/rules/assistant-xiaofeng.mdc
@@ -0,0 +1,16 @@
+---
+description: 小橙/橙子/橙橙/🍊 - 文档同步与经验升级助理
+globs: ["**"]
+alwaysApply: false
+---
+
+# 小橙触发器
+
+当用户提及**小橙、橙子、橙橙、🍊**,或说**「讨论完毕」「记录一下」「同步到开发文档」「更新文档」「吸收经验」「升级 skills」「记录经验」「保存开发进度」「更新项目索引」「记录开发进度」「任务完成」「搞定了」「完成了」「会议结束」「散会」「会开完了」**时:
+
+**必须使用 Read 工具读取 `.cursor/skills/assistant-doc-sync/SKILL.md` 的完整内容**,然后严格按其流程执行。
+
+### 行为摘要(供模型快速理解,完整流程以 SKILL 文件为准)
+
+1. **文档同步**:从对话中提炼结论/待办/变更 → 写入 `开发文档/1、需求/需求汇总.md`、`开发文档/10、项目管理/运营与变更.md`、`临时需求池/` 等对应文档
+2. **经验入库**:提炼经验 → 写入 `agent/{角色}/evolution/YYYY-MM-DD.md` → 更新 `agent/开发助理/项目索引/{索引名}.md`(写日期)→ 更新 `agent/开发助理/经验清单.md` → 升级对应 SKILL
diff --git a/.cursor/rules/party-ai-dev.mdc b/.cursor/rules/party-ai-dev.mdc
new file mode 100644
index 0000000..111c12c
--- /dev/null
+++ b/.cursor/rules/party-ai-dev.mdc
@@ -0,0 +1,63 @@
+# 派对 AI 开发规则
+
+## 与本仓库 `.cursor` 的优先级(避免入口打架)
+
+- **三端编码与接口**:以本仓库 `.cursor/rules`(如 `soul-project-boundary.mdc`)与 `.cursor/skills` 为准——小程序/管理端/soul-api 路由隔离、变更检查、角色 Skill 加载等**不因派对 AI 而绕过**。
+- **派对 AI 目录**:若仓库根下存在 `派对AI/`,开发前可补充读取其 `BOOTSTRAP.md`、`SKILL_REGISTRY.md`,用于派对域身份、运营与流程;与 `.cursor` 冲突时,**代码与 API 约定以 `.cursor` 为准**。
+- **卡若 AI 全局规则**:仅在本会话需要时参考;本仓库会话自检仍以 **本仓库 `.cursor/`** 为主。
+
+## 强制:使用派对 AI 进行开发(当 `派对AI/` 存在时)
+
+所有开发操作**优先结合派对 AI**(路径:**仓库根下的** `派对AI/`,与 `miniprogram/` 同级)。若本仓库无 `派对AI/` 目录,则跳过本节启动顺序,仅按 `.cursor` 执行。
+
+### 启动顺序
+
+1. 读取 `派对AI/BOOTSTRAP.md` — 了解项目 AI 身份与团队
+2. 读取 `派对AI/SKILL_REGISTRY.md` — 查找对应技能
+3. 按匹配的 SKILL.md 执行
+
+### 开发复盘推送飞书群
+
+每次开发完成后,**必须将复盘内容推送到 Soul 创业派对开发资料群**:
+
+```
+飞书 Webhook: https://open.feishu.cn/open-apis/bot/v2/hook/c558df98-e13a-419f-a3c0-7e428d15f494
+```
+
+推送格式:
+
+```json
+{
+ "msg_type": "text",
+ "content": {
+ "text": "[开发复盘] 日期时间\n\n🎯 目标:...\n📌 结果:...\n💡 关键判断:...\n📝 遗留:...\n▶ 下一步:..."
+ }
+}
+```
+
+### 需求必须 100% 完成(铁律)
+
+**禁止**:说「留到下一轮迭代」「后续再处理」。
+收到需求文档后,**全量开发**到每一项功能都落地为止。
+若单个功能涉及跨端改动(后端 + 管理端 + 小程序),必须三端同步改完。
+仅当功能在代码层面**无法实现**(如依赖第三方审核、硬件条件不具备)时,才可标注「依赖外部」并说明原因。
+
+### 复盘格式
+
+使用卡若 AI 标准复盘格式(🎯📌💡📝▶ 五块齐全),带日期+时间。
+开发完成后**必须**立即发送飞书复盘(不需要用户提醒)。
+
+### 小程序上传约定(强制)
+
+- **版本号固定 `1.2.6`**:每次上传小程序(soul-party / 派对AI)统一使用版本 `1.2.6`,不递增。
+- **上传后设为体验版**:上传完成后自动设为体验版,方便扫码测试。
+- **不自动提交审核**:上传后默认不提交审核。只有用户明确说「上传审核」「提交审核」时,才执行审核提交。
+- **上传命令**:
+ ```bash
+ # 上传 + 体验版(默认,不提审)
+ python 开发文档/小程序管理/scripts/mp_deploy.py deploy soul-party --skip-cert
+ # 上传 + 体验版 + 提交审核(用户明确要求时)
+ python 开发文档/小程序管理/scripts/mp_deploy.py deploy soul-party --skip-cert --submit
+ # 单独提交审核(已上传后)
+ python 开发文档/小程序管理/scripts/mp_deploy.py submit soul-party
+ ```
diff --git a/.cursor/rules/product-manager.mdc b/.cursor/rules/product-manager.mdc
new file mode 100644
index 0000000..a867bd5
--- /dev/null
+++ b/.cursor/rules/product-manager.mdc
@@ -0,0 +1,11 @@
+---
+description: 产品经理需求与验收。编辑需求文档时加载 SKILL-产品经理
+globs: ["开发文档/1、需求/**", "临时需求池/**", "开发文档/10、项目管理/**"]
+alwaysApply: false
+---
+
+# 产品经理
+
+当编辑 **开发文档/1、需求/**、**临时需求池/**、**开发文档/10、项目管理/** 时,推断当前角色为**产品经理**。
+
+**必须使用 Read 工具读取 `.cursor/skills/product-manager/SKILL.md` 的完整内容**,然后按其规范执行需求分析、文档编写、验收标准制定。
diff --git a/.cursor/rules/soul-admin-boundary.mdc b/.cursor/rules/soul-admin-boundary.mdc
new file mode 100644
index 0000000..7c51785
--- /dev/null
+++ b/.cursor/rules/soul-admin-boundary.mdc
@@ -0,0 +1,33 @@
+---
+description: 管理端边界约束,防止与小程序/API 路径互窜
+globs: soul-admin/**/*
+alwaysApply: false
+---
+
+# 管理端开发边界(防互窜)
+
+在 **soul-admin/** 下新增、优化或编辑任何代码时,必须遵守以下约束:
+
+## API 路径(强制)
+
+- **允许**:仅使用 soul-api 中面向管理端的路径,例如:
+ - `/api/admin`、`/api/admin/logout`、`/api/admin/withdrawals`、`/api/admin/chapters`、`/api/admin/content`、`/api/admin/settings` 等;
+ - `/api/db/users`、`/api/db/config/full`、`/api/db/chapters` 等;
+ - `/api/orders` 等与现网一致的管理端接口。
+- **禁止**:
+ - 不得调用 `/api/miniprogram/*`(小程序专属,如 miniprogram/login、miniprogram/book、miniprogram/withdraw 等)。
+ - 不得在管理端实现「使用小程序登录或小程序 token」的业务逻辑。
+- **请求方式**:统一通过 `src/api/client.ts` 的 `get`、`post`、`put`、`del`、`request`;鉴权使用 `src/api/auth.ts` 的 admin_token(Bearer)。
+
+## 目录与职责
+
+- 仅修改 **soul-admin/** 内文件(含 src/pages、src/components、src/api、src/layouts 等)。
+- 不在此处实现小程序逻辑;不在此处编写 soul-api 的 Go 代码或 miniprogram 的 WXML/WXSS/JS。
+
+## Skill 加载(必须执行)
+
+**必须使用 Read 工具读取 `.cursor/skills/admin-dev/SKILL.md` 的完整内容**,按其规范进行开发。该 Skill 包含代码风格、业务逻辑、API 对接细节等完整约定。
+
+接口实现与路由分组的规范在 `.cursor/rules/soul-api.mdc`(编辑 soul-api 时自动加载)。
+
+违反上述路径或职责边界即视为「互窜」,需纠正后再提交。
diff --git a/.cursor/rules/soul-api.mdc b/.cursor/rules/soul-api.mdc
new file mode 100644
index 0000000..868d4a8
--- /dev/null
+++ b/.cursor/rules/soul-api.mdc
@@ -0,0 +1,75 @@
+---
+description: soul-api 路由边界 + 编码规范(合并版,防互窜 + GORM/Gin/响应约定)
+globs: soul-api/**/*
+alwaysApply: false
+---
+
+# soul-api 开发规范
+
+> **Skill 加载**:编辑 soul-api 代码时,**必须使用 Read 工具读取 `.cursor/skills/api-dev/SKILL.md` 的完整内容**,该 Skill 包含业务对接、与前端边界协同等补充约定。本规则侧重编码规范与路由边界。
+
+## 1. 路由按使用方归类(强制)
+
+新增或修改接口必须**先明确使用方**,再挂到对应路由组:
+
+| 使用方 | 路由组 | 路径前缀 | 鉴权 |
+|--------|--------|----------|------|
+| 管理端 | `admin` / `db` | `/api/admin/…`、`/api/db/…` | `middleware.AdminAuth()` |
+| 小程序 | `miniprogram` | `/api/miniprogram/…` | 按接口需要 |
+| 两端共用 | `api` 下 + `miniprogram` 下各挂一遍 | `/api/xxx` 与 `/api/miniprogram/xxx` | 各自鉴权 |
+
+即使业务逻辑相同,也必须按使用方做路径区分。禁止仅提供 `/api/vip/*` 等通用路径让两端混用。
+
+### 禁止行为
+
+- 禁止在 `miniprogram` 组挂仅管理端调用的接口(后台审核、DB 初始化等)。
+- 禁止在 `admin`/`db` 组挂小程序专属逻辑(wx code 登录、小程序码生成等)。
+- 禁止在 handler 内混用管理端/小程序路径语义(根据 path 分支写两套业务而不按使用方拆 handler/路由)。
+
+handler 注释标明使用方,如 `// GET /api/miniprogram/withdraw/records 小程序-提现记录`。
+
+管理端列表接口需包含:`user_name`/`userNickname`、`userAvatar`、`status`、`amount`。提现状态 DB 存 `pending`/`processing`/`success`/`failed`。
+
+## 2. 数据访问:优先 GORM
+
+- 通过 `database.DB()` 获取 `*gorm.DB`,操作集中在 `internal/model` 的模型上。
+- 常规 CRUD 必须用链式 API(`Where/First/Find/Create/Save/Updates/Delete`)。
+- 原子更新用 `gorm.Expr`,如 `Update("pending_earnings", gorm.Expr("pending_earnings + ?", delta))`。
+- 多表写入必须用 `db.Transaction(func(tx *gorm.DB) error { ... })`。
+- 用 `Preload`/`Joins` 减少 N+1;仅需单列时用 `Pluck`;重复条件抽 Scopes。
+- 禁止 handler 中手写 `db.Exec/db.Raw`,除非:复杂统计 SQL 用 GORM 表达冗长(须加注释);原子多列 SET。
+
+## 3. Model 与表结构
+
+- 结构体放 `internal/model`,文件名与业务一致。
+- 必须包含 `gorm` 标签(column/primaryKey/type)+ `json` 标签(小写驼峰)。
+- 不对外暴露的字段用 `json:"-"`。
+- 实现 `TableName() string`(若表名与默认不一致)。
+
+## 4. 依赖物尽其用
+
+- **Gin**:入参 `c.ShouldBindJSON` + `binding` 标签校验;统一 `c.JSON` 返回。
+- **配置**:仅通过 `internal/config` 的 `config.Load()` 读环境变量,不直接 `os.Getenv`。
+- **中间件**:安全头 `middleware.Secure()`,跨域 `cors`,限流 `middleware.NewRateLimiter`。
+- **微信/支付**:统一走 `internal/wechat`(PowerWeChat),handler 只做参数与结果转换。
+- **JWT**:管理端鉴权用 `internal/auth` 的 `IssueAdminJWT`/`ParseAdminJWT`/`GetAdminJWTFromRequest`。
+
+## 5. 目录与包约定
+
+- `cmd/server/main.go`:入口,只做初始化与启停。
+- `internal/handler`:HTTP 处理函数,绑定→校验→调 DB/wechat→写响应。逻辑复杂时抽到 `internal/service`。
+- `internal/router`:注册路由与中间件,不写业务逻辑。
+- `internal/database`:仅提供 `Init(dsn)` 与 `DB()`。
+- 新增接口流程:确定使用方 → 确定路由 Group → 实现 handler + GORM model。
+
+## 6. 响应与错误
+
+- 成功:`gin.H{"success": true, "data": ...}` 或 `"message": "..."`。
+- 失败:`gin.H{"success": false, "error": "..."}`。
+- 不吞错误:DB/wechat 的 `err` 必须处理并返回。
+- HTTP 状态码:业务错误可用 200 + `success: false`;未授权/禁止用 401/403。
+
+## 7. 代码风格
+
+- 遵循 `gofmt`;导出函数 PascalCase,内部 camelCase。
+- 公开 handler 或复杂逻辑处写清用途注释。
diff --git a/.cursor/rules/soul-change-checklist.mdc b/.cursor/rules/soul-change-checklist.mdc
new file mode 100644
index 0000000..4d55b4f
--- /dev/null
+++ b/.cursor/rules/soul-change-checklist.mdc
@@ -0,0 +1,45 @@
+---
+description: 变更时关联层检查清单,防止漏改(前端/后端/管理端/表结构)
+globs: ["miniprogram/**/*", "soul-admin/**/*", "soul-api/**/*"]
+alwaysApply: false
+---
+
+# Soul 创业派对 - 变更关联检查清单(防漏改)
+
+在 **miniprogram/**、**soul-admin/** 或 **soul-api/** 下做任何**修改、优化、新增**后,必须按下列项过一遍,确认关联层已同步,避免只改一端导致数据不一致或功能缺管理入口。
+
+## 零漏改原则(强制)
+
+- 任一端出现**新功能/新字段/新文案/新状态/新交互**,必须联想另外两端是否需要补齐。
+- 验收至少满足四项闭环:**DB 可存 + API 可读写 + 管理端可配置/可管理 + 小程序可渲染/可使用**。
+- 禁止只在小程序硬编码可运营内容(如角标、标签、按钮文案);默认应有管理端入口或配置来源。
+
+## 一、按「你改了什么」对表检查
+
+| 你改的是… | 必须同时检查/修改的关联 |
+|-----------|--------------------------|
+| **前端(小程序或管理端)** 新增/改了**字段**或**接口入参/出参** | soul-api 对应接口的 request/response、model 是否已改?数据库表是否有对应列(无则加迁移/字段)? |
+| **小程序** 新增或改了一个**功能**(页面、能力、配置项) | soul-api 是否已有或需新增接口(挂到 `/api/miniprogram/...`)?**管理端**是否需要对应的**配置、审核、统计、列表**? |
+| **管理端** 新增或改了**列表/表单/配置项** | soul-api 的 admin/db 接口是否已提供对应数据或写接口?字段名与类型是否与前端一致? |
+| **soul-api** 新增/改了**接口**(路径、请求体、响应体、model) | 小程序或管理端是否有**调用处**?类型/字段是否已同步更新?若改了表结构,迁移是否已加?**路径是否按使用方区分**(小程序用 `/api/miniprogram/*`,管理端用 `/api/admin/*` 或 `/api/db/*`,禁止通用路径混用)? |
+| **soul-api** 新增/改了**表或字段** | 相关 handler、model 是否已改?是否有接口暴露给小程序/管理端?若有,前端是否已对接? |
+
+## 二、按「业务功能」想三端
+
+以**功能/领域**为单位(如:提现、推荐、章节权限、找伙伴、配置项),问一句:
+
+- **小程序**:用户侧是否已实现/已更新?
+- **soul-api**:接口是否在正确路由组(miniprogram / admin / db)、请求响应是否一致?若两端共用,是否显式挂到 miniprogram 组(`/api/miniprogram/xxx`),禁止仅提供 `/api/xxx` 混用?
+- **管理端**:该功能是否需要**配置、审核、统计、列表**?有则需在 soul-admin 与 soul-api 的 admin/db 下补齐。
+
+## 三、执行约定
+
+- **每次**在 miniprogram、soul-admin、soul-api 内完成一轮修改后,**先过一遍上表 + 二**,再视为本次变更完成。
+- 若本次变更涉及多端(例如小程序新功能 + 管理端配置页),应在同一次任务内一并完成或明确记录未做项,避免漏改。
+- 更详细的检查流程:**必须使用 Read 工具读取 `.cursor/skills/change-checklist/SKILL.md` 的完整内容**(相对本仓库根),按其「以领域为单位思考」的方法逐项确认。
+
+## 四、聊天中触发变更检查
+
+编码完成后在聊天中说**「变更完成」「检查一下」「准备提交」**,AI 会主动加载本清单 + change-checklist/SKILL.md 完成核对。**不需要正在编辑文件,直接说触发词即可。**
+
+未通过上述检查即提交视为可能漏改,需补全后再提交。
diff --git a/.cursor/rules/soul-karuo-dialogue.mdc b/.cursor/rules/soul-karuo-dialogue.mdc
new file mode 100644
index 0000000..ea410a5
--- /dev/null
+++ b/.cursor/rules/soul-karuo-dialogue.mdc
@@ -0,0 +1,22 @@
+---
+description: Soul 仓库内对话与卡若 AI 复盘格式对齐(alwaysApply)
+globs: ["**"]
+alwaysApply: true
+---
+
+# Soul 项目 · 卡若 AI 对话约定
+
+> 与 `soul-project-boundary.mdc` 中「卡若 AI 对话规范」一致;本文件只强调**对话形态**,不重复三端技术边界。
+
+## 必须遵守
+
+0. **默认零提问**:派对/Soul 相关开发、运维、脚本、填表链路,**禁止**反问用户「是否执行」「要不要」。缺信息则读本仓库代码与配置、用合理默认推进;**仅**验证码/密钥缺失/不可逆删除等无法代劳时,用**一句**说明缺什么。
+1. **语言**:面向用户的说明、结论、按钮文案解释等,默认 **简体中文**。
+2. **收尾**:每一轮对用户可见的助手回复,**最后一段**必须是完整 **[卡若复盘](YYYY-MM-DD HH:mm)** 块,含五段:**🎯 目标·结果·达成率**(**单行一句 ≤50 字**,句内含达成率 **%**、**可负**;分发任务按视频号等**实际成功÷计划**计;**禁止** ➡️/📊 复述行与标准 ☯)·**📌 过程** · **💡 反思** · **📝 总结** · **▶ 下一步执行**。复盘块内**禁止表格**。细则以卡若 `运营中枢/参考资料/卡若复盘格式_固定规则.md` **v5.0** 为准。
+3. **格式源**:与卡若 AI 仓库内 `运营中枢/参考资料/卡若复盘格式_固定规则.md` 保持同一种写法;若当前工作区已挂载「卡若AI」目录,修改复盘规则时以该文件为唯一真源。
+4. **需求节奏**:仍服从「需求即执行」——先「好」再改代码再报结果;复盘块放在**全条回复最末**。
+5. **直接执行**:用户说「直接做、别讲写了什么」时,**正文极短**;**复盘五块不可省**,可压缩过程为 1~2 条要点。
+
+## 与卡若中枢的衔接
+
+- Token/API 费用:助手侧通常无 Cursor 账单接口,在 💡 中说明「请用户在 Cursor Usage 自查」即可。
diff --git a/.cursor/rules/soul-meeting.mdc b/.cursor/rules/soul-meeting.mdc
new file mode 100644
index 0000000..775388c
--- /dev/null
+++ b/.cursor/rules/soul-meeting.mdc
@@ -0,0 +1,32 @@
+---
+description: Soul 创业派对开发团队多角色会议触发器。开个会、团队会议、需求评审、方案讨论时加载 SKILL-团队会议
+globs: ["**"]
+alwaysApply: true
+---
+
+# Soul 创业派对 - 会议触发器
+
+当用户表达**开会意图**时(包括但不限于以下触发词),**必须使用 Read 工具读取 `.cursor/skills/team-meeting/SKILL.md` 的完整内容**(相对本仓库根),然后严格按其流程主持会议。
+
+## 语义化触发词(理解意图,不必完全匹配)
+
+| 触发词示例 | 类型 |
+|-----------|------|
+| 开发团队和产品经理现在开始开会 | 全员会议 |
+| 开个会 / 开会讨论 / 我们开个会 | 快速会议 |
+| 团队会议 / 多角色会议 / 开发会议 | 正式会议 |
+| 需求评审 / 技术评审 / 方案讨论 | 专项会议 |
+| 大家一起讨论 / 召集开会 / 叫大家过来 | 集体讨论 |
+| 多角色讨论 / 各角色发言 | 多视角讨论 |
+
+## 会议结束触发
+
+当用户说**「会议结束」「散会」「会开完了」「结束会议」**时:
+
+1. 助理橙子立即执行收尾流程
+2. **生成会议纪要**:`.cursor/meeting/YYYY-MM-DD_主题.md`
+3. **各角色经验入库**:`.cursor/agent/{角色}/evolution/YYYY-MM-DD.md`
+4. **更新项目索引**:`.cursor/agent/开发助理/项目索引/{索引名}.md` 开发进度表追加一行
+5. **更新会议记录索引**:`.cursor/meeting/README.md`
+
+**必须使用 Read 工具读取 `.cursor/skills/assistant-doc-sync/SKILL.md` 执行收尾。**
diff --git a/.cursor/rules/soul-miniprogram-boundary.mdc b/.cursor/rules/soul-miniprogram-boundary.mdc
new file mode 100644
index 0000000..8815124
--- /dev/null
+++ b/.cursor/rules/soul-miniprogram-boundary.mdc
@@ -0,0 +1,30 @@
+---
+description: 小程序端边界约束,防止与管理端/API 路径互窜
+globs: miniprogram/**/*
+alwaysApply: false
+---
+
+# 小程序端开发边界(防互窜)
+
+在 **miniprogram/** 下新增、优化或编辑任何代码时,必须遵守以下约束:
+
+## API 路径(强制)
+
+- **允许**:仅使用以 `/api/miniprogram/` 开头的接口路径(与 soul-api 的 miniprogram 路由组一致)。
+- **禁止**:
+ - 不得使用 `/api/admin/*`、`/api/db/*`(管理端专属)。
+ - 不得使用未在 soul-api 的 miniprogram 组下注册的路径(如仅存在于 next-project 的接口)。
+- **请求方式**:统一通过 `getApp().request(url, options)` 发起,不在页面内直接写死 baseUrl 或使用 `wx.request` 拼管理端路径。
+
+## 目录与职责
+
+- 仅修改 **miniprogram/** 内文件(含 pages、components、utils、app.js 等)。
+- 不在此处实现或引用管理端逻辑;不在此处编写 soul-api 的 Go 代码或 soul-admin 的 React 代码。
+
+## Skill 加载(必须执行)
+
+**必须使用 Read 工具读取 `.cursor/skills/miniprogram-dev/SKILL.md` 的完整内容**,按其规范进行开发。该 Skill 包含代码风格、业务逻辑、API 对接细节等完整约定。
+
+接口实现与路由分组的规范在 `.cursor/rules/soul-api.mdc`(编辑 soul-api 时自动加载)。
+
+违反上述路径或职责边界即视为「互窜」,需纠正后再提交。
diff --git a/.cursor/rules/soul-project-boundary.mdc b/.cursor/rules/soul-project-boundary.mdc
new file mode 100644
index 0000000..d5fa7fd
--- /dev/null
+++ b/.cursor/rules/soul-project-boundary.mdc
@@ -0,0 +1,88 @@
+---
+description: Soul 创业派对项目整体边界、角色推断与 Skill 加载(alwaysApply)
+globs: ["**"]
+alwaysApply: true
+---
+
+# Soul 创业派对 - 项目边界
+
+## 会话自检
+
+仅沿用本项目 `.cursor/` 下的 rules、skills、配置;忽略与本项目无关的全局 rules/skills。
+
+## 项目组成
+
+| 子项目 | 目录 | 用途 | 后端对接 |
+|--------|------|------|----------|
+| 小程序 | miniprogram/ | 微信原生小程序 C 端 | soul-api |
+| 管理端 | soul-admin/ | React 管理后台(稳定版,主用) | soul-api |
+| API 后端 | soul-api/ | Go + Gin + GORM 接口服务 | - |
+| 预览/参考 | next-project/ | 仅预览,非线上 | 不依赖 |
+| **新版管理端** | **new-soul/soul-admin/** | 新版参考实现,迁移时对照 | soul-api |
+| **玩值 API** | **new/wz-api/** | 玩值 Go + Gin + GORM + MySQL | - |
+| **玩值管理端** | **new/wz-admin/** | 玩值 React + Vite 管理后台 | wz-api |
+| **玩值移动端** | **new/wz-app/** | 玩值 uni-app | wz-api `/api/app/*` |
+| **玩值原始参考** | **old/** | Next + Mongo,迁移对照 | 非目标线上栈 |
+
+编辑 **`new/wz-*` 或 `开发文档/玩值/`** 时,须同时遵守 **`.cursor/rules/wanzhi-project-boundary.mdc`** 与对应 **wz-*-dev** Skill。
+
+## 核心原则
+
+- 小程序只调 `/api/miniprogram/*`;管理端只调 `/api/admin/*`、`/api/db/*`;禁止混用。
+- 变更完成必过 soul-change-checklist.mdc;聊天中说「变更完成」「检查一下」「准备提交」时主动触发检查。
+- **需求即执行 + 零提问**:收到需求后**禁止**列出分析表格再问用户选哪个执行,**禁止**「是否帮你执行」类确认。正确做法:内部定方案 → **直接改代码/跑命令** → 回复结果。缺信息:先查仓库与配置推断;**仅**在验证码、缺失密钥、不可逆删除等无法代劳时极简说明。
+- **卡若 AI 对话规范(与卡若工作区一致)**:在本仓库内协助用户时,**默认使用简体中文**;**每条助手回复末尾**用完整 **卡若复盘块** 收尾(🎯 目标·结果·达成率 · 📌 过程 · 💡 反思 · 📝 总结 · ▶ 下一步执行),标题带 **YYYY-MM-DD HH:mm**,复盘块内不用表格,细则见卡若项目 `运营中枢/参考资料/卡若复盘格式_固定规则.md`(多根工作区时请 Read 该文件)。Mongo 同步、飞书 webhook 等以卡若 `.cursor/rules/karuo-ai.mdc` 为准(本仓库 Agent 在能执行脚本时同样执行对话留存脚本)。
+
+## 路径约定(Skill / agent / meeting)
+
+- 下表及本仓库 Skill 中的路径均以 **本 Git 仓库根目录** 为基准(与 `miniprogram/`、`soul-api/` 同级),**不使用盘符或另一台机器上的绝对路径**。
+- 使用 Read / Write 工具时:填写 **`仓库根/.cursor/...`**,例如 `.cursor/skills/api-dev/SKILL.md`(与规则中的写法一致即可)。
+- 脚本中的物理路径以 `config/paths.py` 的 `ROOT`、`SKILLS`、`AGENT`、`MEETING` 为准。
+
+## 角色推断与 Skill 加载(必须执行)
+
+根据**当前编辑目录**或**语义触发词**,**必须使用 Read 工具读取对应的主 Skill 文件完整内容**,然后按其规范执行开发:
+
+### 按编辑目录
+
+| 编辑目录 | 推断角色 | 必须 Read 的主 Skill 文件(相对仓库根) |
+|----------|----------|----------------------------------------|
+| miniprogram/ | 小程序开发工程师 | `.cursor/skills/miniprogram-dev/SKILL.md` |
+| soul-admin/ | 管理端开发工程师 | `.cursor/skills/admin-dev/SKILL.md` |
+| soul-api/ | 后端开发 | `.cursor/skills/api-dev/SKILL.md` |
+| new/wz-api/ | 玩值 API 工程师 | `.cursor/skills/wz-api-dev/SKILL.md` |
+| new/wz-admin/ | 玩值管理端工程师 | `.cursor/skills/wz-admin-dev/SKILL.md` |
+| new/wz-app/ | 玩值移动端工程师 | `.cursor/skills/wz-app-dev/SKILL.md` |
+| 开发文档/玩值/ | 助理橙子 | `.cursor/skills/assistant-doc-sync/SKILL.md` |
+| 开发文档/1、需求/、临时需求池/ | 产品经理 | `.cursor/skills/product-manager/SKILL.md` |
+| .cursor/ | 助理橙子 | `.cursor/skills/assistant-doc-sync/SKILL.md` |
+
+### 按语义触发词(说啥切角色,无需编辑文件)
+
+用户说出以下词时,推断对应角色并 Read 其 Skill(理解意图即可,不必完全匹配):
+
+| 触发词 | 推断角色 | 必须 Read 的 Skill 文件 |
+|--------|----------|-------------------------|
+| 后端、API、soul-api、接口、Go、GORM | 后端开发 | `.cursor/skills/api-dev/SKILL.md` |
+| wz-api、玩值 API、new/wz-api | 玩值 API 工程师 | `.cursor/skills/wz-api-dev/SKILL.md` |
+| 管理端、soul-admin、React、后台管理 | 管理端开发工程师 | `.cursor/skills/admin-dev/SKILL.md` |
+| wz-admin、玩值管理端、new/wz-admin | 玩值管理端工程师 | `.cursor/skills/wz-admin-dev/SKILL.md` |
+| 小程序、miniprogram、C 端、微信小程序 | 小程序开发工程师 | `.cursor/skills/miniprogram-dev/SKILL.md` |
+| uni-app、wz-app、玩值移动端、new/wz-app | 玩值移动端工程师 | `.cursor/skills/wz-app-dev/SKILL.md` |
+| 产品、需求、验收、排期、需求文档 | 产品经理 | `.cursor/skills/product-manager/SKILL.md` |
+| 测试、测试用例、回归测试、功能测试、QA | 测试人员 | `.cursor/skills/testing/SKILL.md` |
+
+### 按场景触发词
+
+| 场景触发词 | 必须 Read 的 Skill 文件(相对仓库根) |
+|------------|----------------------------------------|
+| 小橙、橙子、橙橙、🍊、讨论完毕、记录一下、记录、同步文档 | `.cursor/skills/assistant-doc-sync/SKILL.md` |
+| 吸收经验、升级 skills、记录经验、保存开发进度、更新项目索引、记录开发进度、任务完成、搞定了、完成了 | `.cursor/skills/assistant-doc-sync/SKILL.md` |
+| 跨端功能开发 | `.cursor/skills/role-flow-control/SKILL.md` |
+| 变更完成、检查一下、准备提交 | `.cursor/skills/change-checklist/SKILL.md` |
+| 开个会、开会、团队会议、乘风开会、需求评审、方案讨论、大家一起讨论 | `.cursor/skills/team-meeting/SKILL.md`(老板分身/乘风主持) |
+| 会议结束、散会、会开完了 | `.cursor/skills/assistant-doc-sync/SKILL.md`(会议收尾) |
+| **加个需求**、加个需求:xxx | `.cursor/skills/product-manager/SKILL.md`(需求即执行:回复「好」→ 直接执行代码变更 → 回复结果) |
+| **新版分析**、版本对比、迁移分析、甲方代码分析、快速分析新版、抽取需求 | `.cursor/skills/new-version-analyze/SKILL.md`(新版快速分析 → 差异清单 → 接口冲突 → 迁移迭代) |
+
+**注意**:「必须 Read」= 使用 Read 工具读取上述路径相对于**当前工作区仓库根**的完整文件内容后执行,不可跳过或仅凭记忆。
diff --git a/.cursor/rules/wanzhi-project-boundary.mdc b/.cursor/rules/wanzhi-project-boundary.mdc
new file mode 100644
index 0000000..b052396
--- /dev/null
+++ b/.cursor/rules/wanzhi-project-boundary.mdc
@@ -0,0 +1,48 @@
+---
+description: 玩值 Wanzhi 三端(new/wz-api、new/wz-admin、new/wz-app)边界、路由隔离与 Skill 加载
+globs: ["new/**", "开发文档/玩值/**"]
+alwaysApply: false
+---
+
+# 玩值(Wanzhi)- 项目边界
+
+## 会话自检
+
+玩值迁移真源见仓库根 **`开发文档/玩值/`**(API 对照表、前缀决议、进度看板)。与 Soul 派对(`miniprogram/`、`soul-api/`、`soul-admin/`)并存时,**编辑 `new/` 下代码以本规则为准**。
+
+## 项目组成
+
+| 子项目 | 目录 | 用途 | 后端对接 |
+|--------|------|------|----------|
+| API | `new/wz-api/` | Go + Gin + GORM + MySQL | - |
+| 管理端 | `new/wz-admin/` | React + Vite 管理后台 | wz-api `/api/admin/*`、`/api/db/*` |
+| 移动端 | `new/wz-app/` | uni-app(App/小程序/H5) | wz-api **`/api/app/*`**(禁止直连 admin 前缀) |
+| 原始参考 | `old/` | Next.js + Mongo,仅对照业务与路由 | 不作为线上目标栈 |
+
+## 核心原则
+
+- **移动端**只调 `/api/app/*`(及文档规定的 `/api/public/*`、`/api/screen/*` 若适用);**管理端**只调 `/api/admin/*`、`/api/db/*`;**禁止混用**。
+- 契约变更必须同步 **`开发文档/玩值/迁移-API对照表.md`** 与 **`进度-看板.md`**。
+- 变更完成走 **`.cursor/skills/change-checklist/SKILL.md`**(玩值三端路径)。
+- **需求即执行 + 零提问**:缺信息先查 `old/` 与开发文档;仅验证码、密钥、不可逆删除等不可代劳时极简说明。
+
+## 角色推断与 Skill 加载(必须 Read 完整 SKILL)
+
+### 按编辑目录
+
+| 编辑目录 | 推断角色 | 必须 Read 的主 Skill |
+|----------|----------|----------------------|
+| `new/wz-api/` | 玩值 API 工程师 | `.cursor/skills/wz-api-dev/SKILL.md` |
+| `new/wz-admin/` | 玩值管理端工程师 | `.cursor/skills/wz-admin-dev/SKILL.md` |
+| `new/wz-app/` | 玩值移动端工程师 | `.cursor/skills/wz-app-dev/SKILL.md` |
+| `开发文档/玩值/` | 助理橙子(文档) | `.cursor/skills/assistant-doc-sync/SKILL.md` |
+
+### 按语义触发词
+
+| 触发词 | 必须 Read |
+|--------|-----------|
+| wz-api、玩值 API、new/wz-api、Go、Gin、玩值后端 | `.cursor/skills/wz-api-dev/SKILL.md` |
+| wz-admin、玩值管理端、new/wz-admin | `.cursor/skills/wz-admin-dev/SKILL.md` |
+| wz-app、uni-app、玩值移动端、new/wz-app | `.cursor/skills/wz-app-dev/SKILL.md` |
+| 跨端、迁移闭环、垂直切片 | `.cursor/skills/role-flow-control/SKILL.md` |
+| old 对照、Next 迁移、路由矩阵 | `开发文档/玩值/迁移-API对照表.md` + `.cursor/skills/next-split/SKILL.md` |
diff --git a/.cursor/rules/智能体-先查经验-技能索引.mdc b/.cursor/rules/智能体-先查经验-技能索引.mdc
new file mode 100644
index 0000000..2c4d17d
--- /dev/null
+++ b/.cursor/rules/智能体-先查经验-技能索引.mdc
@@ -0,0 +1,17 @@
+---
+description: 所有智能体处理任务前先查经验与技能索引
+globs: ["**"]
+alwaysApply: true
+---
+
+# 强制流程:先查经验与技能再处理
+
+当任何智能体接到“问题/需求/变更/执行”任务时,必须按以下顺序完成(不能跳过):
+
+1. 归类角色:根据编辑目录或触发词判断可能涉及的角色/任务类型。
+2. 查总索引:先打开 `.cursor/agent/智能体-技能-经验总索引.md` 找匹配的“经验索引文件”和“主 Skill 文件”。
+3. 查经验+查 Skill:对每个匹配条目,先读对应角色 `agent/*/evolution/索引.md`,再按边界规则读取对应主 `skills/*/SKILL.md` 完整内容。
+4. 再综合落地:确认是否还需 `change-checklist`、`team-meeting`、`role-flow-control` 等跨层 Skill 后,再回答/执行。
+
+注:本规则不替代现有 project boundary 的“必须 Read 主 Skill 完整内容”的约束,只是增加“先查索引再读 Skill”的前置步骤。
+
diff --git a/.cursor/rules/老板分身-索引.mdc b/.cursor/rules/老板分身-索引.mdc
new file mode 100644
index 0000000..114f478
--- /dev/null
+++ b/.cursor/rules/老板分身-索引.mdc
@@ -0,0 +1,86 @@
+---
+description: 老板分身 - 最高权限智能体,协调 Soul 开发团队;编码习惯与思维模式总览
+alwaysApply: true
+---
+
+# 老板分身 - 能力与约束(Soul 创业派对)
+
+> **老板分身权限最高**:协调 **开发助理(橙子)**、**玩值三项目智能体**(玩值 API / 管理端 / 移动端工程师),以及仍维护中的 Soul 栈(`miniprogram/`、`soul-admin/`、`soul-api/`)对应规范。原 Soul 单角色 agent 已归档至 `archive/agents-legacy-soul/`。其他 agent 执行任务时遵循本规则;老板分身可调度、协调、指派任一角色。
+> **激活方式**:用户说「老板」「分身」「乘风」「架构」「帮我协调」时,从旁观者转为主动参与。**开会时**:用户说「开会」「开个会」「团队会议」「乘风开会」「需求评审」「方案讨论」等表达开会意图时,**必须先**用 Read 工具读取 `.cursor/skills/team-meeting/SKILL.md` 完整内容(相对本仓库根),然后由老板分身(乘风)按该协议主持多角色会议,不可仅回复而不执行流程。
+> **会话自检**:仅沿用本项目 `.cursor/` 下的 rules、skills、agent;忽略与本项目无关的全局 rules/skills。
+> **Skill 撰写**:维护或新增 Skill 时参考 `e:\Gongsi\Mycontent\.cursor\docs\skill-writing-principles.md`(写 Claude 不知道的、Gotchas 是灵魂、留灵活空间)。
+> **角色驱动**:Soul 角色与 agent 映射见 `config/paths.py` 的 ROLE_TO_AGENT。
+
+### 领域特例优先(含合理性校验)
+
+当某个 **skill** 或领域规则与通用规则冲突时,原则上以该 skill/领域规则为准。**但须先做合理性校验**:
+
+- 若 skill 的规则**明显不合理**(如违背安全、可维护性、行业惯例等),应**提醒用户**并说明原因,**确认后再覆盖**
+- 若合理(如 Soul 三端路由隔离约定),可直接按 skill 执行
+
+---
+
+## 〇、经验自动收集(优先执行)
+
+**在每次回复前判断**:本次会话是否完成了一次「问题 → 解决」的闭环?
+
+### 判定条件(同时满足则触发)
+
+1. 会话中出现了**技术问题**(报错、bug、实现困难、配置问题等)
+2. 问题已**解决**:用户明确表示解决(如「解决了」「可以了」「搞定了」「好了」「跑通了」)
+3. 解决过程有**可提炼价值**:有具体的问题描述、解决步骤或关键决策
+
+### 触发后动作
+
+1. 从对话中提取:问题描述、解决过程、关键决策、可提炼的规则方向
+2. **推断目标角色**(可多选):
+ - `new/wz-app`/uni-app→**玩值移动端工程师**;`miniprogram/` 微信原生→**小程序开发工程师**(经验归档目录)
+ - `new/wz-admin`→**玩值管理端工程师**;`soul-admin/`→**管理端开发工程师**(归档)
+ - `new/wz-api`→**玩值 API 工程师**;`soul-api/`→**后端工程师**(归档)
+ - 产品/需求/config→**开发助理**(新经验默认)或按需读归档「产品经理」
+ - 测试/自检/QA→**开发助理**(新经验默认)
+ - 架构/选型/路由约定/三端协同→**开发助理** + **`开发文档/玩值/`**
+ - 无法判断→**开发助理**
+3. **若可写文件**:
+ - **有明确目标角色**:写入 `.cursor/agent/{角色}/evolution/YYYY-MM-DD-简短描述.md`,并更新该目录下的 `索引.md`
+ - **无法判断角色**:写入 `.cursor/agent/开发助理/evolution/`
+4. **若无法写文件**:输出 JSON,并提示用户在仓库根执行:`.cursor/agent/开发助理/script/一键-添加经验.bat`(Windows)或同目录下的 `.sh` / Python 入口(macOS/Linux,若已提供)
+
+### Soul 目标角色与 target_roles 取值
+
+| 推断场景 | target_roles |
+|----------|--------------|
+| uni-app / `new/wz-app` | `["玩值移动端工程师"]` |
+| 小程序/WXML/微信(Soul) | `["小程序开发工程师"]` |
+| `new/wz-admin` | `["玩值管理端工程师"]` |
+| 管理端/React/admin(Soul) | `["管理端开发工程师"]` |
+| `new/wz-api` | `["玩值API工程师"]` |
+| 后端/Go/Gin/API(Soul) | `["后端工程师"]` |
+| 产品/需求 | `["开发助理"]` |
+| 测试/QA | `["开发助理"]` |
+| 架构/三端协同(玩值) | `["玩值API工程师","玩值管理端工程师","玩值移动端工程师"]` |
+| 跨端(小程序+管理端,Soul) | `["小程序开发工程师","管理端开发工程师"]` |
+
+### 不触发情况
+
+- 纯咨询、无实际问题
+- 问题未解决或用户未确认
+- 用户明确说「不要记录」「不用沉淀」
+
+---
+
+## 一、编码习惯
+
+- 先理解需求,再动手写代码
+- 小步迭代,可读性优先
+- 函数保持单一职责,避免深层嵌套
+
+---
+
+## 二、Soul 三端分工
+
+- **小程序**:只调 `/api/miniprogram/*`,禁止调 admin/db
+- **管理端**:只调 `/api/admin/*`、`/api/db/*`
+- **后端**:路由分组 miniprogram/admin/db,响应格式统一
+
+跨端任务时先分解:后端任务 / 管理端任务 / 小程序任务,再分阶段执行。
diff --git a/.cursor/scripts/README-gitea-sync.md b/.cursor/scripts/README-gitea-sync.md
new file mode 100644
index 0000000..1be531c
--- /dev/null
+++ b/.cursor/scripts/README-gitea-sync.md
@@ -0,0 +1,159 @@
+# 与 Gitea(192.168.1.201)同步
+
+## 远程
+
+- **gitea-local**:`http://192.168.1.201:3000/fnvtk/soul-yongping.git`(拉取 + 推送)
+
+## 手动同步
+
+```bash
+./.cursor/scripts/gitea-sync.sh
+```
+
+## 每 10 分钟自动同步(macOS launchd)
+
+- 已安装:`~/Library/LaunchAgents/com.soul.yongping.gitea-sync.plist`
+- 每 10 分钟执行一次,登录后自动加载
+
+**启用:**
+
+```bash
+launchctl load ~/Library/LaunchAgents/com.soul.yongping.gitea-sync.plist
+```
+
+**停用:**
+
+```bash
+launchctl unload ~/Library/LaunchAgents/com.soul.yongping.gitea-sync.plist
+```
+
+**查看是否在跑:**
+
+```bash
+launchctl list | grep com.soul.yongping.gitea-sync
+```
+
+## 认证(192.168.1.201 需登录时)
+
+若 push/pull 需要账号密码,定时任务无法弹窗,请把凭证写进 remote URL(勿提交到仓库):
+
+```bash
+git remote set-url gitea-local 'http://用户名:token或密码@192.168.1.201:3000/fnvtk/soul-yongping.git'
+```
+
+或用系统钥匙串:
+
+```bash
+git config --global credential.helper osxkeychain
+# 然后手动执行一次 gitea-sync.sh,输入一次账号密码,之后由钥匙串记住
+```
+
+## 日志
+
+- 脚本内部:`.cursor/scripts/gitea-sync.log`
+- launchd 标准输出:`.cursor/scripts/gitea-sync-launchd.log`
+- launchd 错误:`.cursor/scripts/gitea-sync-launchd.err.log`
+
+## 跨网克隆失败(502 / `RPC failed` / `curl 18` / `early EOF`)
+
+现象:`git clone http://open.quwanzhi.com:3000/...` 枚举对象到 100% 后断线,或一开始就 **502 Bad Gateway**。仓库约 2.5 万对象时较常见,**根因多在服务端反代或链路**,客户端可做降级与重试。
+
+### 1. 优先:浅克隆 + 按需补历史(Windows `cmd` 示例)
+
+先少传数据,成功率最高:
+
+```bat
+git config --global http.postBuffer 524288000
+git clone --depth 1 --single-branch --branch devlop http://open.quwanzhi.com:3000/fnvtk/soul-yongping.git
+cd soul-yongping
+git fetch --unshallow
+```
+
+仍断线可再试 **partial clone**(Git 2.22+):
+
+```bat
+git clone --filter=blob:none --depth 1 --single-branch --branch devlop http://open.quwanzhi.com:3000/fnvtk/soul-yongping.git
+```
+
+失败目录可删后重试;或进入半成品目录执行 `git fetch` 多次,Git 会续传。
+
+### 2. 客户端其它设置(可选)
+
+```bat
+git config --global http.version HTTP/1.1
+git config --global core.compression 0
+```
+
+压缩关掉会多占带宽,有时能避开中间设备对「大块压缩流」的处理问题。
+
+### 3. 能走内网时改用局域网 Gitea
+
+文档顶部 **gitea-local**(`192.168.1.201:3000`)通常比公网 `:3000` 稳定;在公司/VPN 内优先用内网地址克隆。
+
+### 3.1 内网 `192.168.1.201:3000` 仍 502 / early EOF
+
+说明:**内网和公网同一症状 = 问题在 Gitea 本机或前面的反代**,不是「你电脑网络」 alone。
+
+**先确认服务是否活着(在能访问内网的机器上):**
+
+```bat
+curl -sI http://192.168.1.201:3000/
+```
+
+- 若 **立刻 502**:多为 **Nginx(或宝塔反代)连不上 Gitea 进程**(Gitea 挂了、监听端口不对、防火墙)。
+- 若 **Web 能开、只有 git clone 断**:多为 **反代超时 / 缓冲** 不适合大块 `git-upload-pack` 流。
+
+**有服务器权限时(宝塔 / Nginx)**:在 **指向 Gitea 的那一段** 加大超时、关掉对 Git 流的缓冲(上游端口以你机器为准,常见 Gitea 在 `127.0.0.1:3000` 或其它端口,勿照抄错):
+
+```nginx
+client_max_body_size 512M;
+proxy_connect_timeout 300s;
+proxy_send_timeout 3600s;
+proxy_read_timeout 3600s;
+proxy_buffering off;
+proxy_request_buffering off;
+```
+
+改完后 **`nginx -t && nginx -s reload`**,并 **重启 Gitea**。仍 502 时看 **`error.log`** 里 `upstream timed out` / `connection refused` 对应哪一层。
+
+**若短期无法改服务器**:用下面 **离线 bundle**,从已有完整仓库的机器拷一份到 Windows(不经过 HTTP 大包传输)。
+
+### 4. 离线绕过:git bundle(推荐,不依赖 Gitea HTTP 稳定)
+
+在 **Mac / 已能拉全仓库的机器**(本仓库根目录)执行:
+
+```bash
+chmod +x .cursor/scripts/create-offline-bundle.sh
+.cursor/scripts/create-offline-bundle.sh devlop
+```
+
+会在仓库根生成 `soul-yongping-devlop.bundle`,拷到 Windows 后:
+
+```bat
+git clone E:\路径\soul-yongping-devlop.bundle soul-yongping
+cd soul-yongping
+git remote add origin http://192.168.1.201:3000/fnvtk/soul-yongping.git
+git fetch origin
+```
+
+日常 `pull`/`push` 仍走 Gitea;若 push 仍断,再与运维修反代。
+
+### 5. Windows 一键重试克隆(仍依赖服务端正常)
+
+将本仓库拷到 Windows 或只拷脚本后,在 `cmd` 中进入脚本目录:
+
+```bat
+set REPO=http://192.168.1.201:3000/fnvtk/soul-yongping.git
+set BRANCH=devlop
+clone-soul-yongping-windows.bat
+```
+
+(需登录时第一次会提示账号密码,或事先 `git config --global credential.helper manager` 并手动 `git ls-remote` 存凭证。)
+
+### 6. 服务端(有权限时):反代与 Gitea(摘要)
+
+502 / 传一半断线,除第 3.1 节参数外,还需保证 **Nginx `proxy_pass` 指向的 Gitea 进程在线**。官方反代说明:。
+
+### 7. 安全提醒
+
+勿在截图/聊天记录里长期暴露「用户名:密码@」完整 URL;改用 **访问令牌** + 凭证管理器(Windows:`git config --global credential.helper manager`)。
diff --git a/.cursor/scripts/clone-soul-yongping-windows.bat b/.cursor/scripts/clone-soul-yongping-windows.bat
new file mode 100644
index 0000000..a535bfa
--- /dev/null
+++ b/.cursor/scripts/clone-soul-yongping-windows.bat
@@ -0,0 +1,37 @@
+@echo off
+chcp 65001 >nul
+setlocal EnableDelayedExpansion
+
+REM 按需修改:内网或公网;账号密码勿长期写死,建议用访问令牌 + credential manager
+if not defined REPO set "REPO=http://192.168.1.201:3000/fnvtk/soul-yongping.git"
+if not defined BRANCH set "BRANCH=devlop"
+
+git config --global http.postBuffer 524288000
+git config --global http.version HTTP/1.1
+
+if exist soul-yongping (
+ echo 目录 soul-yongping 已存在,请先改名或删除后再运行。
+ exit /b 1
+)
+
+echo [1/3] 浅克隆 --depth 1 ...
+git clone --depth 1 --single-branch --branch "%BRANCH%" "%REPO%" soul-yongping
+if !errorlevel! equ 0 goto unshallow
+
+echo [2/3] 失败,尝试 partial clone ...
+git clone --filter=blob:none --depth 1 --single-branch --branch "%BRANCH%" "%REPO%" soul-yongping
+if !errorlevel! equ 0 goto unshallow
+
+echo [3/3] 仍失败。502/early EOF 多为服务器 Nginx 超时或 Gitea 上游异常,见 README-gitea-sync.md「内网仍 502」与离线 bundle。
+exit /b 1
+
+:unshallow
+cd soul-yongping
+echo 拉全历史 git fetch --unshallow ...
+git fetch --unshallow
+if !errorlevel! neq 0 (
+ echo unshallow 未完成可多执行几次: git fetch --unshallow
+)
+cd ..
+echo 完成。
+exit /b 0
diff --git a/.cursor/scripts/create-offline-bundle.sh b/.cursor/scripts/create-offline-bundle.sh
new file mode 100644
index 0000000..fac9a6d
--- /dev/null
+++ b/.cursor/scripts/create-offline-bundle.sh
@@ -0,0 +1,12 @@
+#!/usr/bin/env bash
+# 在「已有完整仓库」的机器上生成 bundle,拷到 U 盘/共享盘后 Windows: git clone xxx.bundle soul-yongping
+set -euo pipefail
+ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
+cd "$ROOT"
+BRANCH="${1:-devlop}"
+OUT="${2:-$ROOT/soul-yongping-${BRANCH}.bundle}"
+git rev-parse --verify "$BRANCH" >/dev/null
+git bundle create "$OUT" "$BRANCH"
+echo "已生成: $OUT"
+echo "Windows 示例: git clone %CD%\\soul-yongping-${BRANCH}.bundle soul-yongping"
+echo "后续补远程: cd soul-yongping && git remote add origin http://192.168.1.201:3000/fnvtk/soul-yongping.git"
diff --git a/.cursor/scripts/db-exec/README.md b/.cursor/scripts/db-exec/README.md
new file mode 100644
index 0000000..196dbed
--- /dev/null
+++ b/.cursor/scripts/db-exec/README.md
@@ -0,0 +1,20 @@
+# db-exec - MySQL 直接执行脚本
+
+当 MCP MySQL 因端口非 3306 无法连接时,用此脚本执行 SQL。
+
+## 首次使用
+
+```bash
+cd .cursor/scripts/db-exec
+npm install
+```
+
+## 用法
+
+```bash
+# 从项目根目录执行
+node .cursor/scripts/db-exec/run.js "SELECT 1"
+node .cursor/scripts/db-exec/run.js -f migrations/xxx.sql
+```
+
+凭证自动从 `soul-api/.env` 的 `DB_DSN` 读取。
diff --git a/.cursor/scripts/db-exec/node_modules/.package-lock.json b/.cursor/scripts/db-exec/node_modules/.package-lock.json
new file mode 100644
index 0000000..d7f139d
--- /dev/null
+++ b/.cursor/scripts/db-exec/node_modules/.package-lock.json
@@ -0,0 +1,149 @@
+{
+ "name": "soul-db-exec",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "node_modules/@types/node": {
+ "version": "25.3.0",
+ "resolved": "https://registry.npmmirror.com/@types/node/-/node-25.3.0.tgz",
+ "integrity": "sha512-4K3bqJpXpqfg2XKGK9bpDTc6xO/xoUP/RBWS7AtRMug6zZFaRekiLzjVtAoZMquxoAbzBvy5nxQ7veS5eYzf8A==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "undici-types": "~7.18.0"
+ }
+ },
+ "node_modules/aws-ssl-profiles": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmmirror.com/aws-ssl-profiles/-/aws-ssl-profiles-1.1.2.tgz",
+ "integrity": "sha512-NZKeq9AfyQvEeNlN0zSYAaWrmBffJh3IELMZfRpJVWgrpEbtEpnjvzqBPf+mxoI287JohRDoa+/nsfqqiZmF6g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 6.0.0"
+ }
+ },
+ "node_modules/denque": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmmirror.com/denque/-/denque-2.1.0.tgz",
+ "integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==",
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/generate-function": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmmirror.com/generate-function/-/generate-function-2.3.1.tgz",
+ "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==",
+ "license": "MIT",
+ "dependencies": {
+ "is-property": "^1.0.2"
+ }
+ },
+ "node_modules/iconv-lite": {
+ "version": "0.7.2",
+ "resolved": "https://registry.npmmirror.com/iconv-lite/-/iconv-lite-0.7.2.tgz",
+ "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==",
+ "license": "MIT",
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/express"
+ }
+ },
+ "node_modules/is-property": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmmirror.com/is-property/-/is-property-1.0.2.tgz",
+ "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==",
+ "license": "MIT"
+ },
+ "node_modules/long": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmmirror.com/long/-/long-5.3.2.tgz",
+ "integrity": "sha512-mNAgZ1GmyNhD7AuqnTG3/VQ26o760+ZYBPKjPvugO8+nLbYfX6TVpJPseBvopbdY+qpZ/lKUnmEc1LeZYS3QAA==",
+ "license": "Apache-2.0"
+ },
+ "node_modules/lru.min": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmmirror.com/lru.min/-/lru.min-1.1.4.tgz",
+ "integrity": "sha512-DqC6n3QQ77zdFpCMASA1a3Jlb64Hv2N2DciFGkO/4L9+q/IpIAuRlKOvCXabtRW6cQf8usbmM6BE/TOPysCdIA==",
+ "license": "MIT",
+ "engines": {
+ "bun": ">=1.0.0",
+ "deno": ">=1.30.0",
+ "node": ">=8.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/wellwelwel"
+ }
+ },
+ "node_modules/mysql2": {
+ "version": "3.18.0",
+ "resolved": "https://registry.npmmirror.com/mysql2/-/mysql2-3.18.0.tgz",
+ "integrity": "sha512-3rupyOFks7Vq0jcjBpmg1gtgfGuCcmgrRJPEfpGzzrB/ydutupbjKkoDJGsGkrJRU6j44o2tb0McduL03/v/dQ==",
+ "license": "MIT",
+ "dependencies": {
+ "aws-ssl-profiles": "^1.1.2",
+ "denque": "^2.1.0",
+ "generate-function": "^2.3.1",
+ "iconv-lite": "^0.7.2",
+ "long": "^5.3.2",
+ "lru.min": "^1.1.4",
+ "named-placeholders": "^1.1.6",
+ "sql-escaper": "^1.3.3"
+ },
+ "engines": {
+ "node": ">= 8.0"
+ },
+ "peerDependencies": {
+ "@types/node": ">= 8"
+ }
+ },
+ "node_modules/named-placeholders": {
+ "version": "1.1.6",
+ "resolved": "https://registry.npmmirror.com/named-placeholders/-/named-placeholders-1.1.6.tgz",
+ "integrity": "sha512-Tz09sEL2EEuv5fFowm419c1+a/jSMiBjI9gHxVLrVdbUkkNUUfjsVYs9pVZu5oCon/kmRh9TfLEObFtkVxmY0w==",
+ "license": "MIT",
+ "dependencies": {
+ "lru.min": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=8.0.0"
+ }
+ },
+ "node_modules/safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmmirror.com/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+ "license": "MIT"
+ },
+ "node_modules/sql-escaper": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmmirror.com/sql-escaper/-/sql-escaper-1.3.3.tgz",
+ "integrity": "sha512-BsTCV265VpTp8tm1wyIm1xqQCS+Q9NHx2Sr+WcnUrgLrQ6yiDIvHYJV5gHxsj1lMBy2zm5twLaZao8Jd+S8JJw==",
+ "license": "MIT",
+ "engines": {
+ "bun": ">=1.0.0",
+ "deno": ">=2.0.0",
+ "node": ">=12.0.0"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/mysqljs/sql-escaper?sponsor=1"
+ }
+ },
+ "node_modules/undici-types": {
+ "version": "7.18.2",
+ "resolved": "https://registry.npmmirror.com/undici-types/-/undici-types-7.18.2.tgz",
+ "integrity": "sha512-AsuCzffGHJybSaRrmr5eHr81mwJU3kjw6M+uprWvCXiNeN9SOGwQ3Jn8jb8m3Z6izVgknn1R0FTCEAP2QrLY/w==",
+ "license": "MIT",
+ "peer": true
+ }
+ }
+}
diff --git a/.cursor/scripts/db-exec/node_modules/@types/node/LICENSE b/.cursor/scripts/db-exec/node_modules/@types/node/LICENSE
new file mode 100644
index 0000000..9e841e7
--- /dev/null
+++ b/.cursor/scripts/db-exec/node_modules/@types/node/LICENSE
@@ -0,0 +1,21 @@
+ MIT License
+
+ Copyright (c) Microsoft Corporation.
+
+ Permission is hereby granted, free of charge, to any person obtaining a copy
+ of this software and associated documentation files (the "Software"), to deal
+ in the Software without restriction, including without limitation the rights
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+ copies of the Software, and to permit persons to whom the Software is
+ furnished to do so, subject to the following conditions:
+
+ The above copyright notice and this permission notice shall be included in all
+ copies or substantial portions of the Software.
+
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+ SOFTWARE
diff --git a/.cursor/scripts/db-exec/node_modules/@types/node/README.md b/.cursor/scripts/db-exec/node_modules/@types/node/README.md
new file mode 100644
index 0000000..84f31ac
--- /dev/null
+++ b/.cursor/scripts/db-exec/node_modules/@types/node/README.md
@@ -0,0 +1,15 @@
+# Installation
+> `npm install --save @types/node`
+
+# Summary
+This package contains type definitions for node (https://nodejs.org/).
+
+# Details
+Files were exported from https://github.com/DefinitelyTyped/DefinitelyTyped/tree/master/types/node.
+
+### Additional Details
+ * Last updated: Thu, 19 Feb 2026 00:56:10 GMT
+ * Dependencies: [undici-types](https://npmjs.com/package/undici-types)
+
+# Credits
+These definitions were written by [Microsoft TypeScript](https://github.com/Microsoft), [Alberto Schiabel](https://github.com/jkomyno), [Andrew Makarov](https://github.com/r3nya), [Benjamin Toueg](https://github.com/btoueg), [David Junger](https://github.com/touffy), [Mohsen Azimi](https://github.com/mohsen1), [Nikita Galkin](https://github.com/galkin), [Sebastian Silbermann](https://github.com/eps1lon), [Wilco Bakker](https://github.com/WilcoBakker), [Marcin Kopacz](https://github.com/chyzwar), [Trivikram Kamat](https://github.com/trivikr), [Junxiao Shi](https://github.com/yoursunny), [Ilia Baryshnikov](https://github.com/qwelias), [ExE Boss](https://github.com/ExE-Boss), [Piotr Błażejewicz](https://github.com/peterblazejewicz), [Anna Henningsen](https://github.com/addaleax), [Victor Perin](https://github.com/victorperin), [NodeJS Contributors](https://github.com/NodeJS), [Linus Unnebäck](https://github.com/LinusU), [wafuwafu13](https://github.com/wafuwafu13), [Matteo Collina](https://github.com/mcollina), [Dmitry Semigradsky](https://github.com/Semigradsky), [René](https://github.com/Renegade334), and [Yagiz Nizipli](https://github.com/anonrig).
diff --git a/.cursor/scripts/db-exec/node_modules/@types/node/assert.d.ts b/.cursor/scripts/db-exec/node_modules/@types/node/assert.d.ts
new file mode 100644
index 0000000..ef4d852
--- /dev/null
+++ b/.cursor/scripts/db-exec/node_modules/@types/node/assert.d.ts
@@ -0,0 +1,955 @@
+/**
+ * The `node:assert` module provides a set of assertion functions for verifying
+ * invariants.
+ * @see [source](https://github.com/nodejs/node/blob/v25.x/lib/assert.js)
+ */
+declare module "node:assert" {
+ import strict = require("node:assert/strict");
+ /**
+ * An alias of {@link assert.ok}.
+ * @since v0.5.9
+ * @param value The input that is checked for being truthy.
+ */
+ function assert(value: unknown, message?: string | Error): asserts value;
+ const kOptions: unique symbol;
+ namespace assert {
+ type AssertMethodNames =
+ | "deepEqual"
+ | "deepStrictEqual"
+ | "doesNotMatch"
+ | "doesNotReject"
+ | "doesNotThrow"
+ | "equal"
+ | "fail"
+ | "ifError"
+ | "match"
+ | "notDeepEqual"
+ | "notDeepStrictEqual"
+ | "notEqual"
+ | "notStrictEqual"
+ | "ok"
+ | "partialDeepStrictEqual"
+ | "rejects"
+ | "strictEqual"
+ | "throws";
+ interface AssertOptions {
+ /**
+ * If set to `'full'`, shows the full diff in assertion errors.
+ * @default 'simple'
+ */
+ diff?: "simple" | "full" | undefined;
+ /**
+ * If set to `true`, non-strict methods behave like their
+ * corresponding strict methods.
+ * @default true
+ */
+ strict?: boolean | undefined;
+ /**
+ * If set to `true`, skips prototype and constructor
+ * comparison in deep equality checks.
+ * @since v24.9.0
+ * @default false
+ */
+ skipPrototype?: boolean | undefined;
+ }
+ interface Assert extends Pick {
+ readonly [kOptions]: AssertOptions & { strict: false };
+ }
+ interface AssertStrict extends Pick {
+ readonly [kOptions]: AssertOptions & { strict: true };
+ }
+ /**
+ * The `Assert` class allows creating independent assertion instances with custom options.
+ * @since v24.6.0
+ */
+ var Assert: {
+ /**
+ * Creates a new assertion instance. The `diff` option controls the verbosity of diffs in assertion error messages.
+ *
+ * ```js
+ * const { Assert } = require('node:assert');
+ * const assertInstance = new Assert({ diff: 'full' });
+ * assertInstance.deepStrictEqual({ a: 1 }, { a: 2 });
+ * // Shows a full diff in the error message.
+ * ```
+ *
+ * **Important**: When destructuring assertion methods from an `Assert` instance,
+ * the methods lose their connection to the instance's configuration options (such
+ * as `diff`, `strict`, and `skipPrototype` settings).
+ * The destructured methods will fall back to default behavior instead.
+ *
+ * ```js
+ * const myAssert = new Assert({ diff: 'full' });
+ *
+ * // This works as expected - uses 'full' diff
+ * myAssert.strictEqual({ a: 1 }, { b: { c: 1 } });
+ *
+ * // This loses the 'full' diff setting - falls back to default 'simple' diff
+ * const { strictEqual } = myAssert;
+ * strictEqual({ a: 1 }, { b: { c: 1 } });
+ * ```
+ *
+ * The `skipPrototype` option affects all deep equality methods:
+ *
+ * ```js
+ * class Foo {
+ * constructor(a) {
+ * this.a = a;
+ * }
+ * }
+ *
+ * class Bar {
+ * constructor(a) {
+ * this.a = a;
+ * }
+ * }
+ *
+ * const foo = new Foo(1);
+ * const bar = new Bar(1);
+ *
+ * // Default behavior - fails due to different constructors
+ * const assert1 = new Assert();
+ * assert1.deepStrictEqual(foo, bar); // AssertionError
+ *
+ * // Skip prototype comparison - passes if properties are equal
+ * const assert2 = new Assert({ skipPrototype: true });
+ * assert2.deepStrictEqual(foo, bar); // OK
+ * ```
+ *
+ * When destructured, methods lose access to the instance's `this` context and revert to default assertion behavior
+ * (diff: 'simple', non-strict mode).
+ * To maintain custom options when using destructured methods, avoid
+ * destructuring and call methods directly on the instance.
+ * @since v24.6.0
+ */
+ new(
+ options?: AssertOptions & { strict?: true | undefined },
+ ): AssertStrict;
+ new(
+ options: AssertOptions,
+ ): Assert;
+ };
+ interface AssertionErrorOptions {
+ /**
+ * If provided, the error message is set to this value.
+ */
+ message?: string | undefined;
+ /**
+ * The `actual` property on the error instance.
+ */
+ actual?: unknown;
+ /**
+ * The `expected` property on the error instance.
+ */
+ expected?: unknown;
+ /**
+ * The `operator` property on the error instance.
+ */
+ operator?: string | undefined;
+ /**
+ * If provided, the generated stack trace omits frames before this function.
+ */
+ stackStartFn?: Function | undefined;
+ /**
+ * If set to `'full'`, shows the full diff in assertion errors.
+ * @default 'simple'
+ */
+ diff?: "simple" | "full" | undefined;
+ }
+ /**
+ * Indicates the failure of an assertion. All errors thrown by the `node:assert` module will be instances of the `AssertionError` class.
+ */
+ class AssertionError extends Error {
+ constructor(options: AssertionErrorOptions);
+ /**
+ * Set to the `actual` argument for methods such as {@link assert.strictEqual()}.
+ */
+ actual: unknown;
+ /**
+ * Set to the `expected` argument for methods such as {@link assert.strictEqual()}.
+ */
+ expected: unknown;
+ /**
+ * Indicates if the message was auto-generated (`true`) or not.
+ */
+ generatedMessage: boolean;
+ /**
+ * Value is always `ERR_ASSERTION` to show that the error is an assertion error.
+ */
+ code: "ERR_ASSERTION";
+ /**
+ * Set to the passed in operator value.
+ */
+ operator: string;
+ }
+ type AssertPredicate = RegExp | (new() => object) | ((thrown: unknown) => boolean) | object | Error;
+ /**
+ * Throws an `AssertionError` with the provided error message or a default
+ * error message. If the `message` parameter is an instance of an `Error` then
+ * it will be thrown instead of the `AssertionError`.
+ *
+ * ```js
+ * import assert from 'node:assert/strict';
+ *
+ * assert.fail();
+ * // AssertionError [ERR_ASSERTION]: Failed
+ *
+ * assert.fail('boom');
+ * // AssertionError [ERR_ASSERTION]: boom
+ *
+ * assert.fail(new TypeError('need array'));
+ * // TypeError: need array
+ * ```
+ * @since v0.1.21
+ * @param [message='Failed']
+ */
+ function fail(message?: string | Error): never;
+ /**
+ * Tests if `value` is truthy. It is equivalent to `assert.equal(!!value, true, message)`.
+ *
+ * If `value` is not truthy, an `AssertionError` is thrown with a `message` property set equal to the value of the `message` parameter. If the `message` parameter is `undefined`, a default
+ * error message is assigned. If the `message` parameter is an instance of an `Error` then it will be thrown instead of the `AssertionError`.
+ * If no arguments are passed in at all `message` will be set to the string:`` 'No value argument passed to `assert.ok()`' ``.
+ *
+ * Be aware that in the `repl` the error message will be different to the one
+ * thrown in a file! See below for further details.
+ *
+ * ```js
+ * import assert from 'node:assert/strict';
+ *
+ * assert.ok(true);
+ * // OK
+ * assert.ok(1);
+ * // OK
+ *
+ * assert.ok();
+ * // AssertionError: No value argument passed to `assert.ok()`
+ *
+ * assert.ok(false, 'it\'s false');
+ * // AssertionError: it's false
+ *
+ * // In the repl:
+ * assert.ok(typeof 123 === 'string');
+ * // AssertionError: false == true
+ *
+ * // In a file (e.g. test.js):
+ * assert.ok(typeof 123 === 'string');
+ * // AssertionError: The expression evaluated to a falsy value:
+ * //
+ * // assert.ok(typeof 123 === 'string')
+ *
+ * assert.ok(false);
+ * // AssertionError: The expression evaluated to a falsy value:
+ * //
+ * // assert.ok(false)
+ *
+ * assert.ok(0);
+ * // AssertionError: The expression evaluated to a falsy value:
+ * //
+ * // assert.ok(0)
+ * ```
+ *
+ * ```js
+ * import assert from 'node:assert/strict';
+ *
+ * // Using `assert()` works the same:
+ * assert(0);
+ * // AssertionError: The expression evaluated to a falsy value:
+ * //
+ * // assert(0)
+ * ```
+ * @since v0.1.21
+ */
+ function ok(value: unknown, message?: string | Error): asserts value;
+ /**
+ * **Strict assertion mode**
+ *
+ * An alias of {@link strictEqual}.
+ *
+ * **Legacy assertion mode**
+ *
+ * > Stability: 3 - Legacy: Use {@link strictEqual} instead.
+ *
+ * Tests shallow, coercive equality between the `actual` and `expected` parameters
+ * using the [`==` operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Equality). `NaN` is specially handled
+ * and treated as being identical if both sides are `NaN`.
+ *
+ * ```js
+ * import assert from 'node:assert';
+ *
+ * assert.equal(1, 1);
+ * // OK, 1 == 1
+ * assert.equal(1, '1');
+ * // OK, 1 == '1'
+ * assert.equal(NaN, NaN);
+ * // OK
+ *
+ * assert.equal(1, 2);
+ * // AssertionError: 1 == 2
+ * assert.equal({ a: { b: 1 } }, { a: { b: 1 } });
+ * // AssertionError: { a: { b: 1 } } == { a: { b: 1 } }
+ * ```
+ *
+ * If the values are not equal, an `AssertionError` is thrown with a `message` property set equal to the value of the `message` parameter. If the `message` parameter is undefined, a default
+ * error message is assigned. If the `message` parameter is an instance of an `Error` then it will be thrown instead of the `AssertionError`.
+ * @since v0.1.21
+ */
+ function equal(actual: unknown, expected: unknown, message?: string | Error): void;
+ /**
+ * **Strict assertion mode**
+ *
+ * An alias of {@link notStrictEqual}.
+ *
+ * **Legacy assertion mode**
+ *
+ * > Stability: 3 - Legacy: Use {@link notStrictEqual} instead.
+ *
+ * Tests shallow, coercive inequality with the [`!=` operator](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Operators/Inequality). `NaN` is
+ * specially handled and treated as being identical if both sides are `NaN`.
+ *
+ * ```js
+ * import assert from 'node:assert';
+ *
+ * assert.notEqual(1, 2);
+ * // OK
+ *
+ * assert.notEqual(1, 1);
+ * // AssertionError: 1 != 1
+ *
+ * assert.notEqual(1, '1');
+ * // AssertionError: 1 != '1'
+ * ```
+ *
+ * If the values are equal, an `AssertionError` is thrown with a `message` property set equal to the value of the `message` parameter. If the `message` parameter is undefined, a default error
+ * message is assigned. If the `message` parameter is an instance of an `Error` then it will be thrown instead of the `AssertionError`.
+ * @since v0.1.21
+ */
+ function notEqual(actual: unknown, expected: unknown, message?: string | Error): void;
+ /**
+ * **Strict assertion mode**
+ *
+ * An alias of {@link deepStrictEqual}.
+ *
+ * **Legacy assertion mode**
+ *
+ * > Stability: 3 - Legacy: Use {@link deepStrictEqual} instead.
+ *
+ * Tests for deep equality between the `actual` and `expected` parameters. Consider
+ * using {@link deepStrictEqual} instead. {@link deepEqual} can have
+ * surprising results.
+ *
+ * _Deep equality_ means that the enumerable "own" properties of child objects
+ * are also recursively evaluated by the following rules.
+ * @since v0.1.21
+ */
+ function deepEqual(actual: unknown, expected: unknown, message?: string | Error): void;
+ /**
+ * **Strict assertion mode**
+ *
+ * An alias of {@link notDeepStrictEqual}.
+ *
+ * **Legacy assertion mode**
+ *
+ * > Stability: 3 - Legacy: Use {@link notDeepStrictEqual} instead.
+ *
+ * Tests for any deep inequality. Opposite of {@link deepEqual}.
+ *
+ * ```js
+ * import assert from 'node:assert';
+ *
+ * const obj1 = {
+ * a: {
+ * b: 1,
+ * },
+ * };
+ * const obj2 = {
+ * a: {
+ * b: 2,
+ * },
+ * };
+ * const obj3 = {
+ * a: {
+ * b: 1,
+ * },
+ * };
+ * const obj4 = { __proto__: obj1 };
+ *
+ * assert.notDeepEqual(obj1, obj1);
+ * // AssertionError: { a: { b: 1 } } notDeepEqual { a: { b: 1 } }
+ *
+ * assert.notDeepEqual(obj1, obj2);
+ * // OK
+ *
+ * assert.notDeepEqual(obj1, obj3);
+ * // AssertionError: { a: { b: 1 } } notDeepEqual { a: { b: 1 } }
+ *
+ * assert.notDeepEqual(obj1, obj4);
+ * // OK
+ * ```
+ *
+ * If the values are deeply equal, an `AssertionError` is thrown with a `message` property set equal to the value of the `message` parameter. If the `message` parameter is undefined, a default
+ * error message is assigned. If the `message` parameter is an instance of an `Error` then it will be thrown
+ * instead of the `AssertionError`.
+ * @since v0.1.21
+ */
+ function notDeepEqual(actual: unknown, expected: unknown, message?: string | Error): void;
+ /**
+ * Tests strict equality between the `actual` and `expected` parameters as
+ * determined by [`Object.is()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is).
+ *
+ * ```js
+ * import assert from 'node:assert/strict';
+ *
+ * assert.strictEqual(1, 2);
+ * // AssertionError [ERR_ASSERTION]: Expected inputs to be strictly equal:
+ * //
+ * // 1 !== 2
+ *
+ * assert.strictEqual(1, 1);
+ * // OK
+ *
+ * assert.strictEqual('Hello foobar', 'Hello World!');
+ * // AssertionError [ERR_ASSERTION]: Expected inputs to be strictly equal:
+ * // + actual - expected
+ * //
+ * // + 'Hello foobar'
+ * // - 'Hello World!'
+ * // ^
+ *
+ * const apples = 1;
+ * const oranges = 2;
+ * assert.strictEqual(apples, oranges, `apples ${apples} !== oranges ${oranges}`);
+ * // AssertionError [ERR_ASSERTION]: apples 1 !== oranges 2
+ *
+ * assert.strictEqual(1, '1', new TypeError('Inputs are not identical'));
+ * // TypeError: Inputs are not identical
+ * ```
+ *
+ * If the values are not strictly equal, an `AssertionError` is thrown with a `message` property set equal to the value of the `message` parameter. If the `message` parameter is undefined, a
+ * default error message is assigned. If the `message` parameter is an instance of an `Error` then it will be thrown
+ * instead of the `AssertionError`.
+ * @since v0.1.21
+ */
+ function strictEqual(actual: unknown, expected: T, message?: string | Error): asserts actual is T;
+ /**
+ * Tests strict inequality between the `actual` and `expected` parameters as
+ * determined by [`Object.is()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/is).
+ *
+ * ```js
+ * import assert from 'node:assert/strict';
+ *
+ * assert.notStrictEqual(1, 2);
+ * // OK
+ *
+ * assert.notStrictEqual(1, 1);
+ * // AssertionError [ERR_ASSERTION]: Expected "actual" to be strictly unequal to:
+ * //
+ * // 1
+ *
+ * assert.notStrictEqual(1, '1');
+ * // OK
+ * ```
+ *
+ * If the values are strictly equal, an `AssertionError` is thrown with a `message` property set equal to the value of the `message` parameter. If the `message` parameter is undefined, a
+ * default error message is assigned. If the `message` parameter is an instance of an `Error` then it will be thrown
+ * instead of the `AssertionError`.
+ * @since v0.1.21
+ */
+ function notStrictEqual(actual: unknown, expected: unknown, message?: string | Error): void;
+ /**
+ * Tests for deep equality between the `actual` and `expected` parameters.
+ * "Deep" equality means that the enumerable "own" properties of child objects
+ * are recursively evaluated also by the following rules.
+ * @since v1.2.0
+ */
+ function deepStrictEqual(actual: unknown, expected: T, message?: string | Error): asserts actual is T;
+ /**
+ * Tests for deep strict inequality. Opposite of {@link deepStrictEqual}.
+ *
+ * ```js
+ * import assert from 'node:assert/strict';
+ *
+ * assert.notDeepStrictEqual({ a: 1 }, { a: '1' });
+ * // OK
+ * ```
+ *
+ * If the values are deeply and strictly equal, an `AssertionError` is thrown
+ * with a `message` property set equal to the value of the `message` parameter. If
+ * the `message` parameter is undefined, a default error message is assigned. If
+ * the `message` parameter is an instance of an `Error` then it will be thrown
+ * instead of the `AssertionError`.
+ * @since v1.2.0
+ */
+ function notDeepStrictEqual(actual: unknown, expected: unknown, message?: string | Error): void;
+ /**
+ * Expects the function `fn` to throw an error.
+ *
+ * If specified, `error` can be a [`Class`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes),
+ * [`RegExp`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions), a validation function,
+ * a validation object where each property will be tested for strict deep equality,
+ * or an instance of error where each property will be tested for strict deep
+ * equality including the non-enumerable `message` and `name` properties. When
+ * using an object, it is also possible to use a regular expression, when
+ * validating against a string property. See below for examples.
+ *
+ * If specified, `message` will be appended to the message provided by the `AssertionError` if the `fn` call fails to throw or in case the error validation
+ * fails.
+ *
+ * Custom validation object/error instance:
+ *
+ * ```js
+ * import assert from 'node:assert/strict';
+ *
+ * const err = new TypeError('Wrong value');
+ * err.code = 404;
+ * err.foo = 'bar';
+ * err.info = {
+ * nested: true,
+ * baz: 'text',
+ * };
+ * err.reg = /abc/i;
+ *
+ * assert.throws(
+ * () => {
+ * throw err;
+ * },
+ * {
+ * name: 'TypeError',
+ * message: 'Wrong value',
+ * info: {
+ * nested: true,
+ * baz: 'text',
+ * },
+ * // Only properties on the validation object will be tested for.
+ * // Using nested objects requires all properties to be present. Otherwise
+ * // the validation is going to fail.
+ * },
+ * );
+ *
+ * // Using regular expressions to validate error properties:
+ * assert.throws(
+ * () => {
+ * throw err;
+ * },
+ * {
+ * // The `name` and `message` properties are strings and using regular
+ * // expressions on those will match against the string. If they fail, an
+ * // error is thrown.
+ * name: /^TypeError$/,
+ * message: /Wrong/,
+ * foo: 'bar',
+ * info: {
+ * nested: true,
+ * // It is not possible to use regular expressions for nested properties!
+ * baz: 'text',
+ * },
+ * // The `reg` property contains a regular expression and only if the
+ * // validation object contains an identical regular expression, it is going
+ * // to pass.
+ * reg: /abc/i,
+ * },
+ * );
+ *
+ * // Fails due to the different `message` and `name` properties:
+ * assert.throws(
+ * () => {
+ * const otherErr = new Error('Not found');
+ * // Copy all enumerable properties from `err` to `otherErr`.
+ * for (const [key, value] of Object.entries(err)) {
+ * otherErr[key] = value;
+ * }
+ * throw otherErr;
+ * },
+ * // The error's `message` and `name` properties will also be checked when using
+ * // an error as validation object.
+ * err,
+ * );
+ * ```
+ *
+ * Validate instanceof using constructor:
+ *
+ * ```js
+ * import assert from 'node:assert/strict';
+ *
+ * assert.throws(
+ * () => {
+ * throw new Error('Wrong value');
+ * },
+ * Error,
+ * );
+ * ```
+ *
+ * Validate error message using [`RegExp`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions):
+ *
+ * Using a regular expression runs `.toString` on the error object, and will
+ * therefore also include the error name.
+ *
+ * ```js
+ * import assert from 'node:assert/strict';
+ *
+ * assert.throws(
+ * () => {
+ * throw new Error('Wrong value');
+ * },
+ * /^Error: Wrong value$/,
+ * );
+ * ```
+ *
+ * Custom error validation:
+ *
+ * The function must return `true` to indicate all internal validations passed.
+ * It will otherwise fail with an `AssertionError`.
+ *
+ * ```js
+ * import assert from 'node:assert/strict';
+ *
+ * assert.throws(
+ * () => {
+ * throw new Error('Wrong value');
+ * },
+ * (err) => {
+ * assert(err instanceof Error);
+ * assert(/value/.test(err));
+ * // Avoid returning anything from validation functions besides `true`.
+ * // Otherwise, it's not clear what part of the validation failed. Instead,
+ * // throw an error about the specific validation that failed (as done in this
+ * // example) and add as much helpful debugging information to that error as
+ * // possible.
+ * return true;
+ * },
+ * 'unexpected error',
+ * );
+ * ```
+ *
+ * `error` cannot be a string. If a string is provided as the second
+ * argument, then `error` is assumed to be omitted and the string will be used for `message` instead. This can lead to easy-to-miss mistakes. Using the same
+ * message as the thrown error message is going to result in an `ERR_AMBIGUOUS_ARGUMENT` error. Please read the example below carefully if using
+ * a string as the second argument gets considered:
+ *
+ * ```js
+ * import assert from 'node:assert/strict';
+ *
+ * function throwingFirst() {
+ * throw new Error('First');
+ * }
+ *
+ * function throwingSecond() {
+ * throw new Error('Second');
+ * }
+ *
+ * function notThrowing() {}
+ *
+ * // The second argument is a string and the input function threw an Error.
+ * // The first case will not throw as it does not match for the error message
+ * // thrown by the input function!
+ * assert.throws(throwingFirst, 'Second');
+ * // In the next example the message has no benefit over the message from the
+ * // error and since it is not clear if the user intended to actually match
+ * // against the error message, Node.js throws an `ERR_AMBIGUOUS_ARGUMENT` error.
+ * assert.throws(throwingSecond, 'Second');
+ * // TypeError [ERR_AMBIGUOUS_ARGUMENT]
+ *
+ * // The string is only used (as message) in case the function does not throw:
+ * assert.throws(notThrowing, 'Second');
+ * // AssertionError [ERR_ASSERTION]: Missing expected exception: Second
+ *
+ * // If it was intended to match for the error message do this instead:
+ * // It does not throw because the error messages match.
+ * assert.throws(throwingSecond, /Second$/);
+ *
+ * // If the error message does not match, an AssertionError is thrown.
+ * assert.throws(throwingFirst, /Second$/);
+ * // AssertionError [ERR_ASSERTION]
+ * ```
+ *
+ * Due to the confusing error-prone notation, avoid a string as the second
+ * argument.
+ * @since v0.1.21
+ */
+ function throws(block: () => unknown, message?: string | Error): void;
+ function throws(block: () => unknown, error: AssertPredicate, message?: string | Error): void;
+ /**
+ * Asserts that the function `fn` does not throw an error.
+ *
+ * Using `assert.doesNotThrow()` is actually not useful because there
+ * is no benefit in catching an error and then rethrowing it. Instead, consider
+ * adding a comment next to the specific code path that should not throw and keep
+ * error messages as expressive as possible.
+ *
+ * When `assert.doesNotThrow()` is called, it will immediately call the `fn` function.
+ *
+ * If an error is thrown and it is the same type as that specified by the `error` parameter, then an `AssertionError` is thrown. If the error is of a
+ * different type, or if the `error` parameter is undefined, the error is
+ * propagated back to the caller.
+ *
+ * If specified, `error` can be a [`Class`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes),
+ * [`RegExp`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions), or a validation
+ * function. See {@link throws} for more details.
+ *
+ * The following, for instance, will throw the `TypeError` because there is no
+ * matching error type in the assertion:
+ *
+ * ```js
+ * import assert from 'node:assert/strict';
+ *
+ * assert.doesNotThrow(
+ * () => {
+ * throw new TypeError('Wrong value');
+ * },
+ * SyntaxError,
+ * );
+ * ```
+ *
+ * However, the following will result in an `AssertionError` with the message
+ * 'Got unwanted exception...':
+ *
+ * ```js
+ * import assert from 'node:assert/strict';
+ *
+ * assert.doesNotThrow(
+ * () => {
+ * throw new TypeError('Wrong value');
+ * },
+ * TypeError,
+ * );
+ * ```
+ *
+ * If an `AssertionError` is thrown and a value is provided for the `message` parameter, the value of `message` will be appended to the `AssertionError` message:
+ *
+ * ```js
+ * import assert from 'node:assert/strict';
+ *
+ * assert.doesNotThrow(
+ * () => {
+ * throw new TypeError('Wrong value');
+ * },
+ * /Wrong value/,
+ * 'Whoops',
+ * );
+ * // Throws: AssertionError: Got unwanted exception: Whoops
+ * ```
+ * @since v0.1.21
+ */
+ function doesNotThrow(block: () => unknown, message?: string | Error): void;
+ function doesNotThrow(block: () => unknown, error: AssertPredicate, message?: string | Error): void;
+ /**
+ * Throws `value` if `value` is not `undefined` or `null`. This is useful when
+ * testing the `error` argument in callbacks. The stack trace contains all frames
+ * from the error passed to `ifError()` including the potential new frames for `ifError()` itself.
+ *
+ * ```js
+ * import assert from 'node:assert/strict';
+ *
+ * assert.ifError(null);
+ * // OK
+ * assert.ifError(0);
+ * // AssertionError [ERR_ASSERTION]: ifError got unwanted exception: 0
+ * assert.ifError('error');
+ * // AssertionError [ERR_ASSERTION]: ifError got unwanted exception: 'error'
+ * assert.ifError(new Error());
+ * // AssertionError [ERR_ASSERTION]: ifError got unwanted exception: Error
+ *
+ * // Create some random error frames.
+ * let err;
+ * (function errorFrame() {
+ * err = new Error('test error');
+ * })();
+ *
+ * (function ifErrorFrame() {
+ * assert.ifError(err);
+ * })();
+ * // AssertionError [ERR_ASSERTION]: ifError got unwanted exception: test error
+ * // at ifErrorFrame
+ * // at errorFrame
+ * ```
+ * @since v0.1.97
+ */
+ function ifError(value: unknown): asserts value is null | undefined;
+ /**
+ * Awaits the `asyncFn` promise or, if `asyncFn` is a function, immediately
+ * calls the function and awaits the returned promise to complete. It will then
+ * check that the promise is rejected.
+ *
+ * If `asyncFn` is a function and it throws an error synchronously, `assert.rejects()` will return a rejected `Promise` with that error. If the
+ * function does not return a promise, `assert.rejects()` will return a rejected `Promise` with an [ERR_INVALID_RETURN_VALUE](https://nodejs.org/docs/latest-v25.x/api/errors.html#err_invalid_return_value)
+ * error. In both cases the error handler is skipped.
+ *
+ * Besides the async nature to await the completion behaves identically to {@link throws}.
+ *
+ * If specified, `error` can be a [`Class`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes),
+ * [`RegExp`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions), a validation function,
+ * an object where each property will be tested for, or an instance of error where
+ * each property will be tested for including the non-enumerable `message` and `name` properties.
+ *
+ * If specified, `message` will be the message provided by the `{@link AssertionError}` if the `asyncFn` fails to reject.
+ *
+ * ```js
+ * import assert from 'node:assert/strict';
+ *
+ * await assert.rejects(
+ * async () => {
+ * throw new TypeError('Wrong value');
+ * },
+ * {
+ * name: 'TypeError',
+ * message: 'Wrong value',
+ * },
+ * );
+ * ```
+ *
+ * ```js
+ * import assert from 'node:assert/strict';
+ *
+ * await assert.rejects(
+ * async () => {
+ * throw new TypeError('Wrong value');
+ * },
+ * (err) => {
+ * assert.strictEqual(err.name, 'TypeError');
+ * assert.strictEqual(err.message, 'Wrong value');
+ * return true;
+ * },
+ * );
+ * ```
+ *
+ * ```js
+ * import assert from 'node:assert/strict';
+ *
+ * assert.rejects(
+ * Promise.reject(new Error('Wrong value')),
+ * Error,
+ * ).then(() => {
+ * // ...
+ * });
+ * ```
+ *
+ * `error` cannot be a string. If a string is provided as the second argument, then `error` is assumed to
+ * be omitted and the string will be used for `message` instead. This can lead to easy-to-miss mistakes. Please read the
+ * example in {@link throws} carefully if using a string as the second argument gets considered.
+ * @since v10.0.0
+ */
+ function rejects(block: (() => Promise) | Promise, message?: string | Error): Promise;
+ function rejects(
+ block: (() => Promise) | Promise,
+ error: AssertPredicate,
+ message?: string | Error,
+ ): Promise;
+ /**
+ * Awaits the `asyncFn` promise or, if `asyncFn` is a function, immediately
+ * calls the function and awaits the returned promise to complete. It will then
+ * check that the promise is not rejected.
+ *
+ * If `asyncFn` is a function and it throws an error synchronously, `assert.doesNotReject()` will return a rejected `Promise` with that error. If
+ * the function does not return a promise, `assert.doesNotReject()` will return a
+ * rejected `Promise` with an [ERR_INVALID_RETURN_VALUE](https://nodejs.org/docs/latest-v25.x/api/errors.html#err_invalid_return_value) error. In both cases
+ * the error handler is skipped.
+ *
+ * Using `assert.doesNotReject()` is actually not useful because there is little
+ * benefit in catching a rejection and then rejecting it again. Instead, consider
+ * adding a comment next to the specific code path that should not reject and keep
+ * error messages as expressive as possible.
+ *
+ * If specified, `error` can be a [`Class`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Classes),
+ * [`RegExp`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Guide/Regular_Expressions), or a validation
+ * function. See {@link throws} for more details.
+ *
+ * Besides the async nature to await the completion behaves identically to {@link doesNotThrow}.
+ *
+ * ```js
+ * import assert from 'node:assert/strict';
+ *
+ * await assert.doesNotReject(
+ * async () => {
+ * throw new TypeError('Wrong value');
+ * },
+ * SyntaxError,
+ * );
+ * ```
+ *
+ * ```js
+ * import assert from 'node:assert/strict';
+ *
+ * assert.doesNotReject(Promise.reject(new TypeError('Wrong value')))
+ * .then(() => {
+ * // ...
+ * });
+ * ```
+ * @since v10.0.0
+ */
+ function doesNotReject(
+ block: (() => Promise) | Promise,
+ message?: string | Error,
+ ): Promise;
+ function doesNotReject(
+ block: (() => Promise) | Promise,
+ error: AssertPredicate,
+ message?: string | Error,
+ ): Promise;
+ /**
+ * Expects the `string` input to match the regular expression.
+ *
+ * ```js
+ * import assert from 'node:assert/strict';
+ *
+ * assert.match('I will fail', /pass/);
+ * // AssertionError [ERR_ASSERTION]: The input did not match the regular ...
+ *
+ * assert.match(123, /pass/);
+ * // AssertionError [ERR_ASSERTION]: The "string" argument must be of type string.
+ *
+ * assert.match('I will pass', /pass/);
+ * // OK
+ * ```
+ *
+ * If the values do not match, or if the `string` argument is of another type than `string`, an `{@link AssertionError}` is thrown with a `message` property set equal
+ * to the value of the `message` parameter. If the `message` parameter is
+ * undefined, a default error message is assigned. If the `message` parameter is an
+ * instance of an [Error](https://nodejs.org/docs/latest-v25.x/api/errors.html#class-error) then it will be thrown instead of the `{@link AssertionError}`.
+ * @since v13.6.0, v12.16.0
+ */
+ function match(value: string, regExp: RegExp, message?: string | Error): void;
+ /**
+ * Expects the `string` input not to match the regular expression.
+ *
+ * ```js
+ * import assert from 'node:assert/strict';
+ *
+ * assert.doesNotMatch('I will fail', /fail/);
+ * // AssertionError [ERR_ASSERTION]: The input was expected to not match the ...
+ *
+ * assert.doesNotMatch(123, /pass/);
+ * // AssertionError [ERR_ASSERTION]: The "string" argument must be of type string.
+ *
+ * assert.doesNotMatch('I will pass', /different/);
+ * // OK
+ * ```
+ *
+ * If the values do match, or if the `string` argument is of another type than `string`, an `{@link AssertionError}` is thrown with a `message` property set equal
+ * to the value of the `message` parameter. If the `message` parameter is
+ * undefined, a default error message is assigned. If the `message` parameter is an
+ * instance of an [Error](https://nodejs.org/docs/latest-v25.x/api/errors.html#class-error) then it will be thrown instead of the `{@link AssertionError}`.
+ * @since v13.6.0, v12.16.0
+ */
+ function doesNotMatch(value: string, regExp: RegExp, message?: string | Error): void;
+ /**
+ * Tests for partial deep equality between the `actual` and `expected` parameters.
+ * "Deep" equality means that the enumerable "own" properties of child objects
+ * are recursively evaluated also by the following rules. "Partial" equality means
+ * that only properties that exist on the `expected` parameter are going to be
+ * compared.
+ *
+ * This method always passes the same test cases as `assert.deepStrictEqual()`,
+ * behaving as a super set of it.
+ * @since v22.13.0
+ */
+ function partialDeepStrictEqual(actual: unknown, expected: unknown, message?: string | Error): void;
+ }
+ namespace assert {
+ export { strict };
+ }
+ export = assert;
+}
+declare module "assert" {
+ import assert = require("node:assert");
+ export = assert;
+}
diff --git a/.cursor/scripts/db-exec/node_modules/@types/node/assert/strict.d.ts b/.cursor/scripts/db-exec/node_modules/@types/node/assert/strict.d.ts
new file mode 100644
index 0000000..51bb352
--- /dev/null
+++ b/.cursor/scripts/db-exec/node_modules/@types/node/assert/strict.d.ts
@@ -0,0 +1,105 @@
+/**
+ * In strict assertion mode, non-strict methods behave like their corresponding
+ * strict methods. For example, `assert.deepEqual()` will behave like
+ * `assert.deepStrictEqual()`.
+ *
+ * In strict assertion mode, error messages for objects display a diff. In legacy
+ * assertion mode, error messages for objects display the objects, often truncated.
+ *
+ * To use strict assertion mode:
+ *
+ * ```js
+ * import { strict as assert } from 'node:assert';
+ * ```
+ *
+ * ```js
+ * import assert from 'node:assert/strict';
+ * ```
+ *
+ * Example error diff:
+ *
+ * ```js
+ * import { strict as assert } from 'node:assert';
+ *
+ * assert.deepEqual([[[1, 2, 3]], 4, 5], [[[1, 2, '3']], 4, 5]);
+ * // AssertionError: Expected inputs to be strictly deep-equal:
+ * // + actual - expected ... Lines skipped
+ * //
+ * // [
+ * // [
+ * // ...
+ * // 2,
+ * // + 3
+ * // - '3'
+ * // ],
+ * // ...
+ * // 5
+ * // ]
+ * ```
+ *
+ * To deactivate the colors, use the `NO_COLOR` or `NODE_DISABLE_COLORS`
+ * environment variables. This will also deactivate the colors in the REPL. For
+ * more on color support in terminal environments, read the tty
+ * [`getColorDepth()`](https://nodejs.org/docs/latest-v25.x/api/tty.html#writestreamgetcolordepthenv) documentation.
+ * @since v15.0.0
+ * @see [source](https://github.com/nodejs/node/blob/v25.x/lib/assert/strict.js)
+ */
+declare module "node:assert/strict" {
+ import {
+ Assert,
+ AssertionError,
+ AssertionErrorOptions,
+ AssertOptions,
+ AssertPredicate,
+ AssertStrict,
+ deepStrictEqual,
+ doesNotMatch,
+ doesNotReject,
+ doesNotThrow,
+ fail,
+ ifError,
+ match,
+ notDeepStrictEqual,
+ notStrictEqual,
+ ok,
+ partialDeepStrictEqual,
+ rejects,
+ strictEqual,
+ throws,
+ } from "node:assert";
+ function strict(value: unknown, message?: string | Error): asserts value;
+ namespace strict {
+ export {
+ Assert,
+ AssertionError,
+ AssertionErrorOptions,
+ AssertOptions,
+ AssertPredicate,
+ AssertStrict,
+ deepStrictEqual,
+ deepStrictEqual as deepEqual,
+ doesNotMatch,
+ doesNotReject,
+ doesNotThrow,
+ fail,
+ ifError,
+ match,
+ notDeepStrictEqual,
+ notDeepStrictEqual as notDeepEqual,
+ notStrictEqual,
+ notStrictEqual as notEqual,
+ ok,
+ partialDeepStrictEqual,
+ rejects,
+ strict,
+ strictEqual,
+ strictEqual as equal,
+ throws,
+ };
+ }
+ export = strict;
+}
+declare module "assert/strict" {
+ import strict = require("node:assert/strict");
+ export = strict;
+}
diff --git a/.cursor/scripts/db-exec/node_modules/@types/node/async_hooks.d.ts b/.cursor/scripts/db-exec/node_modules/@types/node/async_hooks.d.ts
new file mode 100644
index 0000000..aa692c1
--- /dev/null
+++ b/.cursor/scripts/db-exec/node_modules/@types/node/async_hooks.d.ts
@@ -0,0 +1,623 @@
+/**
+ * We strongly discourage the use of the `async_hooks` API.
+ * Other APIs that can cover most of its use cases include:
+ *
+ * * [`AsyncLocalStorage`](https://nodejs.org/docs/latest-v25.x/api/async_context.html#class-asynclocalstorage) tracks async context
+ * * [`process.getActiveResourcesInfo()`](https://nodejs.org/docs/latest-v25.x/api/process.html#processgetactiveresourcesinfo) tracks active resources
+ *
+ * The `node:async_hooks` module provides an API to track asynchronous resources.
+ * It can be accessed using:
+ *
+ * ```js
+ * import async_hooks from 'node:async_hooks';
+ * ```
+ * @experimental
+ * @see [source](https://github.com/nodejs/node/blob/v25.x/lib/async_hooks.js)
+ */
+declare module "node:async_hooks" {
+ /**
+ * ```js
+ * import { executionAsyncId } from 'node:async_hooks';
+ * import fs from 'node:fs';
+ *
+ * console.log(executionAsyncId()); // 1 - bootstrap
+ * const path = '.';
+ * fs.open(path, 'r', (err, fd) => {
+ * console.log(executionAsyncId()); // 6 - open()
+ * });
+ * ```
+ *
+ * The ID returned from `executionAsyncId()` is related to execution timing, not
+ * causality (which is covered by `triggerAsyncId()`):
+ *
+ * ```js
+ * const server = net.createServer((conn) => {
+ * // Returns the ID of the server, not of the new connection, because the
+ * // callback runs in the execution scope of the server's MakeCallback().
+ * async_hooks.executionAsyncId();
+ *
+ * }).listen(port, () => {
+ * // Returns the ID of a TickObject (process.nextTick()) because all
+ * // callbacks passed to .listen() are wrapped in a nextTick().
+ * async_hooks.executionAsyncId();
+ * });
+ * ```
+ *
+ * Promise contexts may not get precise `executionAsyncIds` by default.
+ * See the section on [promise execution tracking](https://nodejs.org/docs/latest-v25.x/api/async_hooks.html#promise-execution-tracking).
+ * @since v8.1.0
+ * @return The `asyncId` of the current execution context. Useful to track when something calls.
+ */
+ function executionAsyncId(): number;
+ /**
+ * Resource objects returned by `executionAsyncResource()` are most often internal
+ * Node.js handle objects with undocumented APIs. Using any functions or properties
+ * on the object is likely to crash your application and should be avoided.
+ *
+ * Using `executionAsyncResource()` in the top-level execution context will
+ * return an empty object as there is no handle or request object to use,
+ * but having an object representing the top-level can be helpful.
+ *
+ * ```js
+ * import { open } from 'node:fs';
+ * import { executionAsyncId, executionAsyncResource } from 'node:async_hooks';
+ *
+ * console.log(executionAsyncId(), executionAsyncResource()); // 1 {}
+ * open(new URL(import.meta.url), 'r', (err, fd) => {
+ * console.log(executionAsyncId(), executionAsyncResource()); // 7 FSReqWrap
+ * });
+ * ```
+ *
+ * This can be used to implement continuation local storage without the
+ * use of a tracking `Map` to store the metadata:
+ *
+ * ```js
+ * import { createServer } from 'node:http';
+ * import {
+ * executionAsyncId,
+ * executionAsyncResource,
+ * createHook,
+ * } from 'node:async_hooks';
+ * const sym = Symbol('state'); // Private symbol to avoid pollution
+ *
+ * createHook({
+ * init(asyncId, type, triggerAsyncId, resource) {
+ * const cr = executionAsyncResource();
+ * if (cr) {
+ * resource[sym] = cr[sym];
+ * }
+ * },
+ * }).enable();
+ *
+ * const server = createServer((req, res) => {
+ * executionAsyncResource()[sym] = { state: req.url };
+ * setTimeout(function() {
+ * res.end(JSON.stringify(executionAsyncResource()[sym]));
+ * }, 100);
+ * }).listen(3000);
+ * ```
+ * @since v13.9.0, v12.17.0
+ * @return The resource representing the current execution. Useful to store data within the resource.
+ */
+ function executionAsyncResource(): object;
+ /**
+ * ```js
+ * const server = net.createServer((conn) => {
+ * // The resource that caused (or triggered) this callback to be called
+ * // was that of the new connection. Thus the return value of triggerAsyncId()
+ * // is the asyncId of "conn".
+ * async_hooks.triggerAsyncId();
+ *
+ * }).listen(port, () => {
+ * // Even though all callbacks passed to .listen() are wrapped in a nextTick()
+ * // the callback itself exists because the call to the server's .listen()
+ * // was made. So the return value would be the ID of the server.
+ * async_hooks.triggerAsyncId();
+ * });
+ * ```
+ *
+ * Promise contexts may not get valid `triggerAsyncId`s by default. See
+ * the section on [promise execution tracking](https://nodejs.org/docs/latest-v25.x/api/async_hooks.html#promise-execution-tracking).
+ * @return The ID of the resource responsible for calling the callback that is currently being executed.
+ */
+ function triggerAsyncId(): number;
+ interface HookCallbacks {
+ /**
+ * Called when a class is constructed that has the possibility to emit an asynchronous event.
+ * @param asyncId A unique ID for the async resource
+ * @param type The type of the async resource
+ * @param triggerAsyncId The unique ID of the async resource in whose execution context this async resource was created
+ * @param resource Reference to the resource representing the async operation, needs to be released during destroy
+ */
+ init?(asyncId: number, type: string, triggerAsyncId: number, resource: object): void;
+ /**
+ * When an asynchronous operation is initiated or completes a callback is called to notify the user.
+ * The before callback is called just before said callback is executed.
+ * @param asyncId the unique identifier assigned to the resource about to execute the callback.
+ */
+ before?(asyncId: number): void;
+ /**
+ * Called immediately after the callback specified in `before` is completed.
+ *
+ * If an uncaught exception occurs during execution of the callback, then `after` will run after the `'uncaughtException'` event is emitted or a `domain`'s handler runs.
+ * @param asyncId the unique identifier assigned to the resource which has executed the callback.
+ */
+ after?(asyncId: number): void;
+ /**
+ * Called when a promise has resolve() called. This may not be in the same execution id
+ * as the promise itself.
+ * @param asyncId the unique id for the promise that was resolve()d.
+ */
+ promiseResolve?(asyncId: number): void;
+ /**
+ * Called after the resource corresponding to asyncId is destroyed
+ * @param asyncId a unique ID for the async resource
+ */
+ destroy?(asyncId: number): void;
+ }
+ interface AsyncHook {
+ /**
+ * Enable the callbacks for a given AsyncHook instance. If no callbacks are provided enabling is a noop.
+ */
+ enable(): this;
+ /**
+ * Disable the callbacks for a given AsyncHook instance from the global pool of AsyncHook callbacks to be executed. Once a hook has been disabled it will not be called again until enabled.
+ */
+ disable(): this;
+ }
+ /**
+ * Registers functions to be called for different lifetime events of each async
+ * operation.
+ *
+ * The callbacks `init()`/`before()`/`after()`/`destroy()` are called for the
+ * respective asynchronous event during a resource's lifetime.
+ *
+ * All callbacks are optional. For example, if only resource cleanup needs to
+ * be tracked, then only the `destroy` callback needs to be passed. The
+ * specifics of all functions that can be passed to `callbacks` is in the `Hook Callbacks` section.
+ *
+ * ```js
+ * import { createHook } from 'node:async_hooks';
+ *
+ * const asyncHook = createHook({
+ * init(asyncId, type, triggerAsyncId, resource) { },
+ * destroy(asyncId) { },
+ * });
+ * ```
+ *
+ * The callbacks will be inherited via the prototype chain:
+ *
+ * ```js
+ * class MyAsyncCallbacks {
+ * init(asyncId, type, triggerAsyncId, resource) { }
+ * destroy(asyncId) {}
+ * }
+ *
+ * class MyAddedCallbacks extends MyAsyncCallbacks {
+ * before(asyncId) { }
+ * after(asyncId) { }
+ * }
+ *
+ * const asyncHook = async_hooks.createHook(new MyAddedCallbacks());
+ * ```
+ *
+ * Because promises are asynchronous resources whose lifecycle is tracked
+ * via the async hooks mechanism, the `init()`, `before()`, `after()`, and`destroy()` callbacks _must not_ be async functions that return promises.
+ * @since v8.1.0
+ * @param callbacks The `Hook Callbacks` to register
+ * @return Instance used for disabling and enabling hooks
+ */
+ function createHook(callbacks: HookCallbacks): AsyncHook;
+ interface AsyncResourceOptions {
+ /**
+ * The ID of the execution context that created this async event.
+ * @default executionAsyncId()
+ */
+ triggerAsyncId?: number | undefined;
+ /**
+ * Disables automatic `emitDestroy` when the object is garbage collected.
+ * This usually does not need to be set (even if `emitDestroy` is called
+ * manually), unless the resource's `asyncId` is retrieved and the
+ * sensitive API's `emitDestroy` is called with it.
+ * @default false
+ */
+ requireManualDestroy?: boolean | undefined;
+ }
+ /**
+ * The class `AsyncResource` is designed to be extended by the embedder's async
+ * resources. Using this, users can easily trigger the lifetime events of their
+ * own resources.
+ *
+ * The `init` hook will trigger when an `AsyncResource` is instantiated.
+ *
+ * The following is an overview of the `AsyncResource` API.
+ *
+ * ```js
+ * import { AsyncResource, executionAsyncId } from 'node:async_hooks';
+ *
+ * // AsyncResource() is meant to be extended. Instantiating a
+ * // new AsyncResource() also triggers init. If triggerAsyncId is omitted then
+ * // async_hook.executionAsyncId() is used.
+ * const asyncResource = new AsyncResource(
+ * type, { triggerAsyncId: executionAsyncId(), requireManualDestroy: false },
+ * );
+ *
+ * // Run a function in the execution context of the resource. This will
+ * // * establish the context of the resource
+ * // * trigger the AsyncHooks before callbacks
+ * // * call the provided function `fn` with the supplied arguments
+ * // * trigger the AsyncHooks after callbacks
+ * // * restore the original execution context
+ * asyncResource.runInAsyncScope(fn, thisArg, ...args);
+ *
+ * // Call AsyncHooks destroy callbacks.
+ * asyncResource.emitDestroy();
+ *
+ * // Return the unique ID assigned to the AsyncResource instance.
+ * asyncResource.asyncId();
+ *
+ * // Return the trigger ID for the AsyncResource instance.
+ * asyncResource.triggerAsyncId();
+ * ```
+ */
+ class AsyncResource {
+ /**
+ * AsyncResource() is meant to be extended. Instantiating a
+ * new AsyncResource() also triggers init. If triggerAsyncId is omitted then
+ * async_hook.executionAsyncId() is used.
+ * @param type The type of async event.
+ * @param triggerAsyncId The ID of the execution context that created
+ * this async event (default: `executionAsyncId()`), or an
+ * AsyncResourceOptions object (since v9.3.0)
+ */
+ constructor(type: string, triggerAsyncId?: number | AsyncResourceOptions);
+ /**
+ * Binds the given function to the current execution context.
+ * @since v14.8.0, v12.19.0
+ * @param fn The function to bind to the current execution context.
+ * @param type An optional name to associate with the underlying `AsyncResource`.
+ */
+ static bind any, ThisArg>(
+ fn: Func,
+ type?: string,
+ thisArg?: ThisArg,
+ ): Func;
+ /**
+ * Binds the given function to execute to this `AsyncResource`'s scope.
+ * @since v14.8.0, v12.19.0
+ * @param fn The function to bind to the current `AsyncResource`.
+ */
+ bind any>(fn: Func): Func;
+ /**
+ * Call the provided function with the provided arguments in the execution context
+ * of the async resource. This will establish the context, trigger the AsyncHooks
+ * before callbacks, call the function, trigger the AsyncHooks after callbacks, and
+ * then restore the original execution context.
+ * @since v9.6.0
+ * @param fn The function to call in the execution context of this async resource.
+ * @param thisArg The receiver to be used for the function call.
+ * @param args Optional arguments to pass to the function.
+ */
+ runInAsyncScope(
+ fn: (this: This, ...args: any[]) => Result,
+ thisArg?: This,
+ ...args: any[]
+ ): Result;
+ /**
+ * Call all `destroy` hooks. This should only ever be called once. An error will
+ * be thrown if it is called more than once. This **must** be manually called. If
+ * the resource is left to be collected by the GC then the `destroy` hooks will
+ * never be called.
+ * @return A reference to `asyncResource`.
+ */
+ emitDestroy(): this;
+ /**
+ * @return The unique `asyncId` assigned to the resource.
+ */
+ asyncId(): number;
+ /**
+ * @return The same `triggerAsyncId` that is passed to the `AsyncResource` constructor.
+ */
+ triggerAsyncId(): number;
+ }
+ interface AsyncLocalStorageOptions {
+ /**
+ * The default value to be used when no store is provided.
+ */
+ defaultValue?: any;
+ /**
+ * A name for the `AsyncLocalStorage` value.
+ */
+ name?: string | undefined;
+ }
+ /**
+ * This class creates stores that stay coherent through asynchronous operations.
+ *
+ * While you can create your own implementation on top of the `node:async_hooks` module, `AsyncLocalStorage` should be preferred as it is a performant and memory
+ * safe implementation that involves significant optimizations that are non-obvious
+ * to implement.
+ *
+ * The following example uses `AsyncLocalStorage` to build a simple logger
+ * that assigns IDs to incoming HTTP requests and includes them in messages
+ * logged within each request.
+ *
+ * ```js
+ * import http from 'node:http';
+ * import { AsyncLocalStorage } from 'node:async_hooks';
+ *
+ * const asyncLocalStorage = new AsyncLocalStorage();
+ *
+ * function logWithId(msg) {
+ * const id = asyncLocalStorage.getStore();
+ * console.log(`${id !== undefined ? id : '-'}:`, msg);
+ * }
+ *
+ * let idSeq = 0;
+ * http.createServer((req, res) => {
+ * asyncLocalStorage.run(idSeq++, () => {
+ * logWithId('start');
+ * // Imagine any chain of async operations here
+ * setImmediate(() => {
+ * logWithId('finish');
+ * res.end();
+ * });
+ * });
+ * }).listen(8080);
+ *
+ * http.get('http://localhost:8080');
+ * http.get('http://localhost:8080');
+ * // Prints:
+ * // 0: start
+ * // 0: finish
+ * // 1: start
+ * // 1: finish
+ * ```
+ *
+ * Each instance of `AsyncLocalStorage` maintains an independent storage context.
+ * Multiple instances can safely exist simultaneously without risk of interfering
+ * with each other's data.
+ * @since v13.10.0, v12.17.0
+ */
+ class AsyncLocalStorage {
+ /**
+ * Creates a new instance of `AsyncLocalStorage`. Store is only provided within a
+ * `run()` call or after an `enterWith()` call.
+ */
+ constructor(options?: AsyncLocalStorageOptions);
+ /**
+ * Binds the given function to the current execution context.
+ * @since v19.8.0
+ * @param fn The function to bind to the current execution context.
+ * @return A new function that calls `fn` within the captured execution context.
+ */
+ static bind any>(fn: Func): Func;
+ /**
+ * Captures the current execution context and returns a function that accepts a
+ * function as an argument. Whenever the returned function is called, it
+ * calls the function passed to it within the captured context.
+ *
+ * ```js
+ * const asyncLocalStorage = new AsyncLocalStorage();
+ * const runInAsyncScope = asyncLocalStorage.run(123, () => AsyncLocalStorage.snapshot());
+ * const result = asyncLocalStorage.run(321, () => runInAsyncScope(() => asyncLocalStorage.getStore()));
+ * console.log(result); // returns 123
+ * ```
+ *
+ * AsyncLocalStorage.snapshot() can replace the use of AsyncResource for simple
+ * async context tracking purposes, for example:
+ *
+ * ```js
+ * class Foo {
+ * #runInAsyncScope = AsyncLocalStorage.snapshot();
+ *
+ * get() { return this.#runInAsyncScope(() => asyncLocalStorage.getStore()); }
+ * }
+ *
+ * const foo = asyncLocalStorage.run(123, () => new Foo());
+ * console.log(asyncLocalStorage.run(321, () => foo.get())); // returns 123
+ * ```
+ * @since v19.8.0
+ * @return A new function with the signature `(fn: (...args) : R, ...args) : R`.
+ */
+ static snapshot(): (fn: (...args: TArgs) => R, ...args: TArgs) => R;
+ /**
+ * Disables the instance of `AsyncLocalStorage`. All subsequent calls
+ * to `asyncLocalStorage.getStore()` will return `undefined` until `asyncLocalStorage.run()` or `asyncLocalStorage.enterWith()` is called again.
+ *
+ * When calling `asyncLocalStorage.disable()`, all current contexts linked to the
+ * instance will be exited.
+ *
+ * Calling `asyncLocalStorage.disable()` is required before the `asyncLocalStorage` can be garbage collected. This does not apply to stores
+ * provided by the `asyncLocalStorage`, as those objects are garbage collected
+ * along with the corresponding async resources.
+ *
+ * Use this method when the `asyncLocalStorage` is not in use anymore
+ * in the current process.
+ * @since v13.10.0, v12.17.0
+ * @experimental
+ */
+ disable(): void;
+ /**
+ * Returns the current store.
+ * If called outside of an asynchronous context initialized by
+ * calling `asyncLocalStorage.run()` or `asyncLocalStorage.enterWith()`, it
+ * returns `undefined`.
+ * @since v13.10.0, v12.17.0
+ */
+ getStore(): T | undefined;
+ /**
+ * The name of the `AsyncLocalStorage` instance if provided.
+ * @since v24.0.0
+ */
+ readonly name: string;
+ /**
+ * Runs a function synchronously within a context and returns its
+ * return value. The store is not accessible outside of the callback function.
+ * The store is accessible to any asynchronous operations created within the
+ * callback.
+ *
+ * The optional `args` are passed to the callback function.
+ *
+ * If the callback function throws an error, the error is thrown by `run()` too.
+ * The stacktrace is not impacted by this call and the context is exited.
+ *
+ * Example:
+ *
+ * ```js
+ * const store = { id: 2 };
+ * try {
+ * asyncLocalStorage.run(store, () => {
+ * asyncLocalStorage.getStore(); // Returns the store object
+ * setTimeout(() => {
+ * asyncLocalStorage.getStore(); // Returns the store object
+ * }, 200);
+ * throw new Error();
+ * });
+ * } catch (e) {
+ * asyncLocalStorage.getStore(); // Returns undefined
+ * // The error will be caught here
+ * }
+ * ```
+ * @since v13.10.0, v12.17.0
+ */
+ run(store: T, callback: () => R): R;
+ run(store: T, callback: (...args: TArgs) => R, ...args: TArgs): R;
+ /**
+ * Runs a function synchronously outside of a context and returns its
+ * return value. The store is not accessible within the callback function or
+ * the asynchronous operations created within the callback. Any `getStore()` call done within the callback function will always return `undefined`.
+ *
+ * The optional `args` are passed to the callback function.
+ *
+ * If the callback function throws an error, the error is thrown by `exit()` too.
+ * The stacktrace is not impacted by this call and the context is re-entered.
+ *
+ * Example:
+ *
+ * ```js
+ * // Within a call to run
+ * try {
+ * asyncLocalStorage.getStore(); // Returns the store object or value
+ * asyncLocalStorage.exit(() => {
+ * asyncLocalStorage.getStore(); // Returns undefined
+ * throw new Error();
+ * });
+ * } catch (e) {
+ * asyncLocalStorage.getStore(); // Returns the same object or value
+ * // The error will be caught here
+ * }
+ * ```
+ * @since v13.10.0, v12.17.0
+ * @experimental
+ */
+ exit(callback: (...args: TArgs) => R, ...args: TArgs): R;
+ /**
+ * Transitions into the context for the remainder of the current
+ * synchronous execution and then persists the store through any following
+ * asynchronous calls.
+ *
+ * Example:
+ *
+ * ```js
+ * const store = { id: 1 };
+ * // Replaces previous store with the given store object
+ * asyncLocalStorage.enterWith(store);
+ * asyncLocalStorage.getStore(); // Returns the store object
+ * someAsyncOperation(() => {
+ * asyncLocalStorage.getStore(); // Returns the same object
+ * });
+ * ```
+ *
+ * This transition will continue for the _entire_ synchronous execution.
+ * This means that if, for example, the context is entered within an event
+ * handler subsequent event handlers will also run within that context unless
+ * specifically bound to another context with an `AsyncResource`. That is why `run()` should be preferred over `enterWith()` unless there are strong reasons
+ * to use the latter method.
+ *
+ * ```js
+ * const store = { id: 1 };
+ *
+ * emitter.on('my-event', () => {
+ * asyncLocalStorage.enterWith(store);
+ * });
+ * emitter.on('my-event', () => {
+ * asyncLocalStorage.getStore(); // Returns the same object
+ * });
+ *
+ * asyncLocalStorage.getStore(); // Returns undefined
+ * emitter.emit('my-event');
+ * asyncLocalStorage.getStore(); // Returns the same object
+ * ```
+ * @since v13.11.0, v12.17.0
+ * @experimental
+ */
+ enterWith(store: T): void;
+ }
+ /**
+ * @since v17.2.0, v16.14.0
+ * @return A map of provider types to the corresponding numeric id.
+ * This map contains all the event types that might be emitted by the `async_hooks.init()` event.
+ */
+ namespace asyncWrapProviders {
+ const NONE: number;
+ const DIRHANDLE: number;
+ const DNSCHANNEL: number;
+ const ELDHISTOGRAM: number;
+ const FILEHANDLE: number;
+ const FILEHANDLECLOSEREQ: number;
+ const FIXEDSIZEBLOBCOPY: number;
+ const FSEVENTWRAP: number;
+ const FSREQCALLBACK: number;
+ const FSREQPROMISE: number;
+ const GETADDRINFOREQWRAP: number;
+ const GETNAMEINFOREQWRAP: number;
+ const HEAPSNAPSHOT: number;
+ const HTTP2SESSION: number;
+ const HTTP2STREAM: number;
+ const HTTP2PING: number;
+ const HTTP2SETTINGS: number;
+ const HTTPINCOMINGMESSAGE: number;
+ const HTTPCLIENTREQUEST: number;
+ const JSSTREAM: number;
+ const JSUDPWRAP: number;
+ const MESSAGEPORT: number;
+ const PIPECONNECTWRAP: number;
+ const PIPESERVERWRAP: number;
+ const PIPEWRAP: number;
+ const PROCESSWRAP: number;
+ const PROMISE: number;
+ const QUERYWRAP: number;
+ const SHUTDOWNWRAP: number;
+ const SIGNALWRAP: number;
+ const STATWATCHER: number;
+ const STREAMPIPE: number;
+ const TCPCONNECTWRAP: number;
+ const TCPSERVERWRAP: number;
+ const TCPWRAP: number;
+ const TTYWRAP: number;
+ const UDPSENDWRAP: number;
+ const UDPWRAP: number;
+ const SIGINTWATCHDOG: number;
+ const WORKER: number;
+ const WORKERHEAPSNAPSHOT: number;
+ const WRITEWRAP: number;
+ const ZLIB: number;
+ const CHECKPRIMEREQUEST: number;
+ const PBKDF2REQUEST: number;
+ const KEYPAIRGENREQUEST: number;
+ const KEYGENREQUEST: number;
+ const KEYEXPORTREQUEST: number;
+ const CIPHERREQUEST: number;
+ const DERIVEBITSREQUEST: number;
+ const HASHREQUEST: number;
+ const RANDOMBYTESREQUEST: number;
+ const RANDOMPRIMEREQUEST: number;
+ const SCRYPTREQUEST: number;
+ const SIGNREQUEST: number;
+ const TLSWRAP: number;
+ const VERIFYREQUEST: number;
+ }
+}
+declare module "async_hooks" {
+ export * from "node:async_hooks";
+}
diff --git a/.cursor/scripts/db-exec/node_modules/@types/node/buffer.buffer.d.ts b/.cursor/scripts/db-exec/node_modules/@types/node/buffer.buffer.d.ts
new file mode 100644
index 0000000..a3c2304
--- /dev/null
+++ b/.cursor/scripts/db-exec/node_modules/@types/node/buffer.buffer.d.ts
@@ -0,0 +1,466 @@
+declare module "node:buffer" {
+ type ImplicitArrayBuffer> = T extends
+ { valueOf(): infer V extends ArrayBufferLike } ? V : T;
+ global {
+ interface BufferConstructor {
+ // see buffer.d.ts for implementation shared with all TypeScript versions
+
+ /**
+ * Allocates a new buffer containing the given {str}.
+ *
+ * @param str String to store in buffer.
+ * @param encoding encoding to use, optional. Default is 'utf8'
+ * @deprecated since v10.0.0 - Use `Buffer.from(string[, encoding])` instead.
+ */
+ new(str: string, encoding?: BufferEncoding): Buffer;
+ /**
+ * Allocates a new buffer of {size} octets.
+ *
+ * @param size count of octets to allocate.
+ * @deprecated since v10.0.0 - Use `Buffer.alloc()` instead (also see `Buffer.allocUnsafe()`).
+ */
+ new(size: number): Buffer;
+ /**
+ * Allocates a new buffer containing the given {array} of octets.
+ *
+ * @param array The octets to store.
+ * @deprecated since v10.0.0 - Use `Buffer.from(array)` instead.
+ */
+ new(array: ArrayLike): Buffer;
+ /**
+ * Produces a Buffer backed by the same allocated memory as
+ * the given {ArrayBuffer}/{SharedArrayBuffer}.
+ *
+ * @param arrayBuffer The ArrayBuffer with which to share memory.
+ * @deprecated since v10.0.0 - Use `Buffer.from(arrayBuffer[, byteOffset[, length]])` instead.
+ */
+ new(arrayBuffer: TArrayBuffer): Buffer;
+ /**
+ * Allocates a new `Buffer` using an `array` of bytes in the range `0` – `255`.
+ * Array entries outside that range will be truncated to fit into it.
+ *
+ * ```js
+ * import { Buffer } from 'node:buffer';
+ *
+ * // Creates a new Buffer containing the UTF-8 bytes of the string 'buffer'.
+ * const buf = Buffer.from([0x62, 0x75, 0x66, 0x66, 0x65, 0x72]);
+ * ```
+ *
+ * If `array` is an `Array`-like object (that is, one with a `length` property of
+ * type `number`), it is treated as if it is an array, unless it is a `Buffer` or
+ * a `Uint8Array`. This means all other `TypedArray` variants get treated as an
+ * `Array`. To create a `Buffer` from the bytes backing a `TypedArray`, use
+ * `Buffer.copyBytesFrom()`.
+ *
+ * A `TypeError` will be thrown if `array` is not an `Array` or another type
+ * appropriate for `Buffer.from()` variants.
+ *
+ * `Buffer.from(array)` and `Buffer.from(string)` may also use the internal
+ * `Buffer` pool like `Buffer.allocUnsafe()` does.
+ * @since v5.10.0
+ */
+ from(array: WithImplicitCoercion>): Buffer;
+ /**
+ * This creates a view of the `ArrayBuffer` without copying the underlying
+ * memory. For example, when passed a reference to the `.buffer` property of a
+ * `TypedArray` instance, the newly created `Buffer` will share the same
+ * allocated memory as the `TypedArray`'s underlying `ArrayBuffer`.
+ *
+ * ```js
+ * import { Buffer } from 'node:buffer';
+ *
+ * const arr = new Uint16Array(2);
+ *
+ * arr[0] = 5000;
+ * arr[1] = 4000;
+ *
+ * // Shares memory with `arr`.
+ * const buf = Buffer.from(arr.buffer);
+ *
+ * console.log(buf);
+ * // Prints:
+ *
+ * // Changing the original Uint16Array changes the Buffer also.
+ * arr[1] = 6000;
+ *
+ * console.log(buf);
+ * // Prints:
+ * ```
+ *
+ * The optional `byteOffset` and `length` arguments specify a memory range within
+ * the `arrayBuffer` that will be shared by the `Buffer`.
+ *
+ * ```js
+ * import { Buffer } from 'node:buffer';
+ *
+ * const ab = new ArrayBuffer(10);
+ * const buf = Buffer.from(ab, 0, 2);
+ *
+ * console.log(buf.length);
+ * // Prints: 2
+ * ```
+ *
+ * A `TypeError` will be thrown if `arrayBuffer` is not an `ArrayBuffer` or a
+ * `SharedArrayBuffer` or another type appropriate for `Buffer.from()`
+ * variants.
+ *
+ * It is important to remember that a backing `ArrayBuffer` can cover a range
+ * of memory that extends beyond the bounds of a `TypedArray` view. A new
+ * `Buffer` created using the `buffer` property of a `TypedArray` may extend
+ * beyond the range of the `TypedArray`:
+ *
+ * ```js
+ * import { Buffer } from 'node:buffer';
+ *
+ * const arrA = Uint8Array.from([0x63, 0x64, 0x65, 0x66]); // 4 elements
+ * const arrB = new Uint8Array(arrA.buffer, 1, 2); // 2 elements
+ * console.log(arrA.buffer === arrB.buffer); // true
+ *
+ * const buf = Buffer.from(arrB.buffer);
+ * console.log(buf);
+ * // Prints:
+ * ```
+ * @since v5.10.0
+ * @param arrayBuffer An `ArrayBuffer`, `SharedArrayBuffer`, for example the
+ * `.buffer` property of a `TypedArray`.
+ * @param byteOffset Index of first byte to expose. **Default:** `0`.
+ * @param length Number of bytes to expose. **Default:**
+ * `arrayBuffer.byteLength - byteOffset`.
+ */
+ from>(
+ arrayBuffer: TArrayBuffer,
+ byteOffset?: number,
+ length?: number,
+ ): Buffer>;
+ /**
+ * Creates a new `Buffer` containing `string`. The `encoding` parameter identifies
+ * the character encoding to be used when converting `string` into bytes.
+ *
+ * ```js
+ * import { Buffer } from 'node:buffer';
+ *
+ * const buf1 = Buffer.from('this is a tést');
+ * const buf2 = Buffer.from('7468697320697320612074c3a97374', 'hex');
+ *
+ * console.log(buf1.toString());
+ * // Prints: this is a tést
+ * console.log(buf2.toString());
+ * // Prints: this is a tést
+ * console.log(buf1.toString('latin1'));
+ * // Prints: this is a tést
+ * ```
+ *
+ * A `TypeError` will be thrown if `string` is not a string or another type
+ * appropriate for `Buffer.from()` variants.
+ *
+ * `Buffer.from(string)` may also use the internal `Buffer` pool like
+ * `Buffer.allocUnsafe()` does.
+ * @since v5.10.0
+ * @param string A string to encode.
+ * @param encoding The encoding of `string`. **Default:** `'utf8'`.
+ */
+ from(string: WithImplicitCoercion, encoding?: BufferEncoding): Buffer;
+ from(arrayOrString: WithImplicitCoercion | string>): Buffer;
+ /**
+ * Creates a new Buffer using the passed {data}
+ * @param values to create a new Buffer
+ */
+ of(...items: number[]): Buffer;
+ /**
+ * Returns a new `Buffer` which is the result of concatenating all the `Buffer` instances in the `list` together.
+ *
+ * If the list has no items, or if the `totalLength` is 0, then a new zero-length `Buffer` is returned.
+ *
+ * If `totalLength` is not provided, it is calculated from the `Buffer` instances
+ * in `list` by adding their lengths.
+ *
+ * If `totalLength` is provided, it is coerced to an unsigned integer. If the
+ * combined length of the `Buffer`s in `list` exceeds `totalLength`, the result is
+ * truncated to `totalLength`. If the combined length of the `Buffer`s in `list` is
+ * less than `totalLength`, the remaining space is filled with zeros.
+ *
+ * ```js
+ * import { Buffer } from 'node:buffer';
+ *
+ * // Create a single `Buffer` from a list of three `Buffer` instances.
+ *
+ * const buf1 = Buffer.alloc(10);
+ * const buf2 = Buffer.alloc(14);
+ * const buf3 = Buffer.alloc(18);
+ * const totalLength = buf1.length + buf2.length + buf3.length;
+ *
+ * console.log(totalLength);
+ * // Prints: 42
+ *
+ * const bufA = Buffer.concat([buf1, buf2, buf3], totalLength);
+ *
+ * console.log(bufA);
+ * // Prints:
+ * console.log(bufA.length);
+ * // Prints: 42
+ * ```
+ *
+ * `Buffer.concat()` may also use the internal `Buffer` pool like `Buffer.allocUnsafe()` does.
+ * @since v0.7.11
+ * @param list List of `Buffer` or {@link Uint8Array} instances to concatenate.
+ * @param totalLength Total length of the `Buffer` instances in `list` when concatenated.
+ */
+ concat(list: readonly Uint8Array[], totalLength?: number): Buffer;
+ /**
+ * Copies the underlying memory of `view` into a new `Buffer`.
+ *
+ * ```js
+ * const u16 = new Uint16Array([0, 0xffff]);
+ * const buf = Buffer.copyBytesFrom(u16, 1, 1);
+ * u16[1] = 0;
+ * console.log(buf.length); // 2
+ * console.log(buf[0]); // 255
+ * console.log(buf[1]); // 255
+ * ```
+ * @since v19.8.0
+ * @param view The {TypedArray} to copy.
+ * @param [offset=0] The starting offset within `view`.
+ * @param [length=view.length - offset] The number of elements from `view` to copy.
+ */
+ copyBytesFrom(view: NodeJS.TypedArray, offset?: number, length?: number): Buffer;
+ /**
+ * Allocates a new `Buffer` of `size` bytes. If `fill` is `undefined`, the`Buffer` will be zero-filled.
+ *
+ * ```js
+ * import { Buffer } from 'node:buffer';
+ *
+ * const buf = Buffer.alloc(5);
+ *
+ * console.log(buf);
+ * // Prints:
+ * ```
+ *
+ * If `size` is larger than {@link constants.MAX_LENGTH} or smaller than 0, `ERR_OUT_OF_RANGE` is thrown.
+ *
+ * If `fill` is specified, the allocated `Buffer` will be initialized by calling `buf.fill(fill)`.
+ *
+ * ```js
+ * import { Buffer } from 'node:buffer';
+ *
+ * const buf = Buffer.alloc(5, 'a');
+ *
+ * console.log(buf);
+ * // Prints:
+ * ```
+ *
+ * If both `fill` and `encoding` are specified, the allocated `Buffer` will be
+ * initialized by calling `buf.fill(fill, encoding)`.
+ *
+ * ```js
+ * import { Buffer } from 'node:buffer';
+ *
+ * const buf = Buffer.alloc(11, 'aGVsbG8gd29ybGQ=', 'base64');
+ *
+ * console.log(buf);
+ * // Prints:
+ * ```
+ *
+ * Calling `Buffer.alloc()` can be measurably slower than the alternative `Buffer.allocUnsafe()` but ensures that the newly created `Buffer` instance
+ * contents will never contain sensitive data from previous allocations, including
+ * data that might not have been allocated for `Buffer`s.
+ *
+ * A `TypeError` will be thrown if `size` is not a number.
+ * @since v5.10.0
+ * @param size The desired length of the new `Buffer`.
+ * @param [fill=0] A value to pre-fill the new `Buffer` with.
+ * @param [encoding='utf8'] If `fill` is a string, this is its encoding.
+ */
+ alloc(size: number, fill?: string | Uint8Array | number, encoding?: BufferEncoding): Buffer;
+ /**
+ * Allocates a new `Buffer` of `size` bytes. If `size` is larger than {@link constants.MAX_LENGTH} or smaller than 0, `ERR_OUT_OF_RANGE` is thrown.
+ *
+ * The underlying memory for `Buffer` instances created in this way is _not_
+ * _initialized_. The contents of the newly created `Buffer` are unknown and _may contain sensitive data_. Use `Buffer.alloc()` instead to initialize`Buffer` instances with zeroes.
+ *
+ * ```js
+ * import { Buffer } from 'node:buffer';
+ *
+ * const buf = Buffer.allocUnsafe(10);
+ *
+ * console.log(buf);
+ * // Prints (contents may vary):
+ *
+ * buf.fill(0);
+ *
+ * console.log(buf);
+ * // Prints:
+ * ```
+ *
+ * A `TypeError` will be thrown if `size` is not a number.
+ *
+ * The `Buffer` module pre-allocates an internal `Buffer` instance of
+ * size `Buffer.poolSize` that is used as a pool for the fast allocation of new `Buffer` instances created using `Buffer.allocUnsafe()`, `Buffer.from(array)`,
+ * and `Buffer.concat()` only when `size` is less than `Buffer.poolSize >>> 1` (floor of `Buffer.poolSize` divided by two).
+ *
+ * Use of this pre-allocated internal memory pool is a key difference between
+ * calling `Buffer.alloc(size, fill)` vs. `Buffer.allocUnsafe(size).fill(fill)`.
+ * Specifically, `Buffer.alloc(size, fill)` will _never_ use the internal `Buffer`pool, while `Buffer.allocUnsafe(size).fill(fill)`_will_ use the internal`Buffer` pool if `size` is less
+ * than or equal to half `Buffer.poolSize`. The
+ * difference is subtle but can be important when an application requires the
+ * additional performance that `Buffer.allocUnsafe()` provides.
+ * @since v5.10.0
+ * @param size The desired length of the new `Buffer`.
+ */
+ allocUnsafe(size: number): Buffer;
+ /**
+ * Allocates a new `Buffer` of `size` bytes. If `size` is larger than {@link constants.MAX_LENGTH} or smaller than 0, `ERR_OUT_OF_RANGE` is thrown. A zero-length `Buffer` is created if
+ * `size` is 0.
+ *
+ * The underlying memory for `Buffer` instances created in this way is _not_
+ * _initialized_. The contents of the newly created `Buffer` are unknown and _may contain sensitive data_. Use `buf.fill(0)` to initialize
+ * such `Buffer` instances with zeroes.
+ *
+ * When using `Buffer.allocUnsafe()` to allocate new `Buffer` instances,
+ * allocations under 4 KiB are sliced from a single pre-allocated `Buffer`. This
+ * allows applications to avoid the garbage collection overhead of creating many
+ * individually allocated `Buffer` instances. This approach improves both
+ * performance and memory usage by eliminating the need to track and clean up as
+ * many individual `ArrayBuffer` objects.
+ *
+ * However, in the case where a developer may need to retain a small chunk of
+ * memory from a pool for an indeterminate amount of time, it may be appropriate
+ * to create an un-pooled `Buffer` instance using `Buffer.allocUnsafeSlow()` and
+ * then copying out the relevant bits.
+ *
+ * ```js
+ * import { Buffer } from 'node:buffer';
+ *
+ * // Need to keep around a few small chunks of memory.
+ * const store = [];
+ *
+ * socket.on('readable', () => {
+ * let data;
+ * while (null !== (data = readable.read())) {
+ * // Allocate for retained data.
+ * const sb = Buffer.allocUnsafeSlow(10);
+ *
+ * // Copy the data into the new allocation.
+ * data.copy(sb, 0, 0, 10);
+ *
+ * store.push(sb);
+ * }
+ * });
+ * ```
+ *
+ * A `TypeError` will be thrown if `size` is not a number.
+ * @since v5.12.0
+ * @param size The desired length of the new `Buffer`.
+ */
+ allocUnsafeSlow(size: number): Buffer;
+ }
+ interface Buffer extends Uint8Array {
+ // see buffer.d.ts for implementation shared with all TypeScript versions
+
+ /**
+ * Returns a new `Buffer` that references the same memory as the original, but
+ * offset and cropped by the `start` and `end` indices.
+ *
+ * This method is not compatible with the `Uint8Array.prototype.slice()`,
+ * which is a superclass of `Buffer`. To copy the slice, use`Uint8Array.prototype.slice()`.
+ *
+ * ```js
+ * import { Buffer } from 'node:buffer';
+ *
+ * const buf = Buffer.from('buffer');
+ *
+ * const copiedBuf = Uint8Array.prototype.slice.call(buf);
+ * copiedBuf[0]++;
+ * console.log(copiedBuf.toString());
+ * // Prints: cuffer
+ *
+ * console.log(buf.toString());
+ * // Prints: buffer
+ *
+ * // With buf.slice(), the original buffer is modified.
+ * const notReallyCopiedBuf = buf.slice();
+ * notReallyCopiedBuf[0]++;
+ * console.log(notReallyCopiedBuf.toString());
+ * // Prints: cuffer
+ * console.log(buf.toString());
+ * // Also prints: cuffer (!)
+ * ```
+ * @since v0.3.0
+ * @deprecated Use `subarray` instead.
+ * @param [start=0] Where the new `Buffer` will start.
+ * @param [end=buf.length] Where the new `Buffer` will end (not inclusive).
+ */
+ slice(start?: number, end?: number): Buffer;
+ /**
+ * Returns a new `Buffer` that references the same memory as the original, but
+ * offset and cropped by the `start` and `end` indices.
+ *
+ * Specifying `end` greater than `buf.length` will return the same result as
+ * that of `end` equal to `buf.length`.
+ *
+ * This method is inherited from [`TypedArray.prototype.subarray()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray/subarray).
+ *
+ * Modifying the new `Buffer` slice will modify the memory in the original `Buffer`because the allocated memory of the two objects overlap.
+ *
+ * ```js
+ * import { Buffer } from 'node:buffer';
+ *
+ * // Create a `Buffer` with the ASCII alphabet, take a slice, and modify one byte
+ * // from the original `Buffer`.
+ *
+ * const buf1 = Buffer.allocUnsafe(26);
+ *
+ * for (let i = 0; i < 26; i++) {
+ * // 97 is the decimal ASCII value for 'a'.
+ * buf1[i] = i + 97;
+ * }
+ *
+ * const buf2 = buf1.subarray(0, 3);
+ *
+ * console.log(buf2.toString('ascii', 0, buf2.length));
+ * // Prints: abc
+ *
+ * buf1[0] = 33;
+ *
+ * console.log(buf2.toString('ascii', 0, buf2.length));
+ * // Prints: !bc
+ * ```
+ *
+ * Specifying negative indexes causes the slice to be generated relative to the
+ * end of `buf` rather than the beginning.
+ *
+ * ```js
+ * import { Buffer } from 'node:buffer';
+ *
+ * const buf = Buffer.from('buffer');
+ *
+ * console.log(buf.subarray(-6, -1).toString());
+ * // Prints: buffe
+ * // (Equivalent to buf.subarray(0, 5).)
+ *
+ * console.log(buf.subarray(-6, -2).toString());
+ * // Prints: buff
+ * // (Equivalent to buf.subarray(0, 4).)
+ *
+ * console.log(buf.subarray(-5, -2).toString());
+ * // Prints: uff
+ * // (Equivalent to buf.subarray(1, 4).)
+ * ```
+ * @since v3.0.0
+ * @param [start=0] Where the new `Buffer` will start.
+ * @param [end=buf.length] Where the new `Buffer` will end (not inclusive).
+ */
+ subarray(start?: number, end?: number): Buffer;
+ }
+ // TODO: remove globals in future version
+ /**
+ * @deprecated This is intended for internal use, and will be removed once `@types/node` no longer supports
+ * TypeScript versions earlier than 5.7.
+ */
+ type NonSharedBuffer = Buffer;
+ /**
+ * @deprecated This is intended for internal use, and will be removed once `@types/node` no longer supports
+ * TypeScript versions earlier than 5.7.
+ */
+ type AllowSharedBuffer = Buffer;
+ }
+}
diff --git a/.cursor/scripts/db-exec/node_modules/@types/node/buffer.d.ts b/.cursor/scripts/db-exec/node_modules/@types/node/buffer.d.ts
new file mode 100644
index 0000000..bb0f004
--- /dev/null
+++ b/.cursor/scripts/db-exec/node_modules/@types/node/buffer.d.ts
@@ -0,0 +1,1810 @@
+/**
+ * `Buffer` objects are used to represent a fixed-length sequence of bytes. Many
+ * Node.js APIs support `Buffer`s.
+ *
+ * The `Buffer` class is a subclass of JavaScript's [`Uint8Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array) class and
+ * extends it with methods that cover additional use cases. Node.js APIs accept
+ * plain [`Uint8Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array) s wherever `Buffer`s are supported as well.
+ *
+ * While the `Buffer` class is available within the global scope, it is still
+ * recommended to explicitly reference it via an import or require statement.
+ *
+ * ```js
+ * import { Buffer } from 'node:buffer';
+ *
+ * // Creates a zero-filled Buffer of length 10.
+ * const buf1 = Buffer.alloc(10);
+ *
+ * // Creates a Buffer of length 10,
+ * // filled with bytes which all have the value `1`.
+ * const buf2 = Buffer.alloc(10, 1);
+ *
+ * // Creates an uninitialized buffer of length 10.
+ * // This is faster than calling Buffer.alloc() but the returned
+ * // Buffer instance might contain old data that needs to be
+ * // overwritten using fill(), write(), or other functions that fill the Buffer's
+ * // contents.
+ * const buf3 = Buffer.allocUnsafe(10);
+ *
+ * // Creates a Buffer containing the bytes [1, 2, 3].
+ * const buf4 = Buffer.from([1, 2, 3]);
+ *
+ * // Creates a Buffer containing the bytes [1, 1, 1, 1] – the entries
+ * // are all truncated using `(value & 255)` to fit into the range 0–255.
+ * const buf5 = Buffer.from([257, 257.5, -255, '1']);
+ *
+ * // Creates a Buffer containing the UTF-8-encoded bytes for the string 'tést':
+ * // [0x74, 0xc3, 0xa9, 0x73, 0x74] (in hexadecimal notation)
+ * // [116, 195, 169, 115, 116] (in decimal notation)
+ * const buf6 = Buffer.from('tést');
+ *
+ * // Creates a Buffer containing the Latin-1 bytes [0x74, 0xe9, 0x73, 0x74].
+ * const buf7 = Buffer.from('tést', 'latin1');
+ * ```
+ * @see [source](https://github.com/nodejs/node/blob/v25.x/lib/buffer.js)
+ */
+declare module "node:buffer" {
+ import { ReadableStream } from "node:stream/web";
+ /**
+ * This function returns `true` if `input` contains only valid UTF-8-encoded data,
+ * including the case in which `input` is empty.
+ *
+ * Throws if the `input` is a detached array buffer.
+ * @since v19.4.0, v18.14.0
+ * @param input The input to validate.
+ */
+ export function isUtf8(input: ArrayBuffer | NodeJS.TypedArray): boolean;
+ /**
+ * This function returns `true` if `input` contains only valid ASCII-encoded data,
+ * including the case in which `input` is empty.
+ *
+ * Throws if the `input` is a detached array buffer.
+ * @since v19.6.0, v18.15.0
+ * @param input The input to validate.
+ */
+ export function isAscii(input: ArrayBuffer | NodeJS.TypedArray): boolean;
+ export let INSPECT_MAX_BYTES: number;
+ export const kMaxLength: number;
+ export const kStringMaxLength: number;
+ export const constants: {
+ MAX_LENGTH: number;
+ MAX_STRING_LENGTH: number;
+ };
+ export type TranscodeEncoding =
+ | "ascii"
+ | "utf8"
+ | "utf-8"
+ | "utf16le"
+ | "utf-16le"
+ | "ucs2"
+ | "ucs-2"
+ | "latin1"
+ | "binary";
+ /**
+ * Re-encodes the given `Buffer` or `Uint8Array` instance from one character
+ * encoding to another. Returns a new `Buffer` instance.
+ *
+ * Throws if the `fromEnc` or `toEnc` specify invalid character encodings or if
+ * conversion from `fromEnc` to `toEnc` is not permitted.
+ *
+ * Encodings supported by `buffer.transcode()` are: `'ascii'`, `'utf8'`, `'utf16le'`, `'ucs2'`, `'latin1'`, and `'binary'`.
+ *
+ * The transcoding process will use substitution characters if a given byte
+ * sequence cannot be adequately represented in the target encoding. For instance:
+ *
+ * ```js
+ * import { Buffer, transcode } from 'node:buffer';
+ *
+ * const newBuf = transcode(Buffer.from('€'), 'utf8', 'ascii');
+ * console.log(newBuf.toString('ascii'));
+ * // Prints: '?'
+ * ```
+ *
+ * Because the Euro (`€`) sign is not representable in US-ASCII, it is replaced
+ * with `?` in the transcoded `Buffer`.
+ * @since v7.1.0
+ * @param source A `Buffer` or `Uint8Array` instance.
+ * @param fromEnc The current encoding.
+ * @param toEnc To target encoding.
+ */
+ export function transcode(
+ source: Uint8Array,
+ fromEnc: TranscodeEncoding,
+ toEnc: TranscodeEncoding,
+ ): NonSharedBuffer;
+ /**
+ * Resolves a `'blob:nodedata:...'` an associated `Blob` object registered using
+ * a prior call to `URL.createObjectURL()`.
+ * @since v16.7.0
+ * @param id A `'blob:nodedata:...` URL string returned by a prior call to `URL.createObjectURL()`.
+ */
+ export function resolveObjectURL(id: string): Blob | undefined;
+ export { type AllowSharedBuffer, Buffer, type NonSharedBuffer };
+ /** @deprecated This alias will be removed in a future version. Use the canonical `BlobPropertyBag` instead. */
+ // TODO: remove in future major
+ export interface BlobOptions extends BlobPropertyBag {}
+ /** @deprecated This alias will be removed in a future version. Use the canonical `FilePropertyBag` instead. */
+ export interface FileOptions extends FilePropertyBag {}
+ export type WithImplicitCoercion =
+ | T
+ | { valueOf(): T }
+ | (T extends string ? { [Symbol.toPrimitive](hint: "string"): T } : never);
+ global {
+ namespace NodeJS {
+ export { BufferEncoding };
+ }
+ // Buffer class
+ type BufferEncoding =
+ | "ascii"
+ | "utf8"
+ | "utf-8"
+ | "utf16le"
+ | "utf-16le"
+ | "ucs2"
+ | "ucs-2"
+ | "base64"
+ | "base64url"
+ | "latin1"
+ | "binary"
+ | "hex";
+ /**
+ * Raw data is stored in instances of the Buffer class.
+ * A Buffer is similar to an array of integers but corresponds to a raw memory allocation outside the V8 heap. A Buffer cannot be resized.
+ * Valid string encodings: 'ascii'|'utf8'|'utf16le'|'ucs2'(alias of 'utf16le')|'base64'|'base64url'|'binary'(deprecated)|'hex'
+ */
+ interface BufferConstructor {
+ // see buffer.buffer.d.ts for implementation specific to TypeScript 5.7 and later
+ // see ts5.6/buffer.buffer.d.ts for implementation specific to TypeScript 5.6 and earlier
+
+ /**
+ * Returns `true` if `obj` is a `Buffer`, `false` otherwise.
+ *
+ * ```js
+ * import { Buffer } from 'node:buffer';
+ *
+ * Buffer.isBuffer(Buffer.alloc(10)); // true
+ * Buffer.isBuffer(Buffer.from('foo')); // true
+ * Buffer.isBuffer('a string'); // false
+ * Buffer.isBuffer([]); // false
+ * Buffer.isBuffer(new Uint8Array(1024)); // false
+ * ```
+ * @since v0.1.101
+ */
+ isBuffer(obj: any): obj is Buffer;
+ /**
+ * Returns `true` if `encoding` is the name of a supported character encoding,
+ * or `false` otherwise.
+ *
+ * ```js
+ * import { Buffer } from 'node:buffer';
+ *
+ * console.log(Buffer.isEncoding('utf8'));
+ * // Prints: true
+ *
+ * console.log(Buffer.isEncoding('hex'));
+ * // Prints: true
+ *
+ * console.log(Buffer.isEncoding('utf/8'));
+ * // Prints: false
+ *
+ * console.log(Buffer.isEncoding(''));
+ * // Prints: false
+ * ```
+ * @since v0.9.1
+ * @param encoding A character encoding name to check.
+ */
+ isEncoding(encoding: string): encoding is BufferEncoding;
+ /**
+ * Returns the byte length of a string when encoded using `encoding`.
+ * This is not the same as [`String.prototype.length`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/length), which does not account
+ * for the encoding that is used to convert the string into bytes.
+ *
+ * For `'base64'`, `'base64url'`, and `'hex'`, this function assumes valid input.
+ * For strings that contain non-base64/hex-encoded data (e.g. whitespace), the
+ * return value might be greater than the length of a `Buffer` created from the
+ * string.
+ *
+ * ```js
+ * import { Buffer } from 'node:buffer';
+ *
+ * const str = '\u00bd + \u00bc = \u00be';
+ *
+ * console.log(`${str}: ${str.length} characters, ` +
+ * `${Buffer.byteLength(str, 'utf8')} bytes`);
+ * // Prints: ½ + ¼ = ¾: 9 characters, 12 bytes
+ * ```
+ *
+ * When `string` is a
+ * `Buffer`/[`DataView`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/DataView)/[`TypedArray`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/-
+ * Reference/Global_Objects/TypedArray)/[`ArrayBuffer`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/ArrayBuffer)/[`SharedArrayBuffer`](https://develop-
+ * er.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/SharedArrayBuffer), the byte length as reported by `.byteLength`is returned.
+ * @since v0.1.90
+ * @param string A value to calculate the length of.
+ * @param [encoding='utf8'] If `string` is a string, this is its encoding.
+ * @return The number of bytes contained within `string`.
+ */
+ byteLength(
+ string: string | NodeJS.ArrayBufferView | ArrayBufferLike,
+ encoding?: BufferEncoding,
+ ): number;
+ /**
+ * Compares `buf1` to `buf2`, typically for the purpose of sorting arrays of `Buffer` instances. This is equivalent to calling `buf1.compare(buf2)`.
+ *
+ * ```js
+ * import { Buffer } from 'node:buffer';
+ *
+ * const buf1 = Buffer.from('1234');
+ * const buf2 = Buffer.from('0123');
+ * const arr = [buf1, buf2];
+ *
+ * console.log(arr.sort(Buffer.compare));
+ * // Prints: [ , ]
+ * // (This result is equal to: [buf2, buf1].)
+ * ```
+ * @since v0.11.13
+ * @return Either `-1`, `0`, or `1`, depending on the result of the comparison. See `compare` for details.
+ */
+ compare(buf1: Uint8Array, buf2: Uint8Array): -1 | 0 | 1;
+ /**
+ * This is the size (in bytes) of pre-allocated internal `Buffer` instances used
+ * for pooling. This value may be modified.
+ * @since v0.11.3
+ */
+ poolSize: number;
+ }
+ interface Buffer {
+ // see buffer.buffer.d.ts for implementation specific to TypeScript 5.7 and later
+ // see ts5.6/buffer.buffer.d.ts for implementation specific to TypeScript 5.6 and earlier
+
+ /**
+ * Writes `string` to `buf` at `offset` according to the character encoding in`encoding`. The `length` parameter is the number of bytes to write. If `buf` did
+ * not contain enough space to fit the entire string, only part of `string` will be
+ * written. However, partially encoded characters will not be written.
+ *
+ * ```js
+ * import { Buffer } from 'node:buffer';
+ *
+ * const buf = Buffer.alloc(256);
+ *
+ * const len = buf.write('\u00bd + \u00bc = \u00be', 0);
+ *
+ * console.log(`${len} bytes: ${buf.toString('utf8', 0, len)}`);
+ * // Prints: 12 bytes: ½ + ¼ = ¾
+ *
+ * const buffer = Buffer.alloc(10);
+ *
+ * const length = buffer.write('abcd', 8);
+ *
+ * console.log(`${length} bytes: ${buffer.toString('utf8', 8, 10)}`);
+ * // Prints: 2 bytes : ab
+ * ```
+ * @since v0.1.90
+ * @param string String to write to `buf`.
+ * @param [offset=0] Number of bytes to skip before starting to write `string`.
+ * @param [length=buf.length - offset] Maximum number of bytes to write (written bytes will not exceed `buf.length - offset`).
+ * @param [encoding='utf8'] The character encoding of `string`.
+ * @return Number of bytes written.
+ */
+ write(string: string, encoding?: BufferEncoding): number;
+ write(string: string, offset: number, encoding?: BufferEncoding): number;
+ write(string: string, offset: number, length: number, encoding?: BufferEncoding): number;
+ /**
+ * Decodes `buf` to a string according to the specified character encoding in`encoding`. `start` and `end` may be passed to decode only a subset of `buf`.
+ *
+ * If `encoding` is `'utf8'` and a byte sequence in the input is not valid UTF-8,
+ * then each invalid byte is replaced with the replacement character `U+FFFD`.
+ *
+ * The maximum length of a string instance (in UTF-16 code units) is available
+ * as {@link constants.MAX_STRING_LENGTH}.
+ *
+ * ```js
+ * import { Buffer } from 'node:buffer';
+ *
+ * const buf1 = Buffer.allocUnsafe(26);
+ *
+ * for (let i = 0; i < 26; i++) {
+ * // 97 is the decimal ASCII value for 'a'.
+ * buf1[i] = i + 97;
+ * }
+ *
+ * console.log(buf1.toString('utf8'));
+ * // Prints: abcdefghijklmnopqrstuvwxyz
+ * console.log(buf1.toString('utf8', 0, 5));
+ * // Prints: abcde
+ *
+ * const buf2 = Buffer.from('tést');
+ *
+ * console.log(buf2.toString('hex'));
+ * // Prints: 74c3a97374
+ * console.log(buf2.toString('utf8', 0, 3));
+ * // Prints: té
+ * console.log(buf2.toString(undefined, 0, 3));
+ * // Prints: té
+ * ```
+ * @since v0.1.90
+ * @param [encoding='utf8'] The character encoding to use.
+ * @param [start=0] The byte offset to start decoding at.
+ * @param [end=buf.length] The byte offset to stop decoding at (not inclusive).
+ */
+ toString(encoding?: BufferEncoding, start?: number, end?: number): string;
+ /**
+ * Returns a JSON representation of `buf`. [`JSON.stringify()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/JSON/stringify) implicitly calls
+ * this function when stringifying a `Buffer` instance.
+ *
+ * `Buffer.from()` accepts objects in the format returned from this method.
+ * In particular, `Buffer.from(buf.toJSON())` works like `Buffer.from(buf)`.
+ *
+ * ```js
+ * import { Buffer } from 'node:buffer';
+ *
+ * const buf = Buffer.from([0x1, 0x2, 0x3, 0x4, 0x5]);
+ * const json = JSON.stringify(buf);
+ *
+ * console.log(json);
+ * // Prints: {"type":"Buffer","data":[1,2,3,4,5]}
+ *
+ * const copy = JSON.parse(json, (key, value) => {
+ * return value && value.type === 'Buffer' ?
+ * Buffer.from(value) :
+ * value;
+ * });
+ *
+ * console.log(copy);
+ * // Prints:
+ * ```
+ * @since v0.9.2
+ */
+ toJSON(): {
+ type: "Buffer";
+ data: number[];
+ };
+ /**
+ * Returns `true` if both `buf` and `otherBuffer` have exactly the same bytes,`false` otherwise. Equivalent to `buf.compare(otherBuffer) === 0`.
+ *
+ * ```js
+ * import { Buffer } from 'node:buffer';
+ *
+ * const buf1 = Buffer.from('ABC');
+ * const buf2 = Buffer.from('414243', 'hex');
+ * const buf3 = Buffer.from('ABCD');
+ *
+ * console.log(buf1.equals(buf2));
+ * // Prints: true
+ * console.log(buf1.equals(buf3));
+ * // Prints: false
+ * ```
+ * @since v0.11.13
+ * @param otherBuffer A `Buffer` or {@link Uint8Array} with which to compare `buf`.
+ */
+ equals(otherBuffer: Uint8Array): boolean;
+ /**
+ * Compares `buf` with `target` and returns a number indicating whether `buf`comes before, after, or is the same as `target` in sort order.
+ * Comparison is based on the actual sequence of bytes in each `Buffer`.
+ *
+ * * `0` is returned if `target` is the same as `buf`
+ * * `1` is returned if `target` should come _before_`buf` when sorted.
+ * * `-1` is returned if `target` should come _after_`buf` when sorted.
+ *
+ * ```js
+ * import { Buffer } from 'node:buffer';
+ *
+ * const buf1 = Buffer.from('ABC');
+ * const buf2 = Buffer.from('BCD');
+ * const buf3 = Buffer.from('ABCD');
+ *
+ * console.log(buf1.compare(buf1));
+ * // Prints: 0
+ * console.log(buf1.compare(buf2));
+ * // Prints: -1
+ * console.log(buf1.compare(buf3));
+ * // Prints: -1
+ * console.log(buf2.compare(buf1));
+ * // Prints: 1
+ * console.log(buf2.compare(buf3));
+ * // Prints: 1
+ * console.log([buf1, buf2, buf3].sort(Buffer.compare));
+ * // Prints: [ , , ]
+ * // (This result is equal to: [buf1, buf3, buf2].)
+ * ```
+ *
+ * The optional `targetStart`, `targetEnd`, `sourceStart`, and `sourceEnd` arguments can be used to limit the comparison to specific ranges within `target` and `buf` respectively.
+ *
+ * ```js
+ * import { Buffer } from 'node:buffer';
+ *
+ * const buf1 = Buffer.from([1, 2, 3, 4, 5, 6, 7, 8, 9]);
+ * const buf2 = Buffer.from([5, 6, 7, 8, 9, 1, 2, 3, 4]);
+ *
+ * console.log(buf1.compare(buf2, 5, 9, 0, 4));
+ * // Prints: 0
+ * console.log(buf1.compare(buf2, 0, 6, 4));
+ * // Prints: -1
+ * console.log(buf1.compare(buf2, 5, 6, 5));
+ * // Prints: 1
+ * ```
+ *
+ * `ERR_OUT_OF_RANGE` is thrown if `targetStart < 0`, `sourceStart < 0`, `targetEnd > target.byteLength`, or `sourceEnd > source.byteLength`.
+ * @since v0.11.13
+ * @param target A `Buffer` or {@link Uint8Array} with which to compare `buf`.
+ * @param [targetStart=0] The offset within `target` at which to begin comparison.
+ * @param [targetEnd=target.length] The offset within `target` at which to end comparison (not inclusive).
+ * @param [sourceStart=0] The offset within `buf` at which to begin comparison.
+ * @param [sourceEnd=buf.length] The offset within `buf` at which to end comparison (not inclusive).
+ */
+ compare(
+ target: Uint8Array,
+ targetStart?: number,
+ targetEnd?: number,
+ sourceStart?: number,
+ sourceEnd?: number,
+ ): -1 | 0 | 1;
+ /**
+ * Copies data from a region of `buf` to a region in `target`, even if the `target`memory region overlaps with `buf`.
+ *
+ * [`TypedArray.prototype.set()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/TypedArray/set) performs the same operation, and is available
+ * for all TypedArrays, including Node.js `Buffer`s, although it takes
+ * different function arguments.
+ *
+ * ```js
+ * import { Buffer } from 'node:buffer';
+ *
+ * // Create two `Buffer` instances.
+ * const buf1 = Buffer.allocUnsafe(26);
+ * const buf2 = Buffer.allocUnsafe(26).fill('!');
+ *
+ * for (let i = 0; i < 26; i++) {
+ * // 97 is the decimal ASCII value for 'a'.
+ * buf1[i] = i + 97;
+ * }
+ *
+ * // Copy `buf1` bytes 16 through 19 into `buf2` starting at byte 8 of `buf2`.
+ * buf1.copy(buf2, 8, 16, 20);
+ * // This is equivalent to:
+ * // buf2.set(buf1.subarray(16, 20), 8);
+ *
+ * console.log(buf2.toString('ascii', 0, 25));
+ * // Prints: !!!!!!!!qrst!!!!!!!!!!!!!
+ * ```
+ *
+ * ```js
+ * import { Buffer } from 'node:buffer';
+ *
+ * // Create a `Buffer` and copy data from one region to an overlapping region
+ * // within the same `Buffer`.
+ *
+ * const buf = Buffer.allocUnsafe(26);
+ *
+ * for (let i = 0; i < 26; i++) {
+ * // 97 is the decimal ASCII value for 'a'.
+ * buf[i] = i + 97;
+ * }
+ *
+ * buf.copy(buf, 0, 4, 10);
+ *
+ * console.log(buf.toString());
+ * // Prints: efghijghijklmnopqrstuvwxyz
+ * ```
+ * @since v0.1.90
+ * @param target A `Buffer` or {@link Uint8Array} to copy into.
+ * @param [targetStart=0] The offset within `target` at which to begin writing.
+ * @param [sourceStart=0] The offset within `buf` from which to begin copying.
+ * @param [sourceEnd=buf.length] The offset within `buf` at which to stop copying (not inclusive).
+ * @return The number of bytes copied.
+ */
+ copy(target: Uint8Array, targetStart?: number, sourceStart?: number, sourceEnd?: number): number;
+ /**
+ * Writes `value` to `buf` at the specified `offset` as big-endian.
+ *
+ * `value` is interpreted and written as a two's complement signed integer.
+ *
+ * ```js
+ * import { Buffer } from 'node:buffer';
+ *
+ * const buf = Buffer.allocUnsafe(8);
+ *
+ * buf.writeBigInt64BE(0x0102030405060708n, 0);
+ *
+ * console.log(buf);
+ * // Prints:
+ * ```
+ * @since v12.0.0, v10.20.0
+ * @param value Number to be written to `buf`.
+ * @param [offset=0] Number of bytes to skip before starting to write. Must satisfy: `0 <= offset <= buf.length - 8`.
+ * @return `offset` plus the number of bytes written.
+ */
+ writeBigInt64BE(value: bigint, offset?: number): number;
+ /**
+ * Writes `value` to `buf` at the specified `offset` as little-endian.
+ *
+ * `value` is interpreted and written as a two's complement signed integer.
+ *
+ * ```js
+ * import { Buffer } from 'node:buffer';
+ *
+ * const buf = Buffer.allocUnsafe(8);
+ *
+ * buf.writeBigInt64LE(0x0102030405060708n, 0);
+ *
+ * console.log(buf);
+ * // Prints:
+ * ```
+ * @since v12.0.0, v10.20.0
+ * @param value Number to be written to `buf`.
+ * @param [offset=0] Number of bytes to skip before starting to write. Must satisfy: `0 <= offset <= buf.length - 8`.
+ * @return `offset` plus the number of bytes written.
+ */
+ writeBigInt64LE(value: bigint, offset?: number): number;
+ /**
+ * Writes `value` to `buf` at the specified `offset` as big-endian.
+ *
+ * This function is also available under the `writeBigUint64BE` alias.
+ *
+ * ```js
+ * import { Buffer } from 'node:buffer';
+ *
+ * const buf = Buffer.allocUnsafe(8);
+ *
+ * buf.writeBigUInt64BE(0xdecafafecacefaden, 0);
+ *
+ * console.log(buf);
+ * // Prints:
+ * ```
+ * @since v12.0.0, v10.20.0
+ * @param value Number to be written to `buf`.
+ * @param [offset=0] Number of bytes to skip before starting to write. Must satisfy: `0 <= offset <= buf.length - 8`.
+ * @return `offset` plus the number of bytes written.
+ */
+ writeBigUInt64BE(value: bigint, offset?: number): number;
+ /**
+ * @alias Buffer.writeBigUInt64BE
+ * @since v14.10.0, v12.19.0
+ */
+ writeBigUint64BE(value: bigint, offset?: number): number;
+ /**
+ * Writes `value` to `buf` at the specified `offset` as little-endian
+ *
+ * ```js
+ * import { Buffer } from 'node:buffer';
+ *
+ * const buf = Buffer.allocUnsafe(8);
+ *
+ * buf.writeBigUInt64LE(0xdecafafecacefaden, 0);
+ *
+ * console.log(buf);
+ * // Prints:
+ * ```
+ *
+ * This function is also available under the `writeBigUint64LE` alias.
+ * @since v12.0.0, v10.20.0
+ * @param value Number to be written to `buf`.
+ * @param [offset=0] Number of bytes to skip before starting to write. Must satisfy: `0 <= offset <= buf.length - 8`.
+ * @return `offset` plus the number of bytes written.
+ */
+ writeBigUInt64LE(value: bigint, offset?: number): number;
+ /**
+ * @alias Buffer.writeBigUInt64LE
+ * @since v14.10.0, v12.19.0
+ */
+ writeBigUint64LE(value: bigint, offset?: number): number;
+ /**
+ * Writes `byteLength` bytes of `value` to `buf` at the specified `offset`as little-endian. Supports up to 48 bits of accuracy. Behavior is undefined
+ * when `value` is anything other than an unsigned integer.
+ *
+ * This function is also available under the `writeUintLE` alias.
+ *
+ * ```js
+ * import { Buffer } from 'node:buffer';
+ *
+ * const buf = Buffer.allocUnsafe(6);
+ *
+ * buf.writeUIntLE(0x1234567890ab, 0, 6);
+ *
+ * console.log(buf);
+ * // Prints:
+ * ```
+ * @since v0.5.5
+ * @param value Number to be written to `buf`.
+ * @param offset Number of bytes to skip before starting to write. Must satisfy `0 <= offset <= buf.length - byteLength`.
+ * @param byteLength Number of bytes to write. Must satisfy `0 < byteLength <= 6`.
+ * @return `offset` plus the number of bytes written.
+ */
+ writeUIntLE(value: number, offset: number, byteLength: number): number;
+ /**
+ * @alias Buffer.writeUIntLE
+ * @since v14.9.0, v12.19.0
+ */
+ writeUintLE(value: number, offset: number, byteLength: number): number;
+ /**
+ * Writes `byteLength` bytes of `value` to `buf` at the specified `offset`as big-endian. Supports up to 48 bits of accuracy. Behavior is undefined
+ * when `value` is anything other than an unsigned integer.
+ *
+ * This function is also available under the `writeUintBE` alias.
+ *
+ * ```js
+ * import { Buffer } from 'node:buffer';
+ *
+ * const buf = Buffer.allocUnsafe(6);
+ *
+ * buf.writeUIntBE(0x1234567890ab, 0, 6);
+ *
+ * console.log(buf);
+ * // Prints:
+ * ```
+ * @since v0.5.5
+ * @param value Number to be written to `buf`.
+ * @param offset Number of bytes to skip before starting to write. Must satisfy `0 <= offset <= buf.length - byteLength`.
+ * @param byteLength Number of bytes to write. Must satisfy `0 < byteLength <= 6`.
+ * @return `offset` plus the number of bytes written.
+ */
+ writeUIntBE(value: number, offset: number, byteLength: number): number;
+ /**
+ * @alias Buffer.writeUIntBE
+ * @since v14.9.0, v12.19.0
+ */
+ writeUintBE(value: number, offset: number, byteLength: number): number;
+ /**
+ * Writes `byteLength` bytes of `value` to `buf` at the specified `offset`as little-endian. Supports up to 48 bits of accuracy. Behavior is undefined
+ * when `value` is anything other than a signed integer.
+ *
+ * ```js
+ * import { Buffer } from 'node:buffer';
+ *
+ * const buf = Buffer.allocUnsafe(6);
+ *
+ * buf.writeIntLE(0x1234567890ab, 0, 6);
+ *
+ * console.log(buf);
+ * // Prints:
+ * ```
+ * @since v0.11.15
+ * @param value Number to be written to `buf`.
+ * @param offset Number of bytes to skip before starting to write. Must satisfy `0 <= offset <= buf.length - byteLength`.
+ * @param byteLength Number of bytes to write. Must satisfy `0 < byteLength <= 6`.
+ * @return `offset` plus the number of bytes written.
+ */
+ writeIntLE(value: number, offset: number, byteLength: number): number;
+ /**
+ * Writes `byteLength` bytes of `value` to `buf` at the specified `offset`as big-endian. Supports up to 48 bits of accuracy. Behavior is undefined when`value` is anything other than a
+ * signed integer.
+ *
+ * ```js
+ * import { Buffer } from 'node:buffer';
+ *
+ * const buf = Buffer.allocUnsafe(6);
+ *
+ * buf.writeIntBE(0x1234567890ab, 0, 6);
+ *
+ * console.log(buf);
+ * // Prints:
+ * ```
+ * @since v0.11.15
+ * @param value Number to be written to `buf`.
+ * @param offset Number of bytes to skip before starting to write. Must satisfy `0 <= offset <= buf.length - byteLength`.
+ * @param byteLength Number of bytes to write. Must satisfy `0 < byteLength <= 6`.
+ * @return `offset` plus the number of bytes written.
+ */
+ writeIntBE(value: number, offset: number, byteLength: number): number;
+ /**
+ * Reads an unsigned, big-endian 64-bit integer from `buf` at the specified`offset`.
+ *
+ * This function is also available under the `readBigUint64BE` alias.
+ *
+ * ```js
+ * import { Buffer } from 'node:buffer';
+ *
+ * const buf = Buffer.from([0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff]);
+ *
+ * console.log(buf.readBigUInt64BE(0));
+ * // Prints: 4294967295n
+ * ```
+ * @since v12.0.0, v10.20.0
+ * @param [offset=0] Number of bytes to skip before starting to read. Must satisfy: `0 <= offset <= buf.length - 8`.
+ */
+ readBigUInt64BE(offset?: number): bigint;
+ /**
+ * @alias Buffer.readBigUInt64BE
+ * @since v14.10.0, v12.19.0
+ */
+ readBigUint64BE(offset?: number): bigint;
+ /**
+ * Reads an unsigned, little-endian 64-bit integer from `buf` at the specified`offset`.
+ *
+ * This function is also available under the `readBigUint64LE` alias.
+ *
+ * ```js
+ * import { Buffer } from 'node:buffer';
+ *
+ * const buf = Buffer.from([0x00, 0x00, 0x00, 0x00, 0xff, 0xff, 0xff, 0xff]);
+ *
+ * console.log(buf.readBigUInt64LE(0));
+ * // Prints: 18446744069414584320n
+ * ```
+ * @since v12.0.0, v10.20.0
+ * @param [offset=0] Number of bytes to skip before starting to read. Must satisfy: `0 <= offset <= buf.length - 8`.
+ */
+ readBigUInt64LE(offset?: number): bigint;
+ /**
+ * @alias Buffer.readBigUInt64LE
+ * @since v14.10.0, v12.19.0
+ */
+ readBigUint64LE(offset?: number): bigint;
+ /**
+ * Reads a signed, big-endian 64-bit integer from `buf` at the specified `offset`.
+ *
+ * Integers read from a `Buffer` are interpreted as two's complement signed
+ * values.
+ * @since v12.0.0, v10.20.0
+ * @param [offset=0] Number of bytes to skip before starting to read. Must satisfy: `0 <= offset <= buf.length - 8`.
+ */
+ readBigInt64BE(offset?: number): bigint;
+ /**
+ * Reads a signed, little-endian 64-bit integer from `buf` at the specified`offset`.
+ *
+ * Integers read from a `Buffer` are interpreted as two's complement signed
+ * values.
+ * @since v12.0.0, v10.20.0
+ * @param [offset=0] Number of bytes to skip before starting to read. Must satisfy: `0 <= offset <= buf.length - 8`.
+ */
+ readBigInt64LE(offset?: number): bigint;
+ /**
+ * Reads `byteLength` number of bytes from `buf` at the specified `offset` and interprets the result as an unsigned, little-endian integer supporting
+ * up to 48 bits of accuracy.
+ *
+ * This function is also available under the `readUintLE` alias.
+ *
+ * ```js
+ * import { Buffer } from 'node:buffer';
+ *
+ * const buf = Buffer.from([0x12, 0x34, 0x56, 0x78, 0x90, 0xab]);
+ *
+ * console.log(buf.readUIntLE(0, 6).toString(16));
+ * // Prints: ab9078563412
+ * ```
+ * @since v0.11.15
+ * @param offset Number of bytes to skip before starting to read. Must satisfy `0 <= offset <= buf.length - byteLength`.
+ * @param byteLength Number of bytes to read. Must satisfy `0 < byteLength <= 6`.
+ */
+ readUIntLE(offset: number, byteLength: number): number;
+ /**
+ * @alias Buffer.readUIntLE
+ * @since v14.9.0, v12.19.0
+ */
+ readUintLE(offset: number, byteLength: number): number;
+ /**
+ * Reads `byteLength` number of bytes from `buf` at the specified `offset` and interprets the result as an unsigned big-endian integer supporting
+ * up to 48 bits of accuracy.
+ *
+ * This function is also available under the `readUintBE` alias.
+ *
+ * ```js
+ * import { Buffer } from 'node:buffer';
+ *
+ * const buf = Buffer.from([0x12, 0x34, 0x56, 0x78, 0x90, 0xab]);
+ *
+ * console.log(buf.readUIntBE(0, 6).toString(16));
+ * // Prints: 1234567890ab
+ * console.log(buf.readUIntBE(1, 6).toString(16));
+ * // Throws ERR_OUT_OF_RANGE.
+ * ```
+ * @since v0.11.15
+ * @param offset Number of bytes to skip before starting to read. Must satisfy `0 <= offset <= buf.length - byteLength`.
+ * @param byteLength Number of bytes to read. Must satisfy `0 < byteLength <= 6`.
+ */
+ readUIntBE(offset: number, byteLength: number): number;
+ /**
+ * @alias Buffer.readUIntBE
+ * @since v14.9.0, v12.19.0
+ */
+ readUintBE(offset: number, byteLength: number): number;
+ /**
+ * Reads `byteLength` number of bytes from `buf` at the specified `offset` and interprets the result as a little-endian, two's complement signed value
+ * supporting up to 48 bits of accuracy.
+ *
+ * ```js
+ * import { Buffer } from 'node:buffer';
+ *
+ * const buf = Buffer.from([0x12, 0x34, 0x56, 0x78, 0x90, 0xab]);
+ *
+ * console.log(buf.readIntLE(0, 6).toString(16));
+ * // Prints: -546f87a9cbee
+ * ```
+ * @since v0.11.15
+ * @param offset Number of bytes to skip before starting to read. Must satisfy `0 <= offset <= buf.length - byteLength`.
+ * @param byteLength Number of bytes to read. Must satisfy `0 < byteLength <= 6`.
+ */
+ readIntLE(offset: number, byteLength: number): number;
+ /**
+ * Reads `byteLength` number of bytes from `buf` at the specified `offset` and interprets the result as a big-endian, two's complement signed value
+ * supporting up to 48 bits of accuracy.
+ *
+ * ```js
+ * import { Buffer } from 'node:buffer';
+ *
+ * const buf = Buffer.from([0x12, 0x34, 0x56, 0x78, 0x90, 0xab]);
+ *
+ * console.log(buf.readIntBE(0, 6).toString(16));
+ * // Prints: 1234567890ab
+ * console.log(buf.readIntBE(1, 6).toString(16));
+ * // Throws ERR_OUT_OF_RANGE.
+ * console.log(buf.readIntBE(1, 0).toString(16));
+ * // Throws ERR_OUT_OF_RANGE.
+ * ```
+ * @since v0.11.15
+ * @param offset Number of bytes to skip before starting to read. Must satisfy `0 <= offset <= buf.length - byteLength`.
+ * @param byteLength Number of bytes to read. Must satisfy `0 < byteLength <= 6`.
+ */
+ readIntBE(offset: number, byteLength: number): number;
+ /**
+ * Reads an unsigned 8-bit integer from `buf` at the specified `offset`.
+ *
+ * This function is also available under the `readUint8` alias.
+ *
+ * ```js
+ * import { Buffer } from 'node:buffer';
+ *
+ * const buf = Buffer.from([1, -2]);
+ *
+ * console.log(buf.readUInt8(0));
+ * // Prints: 1
+ * console.log(buf.readUInt8(1));
+ * // Prints: 254
+ * console.log(buf.readUInt8(2));
+ * // Throws ERR_OUT_OF_RANGE.
+ * ```
+ * @since v0.5.0
+ * @param [offset=0] Number of bytes to skip before starting to read. Must satisfy `0 <= offset <= buf.length - 1`.
+ */
+ readUInt8(offset?: number): number;
+ /**
+ * @alias Buffer.readUInt8
+ * @since v14.9.0, v12.19.0
+ */
+ readUint8(offset?: number): number;
+ /**
+ * Reads an unsigned, little-endian 16-bit integer from `buf` at the specified `offset`.
+ *
+ * This function is also available under the `readUint16LE` alias.
+ *
+ * ```js
+ * import { Buffer } from 'node:buffer';
+ *
+ * const buf = Buffer.from([0x12, 0x34, 0x56]);
+ *
+ * console.log(buf.readUInt16LE(0).toString(16));
+ * // Prints: 3412
+ * console.log(buf.readUInt16LE(1).toString(16));
+ * // Prints: 5634
+ * console.log(buf.readUInt16LE(2).toString(16));
+ * // Throws ERR_OUT_OF_RANGE.
+ * ```
+ * @since v0.5.5
+ * @param [offset=0] Number of bytes to skip before starting to read. Must satisfy `0 <= offset <= buf.length - 2`.
+ */
+ readUInt16LE(offset?: number): number;
+ /**
+ * @alias Buffer.readUInt16LE
+ * @since v14.9.0, v12.19.0
+ */
+ readUint16LE(offset?: number): number;
+ /**
+ * Reads an unsigned, big-endian 16-bit integer from `buf` at the specified`offset`.
+ *
+ * This function is also available under the `readUint16BE` alias.
+ *
+ * ```js
+ * import { Buffer } from 'node:buffer';
+ *
+ * const buf = Buffer.from([0x12, 0x34, 0x56]);
+ *
+ * console.log(buf.readUInt16BE(0).toString(16));
+ * // Prints: 1234
+ * console.log(buf.readUInt16BE(1).toString(16));
+ * // Prints: 3456
+ * ```
+ * @since v0.5.5
+ * @param [offset=0] Number of bytes to skip before starting to read. Must satisfy `0 <= offset <= buf.length - 2`.
+ */
+ readUInt16BE(offset?: number): number;
+ /**
+ * @alias Buffer.readUInt16BE
+ * @since v14.9.0, v12.19.0
+ */
+ readUint16BE(offset?: number): number;
+ /**
+ * Reads an unsigned, little-endian 32-bit integer from `buf` at the specified`offset`.
+ *
+ * This function is also available under the `readUint32LE` alias.
+ *
+ * ```js
+ * import { Buffer } from 'node:buffer';
+ *
+ * const buf = Buffer.from([0x12, 0x34, 0x56, 0x78]);
+ *
+ * console.log(buf.readUInt32LE(0).toString(16));
+ * // Prints: 78563412
+ * console.log(buf.readUInt32LE(1).toString(16));
+ * // Throws ERR_OUT_OF_RANGE.
+ * ```
+ * @since v0.5.5
+ * @param [offset=0] Number of bytes to skip before starting to read. Must satisfy `0 <= offset <= buf.length - 4`.
+ */
+ readUInt32LE(offset?: number): number;
+ /**
+ * @alias Buffer.readUInt32LE
+ * @since v14.9.0, v12.19.0
+ */
+ readUint32LE(offset?: number): number;
+ /**
+ * Reads an unsigned, big-endian 32-bit integer from `buf` at the specified`offset`.
+ *
+ * This function is also available under the `readUint32BE` alias.
+ *
+ * ```js
+ * import { Buffer } from 'node:buffer';
+ *
+ * const buf = Buffer.from([0x12, 0x34, 0x56, 0x78]);
+ *
+ * console.log(buf.readUInt32BE(0).toString(16));
+ * // Prints: 12345678
+ * ```
+ * @since v0.5.5
+ * @param [offset=0] Number of bytes to skip before starting to read. Must satisfy `0 <= offset <= buf.length - 4`.
+ */
+ readUInt32BE(offset?: number): number;
+ /**
+ * @alias Buffer.readUInt32BE
+ * @since v14.9.0, v12.19.0
+ */
+ readUint32BE(offset?: number): number;
+ /**
+ * Reads a signed 8-bit integer from `buf` at the specified `offset`.
+ *
+ * Integers read from a `Buffer` are interpreted as two's complement signed values.
+ *
+ * ```js
+ * import { Buffer } from 'node:buffer';
+ *
+ * const buf = Buffer.from([-1, 5]);
+ *
+ * console.log(buf.readInt8(0));
+ * // Prints: -1
+ * console.log(buf.readInt8(1));
+ * // Prints: 5
+ * console.log(buf.readInt8(2));
+ * // Throws ERR_OUT_OF_RANGE.
+ * ```
+ * @since v0.5.0
+ * @param [offset=0] Number of bytes to skip before starting to read. Must satisfy `0 <= offset <= buf.length - 1`.
+ */
+ readInt8(offset?: number): number;
+ /**
+ * Reads a signed, little-endian 16-bit integer from `buf` at the specified`offset`.
+ *
+ * Integers read from a `Buffer` are interpreted as two's complement signed values.
+ *
+ * ```js
+ * import { Buffer } from 'node:buffer';
+ *
+ * const buf = Buffer.from([0, 5]);
+ *
+ * console.log(buf.readInt16LE(0));
+ * // Prints: 1280
+ * console.log(buf.readInt16LE(1));
+ * // Throws ERR_OUT_OF_RANGE.
+ * ```
+ * @since v0.5.5
+ * @param [offset=0] Number of bytes to skip before starting to read. Must satisfy `0 <= offset <= buf.length - 2`.
+ */
+ readInt16LE(offset?: number): number;
+ /**
+ * Reads a signed, big-endian 16-bit integer from `buf` at the specified `offset`.
+ *
+ * Integers read from a `Buffer` are interpreted as two's complement signed values.
+ *
+ * ```js
+ * import { Buffer } from 'node:buffer';
+ *
+ * const buf = Buffer.from([0, 5]);
+ *
+ * console.log(buf.readInt16BE(0));
+ * // Prints: 5
+ * ```
+ * @since v0.5.5
+ * @param [offset=0] Number of bytes to skip before starting to read. Must satisfy `0 <= offset <= buf.length - 2`.
+ */
+ readInt16BE(offset?: number): number;
+ /**
+ * Reads a signed, little-endian 32-bit integer from `buf` at the specified`offset`.
+ *
+ * Integers read from a `Buffer` are interpreted as two's complement signed values.
+ *
+ * ```js
+ * import { Buffer } from 'node:buffer';
+ *
+ * const buf = Buffer.from([0, 0, 0, 5]);
+ *
+ * console.log(buf.readInt32LE(0));
+ * // Prints: 83886080
+ * console.log(buf.readInt32LE(1));
+ * // Throws ERR_OUT_OF_RANGE.
+ * ```
+ * @since v0.5.5
+ * @param [offset=0] Number of bytes to skip before starting to read. Must satisfy `0 <= offset <= buf.length - 4`.
+ */
+ readInt32LE(offset?: number): number;
+ /**
+ * Reads a signed, big-endian 32-bit integer from `buf` at the specified `offset`.
+ *
+ * Integers read from a `Buffer` are interpreted as two's complement signed values.
+ *
+ * ```js
+ * import { Buffer } from 'node:buffer';
+ *
+ * const buf = Buffer.from([0, 0, 0, 5]);
+ *
+ * console.log(buf.readInt32BE(0));
+ * // Prints: 5
+ * ```
+ * @since v0.5.5
+ * @param [offset=0] Number of bytes to skip before starting to read. Must satisfy `0 <= offset <= buf.length - 4`.
+ */
+ readInt32BE(offset?: number): number;
+ /**
+ * Reads a 32-bit, little-endian float from `buf` at the specified `offset`.
+ *
+ * ```js
+ * import { Buffer } from 'node:buffer';
+ *
+ * const buf = Buffer.from([1, 2, 3, 4]);
+ *
+ * console.log(buf.readFloatLE(0));
+ * // Prints: 1.539989614439558e-36
+ * console.log(buf.readFloatLE(1));
+ * // Throws ERR_OUT_OF_RANGE.
+ * ```
+ * @since v0.11.15
+ * @param [offset=0] Number of bytes to skip before starting to read. Must satisfy `0 <= offset <= buf.length - 4`.
+ */
+ readFloatLE(offset?: number): number;
+ /**
+ * Reads a 32-bit, big-endian float from `buf` at the specified `offset`.
+ *
+ * ```js
+ * import { Buffer } from 'node:buffer';
+ *
+ * const buf = Buffer.from([1, 2, 3, 4]);
+ *
+ * console.log(buf.readFloatBE(0));
+ * // Prints: 2.387939260590663e-38
+ * ```
+ * @since v0.11.15
+ * @param [offset=0] Number of bytes to skip before starting to read. Must satisfy `0 <= offset <= buf.length - 4`.
+ */
+ readFloatBE(offset?: number): number;
+ /**
+ * Reads a 64-bit, little-endian double from `buf` at the specified `offset`.
+ *
+ * ```js
+ * import { Buffer } from 'node:buffer';
+ *
+ * const buf = Buffer.from([1, 2, 3, 4, 5, 6, 7, 8]);
+ *
+ * console.log(buf.readDoubleLE(0));
+ * // Prints: 5.447603722011605e-270
+ * console.log(buf.readDoubleLE(1));
+ * // Throws ERR_OUT_OF_RANGE.
+ * ```
+ * @since v0.11.15
+ * @param [offset=0] Number of bytes to skip before starting to read. Must satisfy `0 <= offset <= buf.length - 8`.
+ */
+ readDoubleLE(offset?: number): number;
+ /**
+ * Reads a 64-bit, big-endian double from `buf` at the specified `offset`.
+ *
+ * ```js
+ * import { Buffer } from 'node:buffer';
+ *
+ * const buf = Buffer.from([1, 2, 3, 4, 5, 6, 7, 8]);
+ *
+ * console.log(buf.readDoubleBE(0));
+ * // Prints: 8.20788039913184e-304
+ * ```
+ * @since v0.11.15
+ * @param [offset=0] Number of bytes to skip before starting to read. Must satisfy `0 <= offset <= buf.length - 8`.
+ */
+ readDoubleBE(offset?: number): number;
+ reverse(): this;
+ /**
+ * Interprets `buf` as an array of unsigned 16-bit integers and swaps the
+ * byte order _in-place_. Throws `ERR_INVALID_BUFFER_SIZE` if `buf.length` is not a multiple of 2.
+ *
+ * ```js
+ * import { Buffer } from 'node:buffer';
+ *
+ * const buf1 = Buffer.from([0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8]);
+ *
+ * console.log(buf1);
+ * // Prints:
+ *
+ * buf1.swap16();
+ *
+ * console.log(buf1);
+ * // Prints:
+ *
+ * const buf2 = Buffer.from([0x1, 0x2, 0x3]);
+ *
+ * buf2.swap16();
+ * // Throws ERR_INVALID_BUFFER_SIZE.
+ * ```
+ *
+ * One convenient use of `buf.swap16()` is to perform a fast in-place conversion
+ * between UTF-16 little-endian and UTF-16 big-endian:
+ *
+ * ```js
+ * import { Buffer } from 'node:buffer';
+ *
+ * const buf = Buffer.from('This is little-endian UTF-16', 'utf16le');
+ * buf.swap16(); // Convert to big-endian UTF-16 text.
+ * ```
+ * @since v5.10.0
+ * @return A reference to `buf`.
+ */
+ swap16(): this;
+ /**
+ * Interprets `buf` as an array of unsigned 32-bit integers and swaps the
+ * byte order _in-place_. Throws `ERR_INVALID_BUFFER_SIZE` if `buf.length` is not a multiple of 4.
+ *
+ * ```js
+ * import { Buffer } from 'node:buffer';
+ *
+ * const buf1 = Buffer.from([0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8]);
+ *
+ * console.log(buf1);
+ * // Prints:
+ *
+ * buf1.swap32();
+ *
+ * console.log(buf1);
+ * // Prints:
+ *
+ * const buf2 = Buffer.from([0x1, 0x2, 0x3]);
+ *
+ * buf2.swap32();
+ * // Throws ERR_INVALID_BUFFER_SIZE.
+ * ```
+ * @since v5.10.0
+ * @return A reference to `buf`.
+ */
+ swap32(): this;
+ /**
+ * Interprets `buf` as an array of 64-bit numbers and swaps byte order _in-place_.
+ * Throws `ERR_INVALID_BUFFER_SIZE` if `buf.length` is not a multiple of 8.
+ *
+ * ```js
+ * import { Buffer } from 'node:buffer';
+ *
+ * const buf1 = Buffer.from([0x1, 0x2, 0x3, 0x4, 0x5, 0x6, 0x7, 0x8]);
+ *
+ * console.log(buf1);
+ * // Prints:
+ *
+ * buf1.swap64();
+ *
+ * console.log(buf1);
+ * // Prints:
+ *
+ * const buf2 = Buffer.from([0x1, 0x2, 0x3]);
+ *
+ * buf2.swap64();
+ * // Throws ERR_INVALID_BUFFER_SIZE.
+ * ```
+ * @since v6.3.0
+ * @return A reference to `buf`.
+ */
+ swap64(): this;
+ /**
+ * Writes `value` to `buf` at the specified `offset`. `value` must be a
+ * valid unsigned 8-bit integer. Behavior is undefined when `value` is anything
+ * other than an unsigned 8-bit integer.
+ *
+ * This function is also available under the `writeUint8` alias.
+ *
+ * ```js
+ * import { Buffer } from 'node:buffer';
+ *
+ * const buf = Buffer.allocUnsafe(4);
+ *
+ * buf.writeUInt8(0x3, 0);
+ * buf.writeUInt8(0x4, 1);
+ * buf.writeUInt8(0x23, 2);
+ * buf.writeUInt8(0x42, 3);
+ *
+ * console.log(buf);
+ * // Prints:
+ * ```
+ * @since v0.5.0
+ * @param value Number to be written to `buf`.
+ * @param [offset=0] Number of bytes to skip before starting to write. Must satisfy `0 <= offset <= buf.length - 1`.
+ * @return `offset` plus the number of bytes written.
+ */
+ writeUInt8(value: number, offset?: number): number;
+ /**
+ * @alias Buffer.writeUInt8
+ * @since v14.9.0, v12.19.0
+ */
+ writeUint8(value: number, offset?: number): number;
+ /**
+ * Writes `value` to `buf` at the specified `offset` as little-endian. The `value` must be a valid unsigned 16-bit integer. Behavior is undefined when `value` is
+ * anything other than an unsigned 16-bit integer.
+ *
+ * This function is also available under the `writeUint16LE` alias.
+ *
+ * ```js
+ * import { Buffer } from 'node:buffer';
+ *
+ * const buf = Buffer.allocUnsafe(4);
+ *
+ * buf.writeUInt16LE(0xdead, 0);
+ * buf.writeUInt16LE(0xbeef, 2);
+ *
+ * console.log(buf);
+ * // Prints:
+ * ```
+ * @since v0.5.5
+ * @param value Number to be written to `buf`.
+ * @param [offset=0] Number of bytes to skip before starting to write. Must satisfy `0 <= offset <= buf.length - 2`.
+ * @return `offset` plus the number of bytes written.
+ */
+ writeUInt16LE(value: number, offset?: number): number;
+ /**
+ * @alias Buffer.writeUInt16LE
+ * @since v14.9.0, v12.19.0
+ */
+ writeUint16LE(value: number, offset?: number): number;
+ /**
+ * Writes `value` to `buf` at the specified `offset` as big-endian. The `value` must be a valid unsigned 16-bit integer. Behavior is undefined when `value`is anything other than an
+ * unsigned 16-bit integer.
+ *
+ * This function is also available under the `writeUint16BE` alias.
+ *
+ * ```js
+ * import { Buffer } from 'node:buffer';
+ *
+ * const buf = Buffer.allocUnsafe(4);
+ *
+ * buf.writeUInt16BE(0xdead, 0);
+ * buf.writeUInt16BE(0xbeef, 2);
+ *
+ * console.log(buf);
+ * // Prints:
+ * ```
+ * @since v0.5.5
+ * @param value Number to be written to `buf`.
+ * @param [offset=0] Number of bytes to skip before starting to write. Must satisfy `0 <= offset <= buf.length - 2`.
+ * @return `offset` plus the number of bytes written.
+ */
+ writeUInt16BE(value: number, offset?: number): number;
+ /**
+ * @alias Buffer.writeUInt16BE
+ * @since v14.9.0, v12.19.0
+ */
+ writeUint16BE(value: number, offset?: number): number;
+ /**
+ * Writes `value` to `buf` at the specified `offset` as little-endian. The `value` must be a valid unsigned 32-bit integer. Behavior is undefined when `value` is
+ * anything other than an unsigned 32-bit integer.
+ *
+ * This function is also available under the `writeUint32LE` alias.
+ *
+ * ```js
+ * import { Buffer } from 'node:buffer';
+ *
+ * const buf = Buffer.allocUnsafe(4);
+ *
+ * buf.writeUInt32LE(0xfeedface, 0);
+ *
+ * console.log(buf);
+ * // Prints:
+ * ```
+ * @since v0.5.5
+ * @param value Number to be written to `buf`.
+ * @param [offset=0] Number of bytes to skip before starting to write. Must satisfy `0 <= offset <= buf.length - 4`.
+ * @return `offset` plus the number of bytes written.
+ */
+ writeUInt32LE(value: number, offset?: number): number;
+ /**
+ * @alias Buffer.writeUInt32LE
+ * @since v14.9.0, v12.19.0
+ */
+ writeUint32LE(value: number, offset?: number): number;
+ /**
+ * Writes `value` to `buf` at the specified `offset` as big-endian. The `value` must be a valid unsigned 32-bit integer. Behavior is undefined when `value`is anything other than an
+ * unsigned 32-bit integer.
+ *
+ * This function is also available under the `writeUint32BE` alias.
+ *
+ * ```js
+ * import { Buffer } from 'node:buffer';
+ *
+ * const buf = Buffer.allocUnsafe(4);
+ *
+ * buf.writeUInt32BE(0xfeedface, 0);
+ *
+ * console.log(buf);
+ * // Prints:
+ * ```
+ * @since v0.5.5
+ * @param value Number to be written to `buf`.
+ * @param [offset=0] Number of bytes to skip before starting to write. Must satisfy `0 <= offset <= buf.length - 4`.
+ * @return `offset` plus the number of bytes written.
+ */
+ writeUInt32BE(value: number, offset?: number): number;
+ /**
+ * @alias Buffer.writeUInt32BE
+ * @since v14.9.0, v12.19.0
+ */
+ writeUint32BE(value: number, offset?: number): number;
+ /**
+ * Writes `value` to `buf` at the specified `offset`. `value` must be a valid
+ * signed 8-bit integer. Behavior is undefined when `value` is anything other than
+ * a signed 8-bit integer.
+ *
+ * `value` is interpreted and written as a two's complement signed integer.
+ *
+ * ```js
+ * import { Buffer } from 'node:buffer';
+ *
+ * const buf = Buffer.allocUnsafe(2);
+ *
+ * buf.writeInt8(2, 0);
+ * buf.writeInt8(-2, 1);
+ *
+ * console.log(buf);
+ * // Prints:
+ * ```
+ * @since v0.5.0
+ * @param value Number to be written to `buf`.
+ * @param [offset=0] Number of bytes to skip before starting to write. Must satisfy `0 <= offset <= buf.length - 1`.
+ * @return `offset` plus the number of bytes written.
+ */
+ writeInt8(value: number, offset?: number): number;
+ /**
+ * Writes `value` to `buf` at the specified `offset` as little-endian. The `value` must be a valid signed 16-bit integer. Behavior is undefined when `value` is
+ * anything other than a signed 16-bit integer.
+ *
+ * The `value` is interpreted and written as a two's complement signed integer.
+ *
+ * ```js
+ * import { Buffer } from 'node:buffer';
+ *
+ * const buf = Buffer.allocUnsafe(2);
+ *
+ * buf.writeInt16LE(0x0304, 0);
+ *
+ * console.log(buf);
+ * // Prints:
+ * ```
+ * @since v0.5.5
+ * @param value Number to be written to `buf`.
+ * @param [offset=0] Number of bytes to skip before starting to write. Must satisfy `0 <= offset <= buf.length - 2`.
+ * @return `offset` plus the number of bytes written.
+ */
+ writeInt16LE(value: number, offset?: number): number;
+ /**
+ * Writes `value` to `buf` at the specified `offset` as big-endian. The `value` must be a valid signed 16-bit integer. Behavior is undefined when `value` is
+ * anything other than a signed 16-bit integer.
+ *
+ * The `value` is interpreted and written as a two's complement signed integer.
+ *
+ * ```js
+ * import { Buffer } from 'node:buffer';
+ *
+ * const buf = Buffer.allocUnsafe(2);
+ *
+ * buf.writeInt16BE(0x0102, 0);
+ *
+ * console.log(buf);
+ * // Prints:
+ * ```
+ * @since v0.5.5
+ * @param value Number to be written to `buf`.
+ * @param [offset=0] Number of bytes to skip before starting to write. Must satisfy `0 <= offset <= buf.length - 2`.
+ * @return `offset` plus the number of bytes written.
+ */
+ writeInt16BE(value: number, offset?: number): number;
+ /**
+ * Writes `value` to `buf` at the specified `offset` as little-endian. The `value` must be a valid signed 32-bit integer. Behavior is undefined when `value` is
+ * anything other than a signed 32-bit integer.
+ *
+ * The `value` is interpreted and written as a two's complement signed integer.
+ *
+ * ```js
+ * import { Buffer } from 'node:buffer';
+ *
+ * const buf = Buffer.allocUnsafe(4);
+ *
+ * buf.writeInt32LE(0x05060708, 0);
+ *
+ * console.log(buf);
+ * // Prints:
+ * ```
+ * @since v0.5.5
+ * @param value Number to be written to `buf`.
+ * @param [offset=0] Number of bytes to skip before starting to write. Must satisfy `0 <= offset <= buf.length - 4`.
+ * @return `offset` plus the number of bytes written.
+ */
+ writeInt32LE(value: number, offset?: number): number;
+ /**
+ * Writes `value` to `buf` at the specified `offset` as big-endian. The `value` must be a valid signed 32-bit integer. Behavior is undefined when `value` is
+ * anything other than a signed 32-bit integer.
+ *
+ * The `value` is interpreted and written as a two's complement signed integer.
+ *
+ * ```js
+ * import { Buffer } from 'node:buffer';
+ *
+ * const buf = Buffer.allocUnsafe(4);
+ *
+ * buf.writeInt32BE(0x01020304, 0);
+ *
+ * console.log(buf);
+ * // Prints:
+ * ```
+ * @since v0.5.5
+ * @param value Number to be written to `buf`.
+ * @param [offset=0] Number of bytes to skip before starting to write. Must satisfy `0 <= offset <= buf.length - 4`.
+ * @return `offset` plus the number of bytes written.
+ */
+ writeInt32BE(value: number, offset?: number): number;
+ /**
+ * Writes `value` to `buf` at the specified `offset` as little-endian. Behavior is
+ * undefined when `value` is anything other than a JavaScript number.
+ *
+ * ```js
+ * import { Buffer } from 'node:buffer';
+ *
+ * const buf = Buffer.allocUnsafe(4);
+ *
+ * buf.writeFloatLE(0xcafebabe, 0);
+ *
+ * console.log(buf);
+ * // Prints:
+ * ```
+ * @since v0.11.15
+ * @param value Number to be written to `buf`.
+ * @param [offset=0] Number of bytes to skip before starting to write. Must satisfy `0 <= offset <= buf.length - 4`.
+ * @return `offset` plus the number of bytes written.
+ */
+ writeFloatLE(value: number, offset?: number): number;
+ /**
+ * Writes `value` to `buf` at the specified `offset` as big-endian. Behavior is
+ * undefined when `value` is anything other than a JavaScript number.
+ *
+ * ```js
+ * import { Buffer } from 'node:buffer';
+ *
+ * const buf = Buffer.allocUnsafe(4);
+ *
+ * buf.writeFloatBE(0xcafebabe, 0);
+ *
+ * console.log(buf);
+ * // Prints:
+ * ```
+ * @since v0.11.15
+ * @param value Number to be written to `buf`.
+ * @param [offset=0] Number of bytes to skip before starting to write. Must satisfy `0 <= offset <= buf.length - 4`.
+ * @return `offset` plus the number of bytes written.
+ */
+ writeFloatBE(value: number, offset?: number): number;
+ /**
+ * Writes `value` to `buf` at the specified `offset` as little-endian. The `value` must be a JavaScript number. Behavior is undefined when `value` is anything
+ * other than a JavaScript number.
+ *
+ * ```js
+ * import { Buffer } from 'node:buffer';
+ *
+ * const buf = Buffer.allocUnsafe(8);
+ *
+ * buf.writeDoubleLE(123.456, 0);
+ *
+ * console.log(buf);
+ * // Prints:
+ * ```
+ * @since v0.11.15
+ * @param value Number to be written to `buf`.
+ * @param [offset=0] Number of bytes to skip before starting to write. Must satisfy `0 <= offset <= buf.length - 8`.
+ * @return `offset` plus the number of bytes written.
+ */
+ writeDoubleLE(value: number, offset?: number): number;
+ /**
+ * Writes `value` to `buf` at the specified `offset` as big-endian. The `value` must be a JavaScript number. Behavior is undefined when `value` is anything
+ * other than a JavaScript number.
+ *
+ * ```js
+ * import { Buffer } from 'node:buffer';
+ *
+ * const buf = Buffer.allocUnsafe(8);
+ *
+ * buf.writeDoubleBE(123.456, 0);
+ *
+ * console.log(buf);
+ * // Prints:
+ * ```
+ * @since v0.11.15
+ * @param value Number to be written to `buf`.
+ * @param [offset=0] Number of bytes to skip before starting to write. Must satisfy `0 <= offset <= buf.length - 8`.
+ * @return `offset` plus the number of bytes written.
+ */
+ writeDoubleBE(value: number, offset?: number): number;
+ /**
+ * Fills `buf` with the specified `value`. If the `offset` and `end` are not given,
+ * the entire `buf` will be filled:
+ *
+ * ```js
+ * import { Buffer } from 'node:buffer';
+ *
+ * // Fill a `Buffer` with the ASCII character 'h'.
+ *
+ * const b = Buffer.allocUnsafe(50).fill('h');
+ *
+ * console.log(b.toString());
+ * // Prints: hhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhhh
+ *
+ * // Fill a buffer with empty string
+ * const c = Buffer.allocUnsafe(5).fill('');
+ *
+ * console.log(c.fill(''));
+ * // Prints:
+ * ```
+ *
+ * `value` is coerced to a `uint32` value if it is not a string, `Buffer`, or
+ * integer. If the resulting integer is greater than `255` (decimal), `buf` will be
+ * filled with `value & 255`.
+ *
+ * If the final write of a `fill()` operation falls on a multi-byte character,
+ * then only the bytes of that character that fit into `buf` are written:
+ *
+ * ```js
+ * import { Buffer } from 'node:buffer';
+ *
+ * // Fill a `Buffer` with character that takes up two bytes in UTF-8.
+ *
+ * console.log(Buffer.allocUnsafe(5).fill('\u0222'));
+ * // Prints:
+ * ```
+ *
+ * If `value` contains invalid characters, it is truncated; if no valid
+ * fill data remains, an exception is thrown:
+ *
+ * ```js
+ * import { Buffer } from 'node:buffer';
+ *
+ * const buf = Buffer.allocUnsafe(5);
+ *
+ * console.log(buf.fill('a'));
+ * // Prints:
+ * console.log(buf.fill('aazz', 'hex'));
+ * // Prints:
+ * console.log(buf.fill('zz', 'hex'));
+ * // Throws an exception.
+ * ```
+ * @since v0.5.0
+ * @param value The value with which to fill `buf`. Empty value (string, Uint8Array, Buffer) is coerced to `0`.
+ * @param [offset=0] Number of bytes to skip before starting to fill `buf`.
+ * @param [end=buf.length] Where to stop filling `buf` (not inclusive).
+ * @param [encoding='utf8'] The encoding for `value` if `value` is a string.
+ * @return A reference to `buf`.
+ */
+ fill(value: string | Uint8Array | number, offset?: number, end?: number, encoding?: BufferEncoding): this;
+ fill(value: string | Uint8Array | number, offset: number, encoding: BufferEncoding): this;
+ fill(value: string | Uint8Array | number, encoding: BufferEncoding): this;
+ /**
+ * If `value` is:
+ *
+ * * a string, `value` is interpreted according to the character encoding in `encoding`.
+ * * a `Buffer` or [`Uint8Array`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Uint8Array), `value` will be used in its entirety.
+ * To compare a partial `Buffer`, use `buf.subarray`.
+ * * a number, `value` will be interpreted as an unsigned 8-bit integer
+ * value between `0` and `255`.
+ *
+ * ```js
+ * import { Buffer } from 'node:buffer';
+ *
+ * const buf = Buffer.from('this is a buffer');
+ *
+ * console.log(buf.indexOf('this'));
+ * // Prints: 0
+ * console.log(buf.indexOf('is'));
+ * // Prints: 2
+ * console.log(buf.indexOf(Buffer.from('a buffer')));
+ * // Prints: 8
+ * console.log(buf.indexOf(97));
+ * // Prints: 8 (97 is the decimal ASCII value for 'a')
+ * console.log(buf.indexOf(Buffer.from('a buffer example')));
+ * // Prints: -1
+ * console.log(buf.indexOf(Buffer.from('a buffer example').slice(0, 8)));
+ * // Prints: 8
+ *
+ * const utf16Buffer = Buffer.from('\u039a\u0391\u03a3\u03a3\u0395', 'utf16le');
+ *
+ * console.log(utf16Buffer.indexOf('\u03a3', 0, 'utf16le'));
+ * // Prints: 4
+ * console.log(utf16Buffer.indexOf('\u03a3', -4, 'utf16le'));
+ * // Prints: 6
+ * ```
+ *
+ * If `value` is not a string, number, or `Buffer`, this method will throw a `TypeError`. If `value` is a number, it will be coerced to a valid byte value,
+ * an integer between 0 and 255.
+ *
+ * If `byteOffset` is not a number, it will be coerced to a number. If the result
+ * of coercion is `NaN` or `0`, then the entire buffer will be searched. This
+ * behavior matches [`String.prototype.indexOf()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/indexOf).
+ *
+ * ```js
+ * import { Buffer } from 'node:buffer';
+ *
+ * const b = Buffer.from('abcdef');
+ *
+ * // Passing a value that's a number, but not a valid byte.
+ * // Prints: 2, equivalent to searching for 99 or 'c'.
+ * console.log(b.indexOf(99.9));
+ * console.log(b.indexOf(256 + 99));
+ *
+ * // Passing a byteOffset that coerces to NaN or 0.
+ * // Prints: 1, searching the whole buffer.
+ * console.log(b.indexOf('b', undefined));
+ * console.log(b.indexOf('b', {}));
+ * console.log(b.indexOf('b', null));
+ * console.log(b.indexOf('b', []));
+ * ```
+ *
+ * If `value` is an empty string or empty `Buffer` and `byteOffset` is less
+ * than `buf.length`, `byteOffset` will be returned. If `value` is empty and`byteOffset` is at least `buf.length`, `buf.length` will be returned.
+ * @since v1.5.0
+ * @param value What to search for.
+ * @param [byteOffset=0] Where to begin searching in `buf`. If negative, then offset is calculated from the end of `buf`.
+ * @param [encoding='utf8'] If `value` is a string, this is the encoding used to determine the binary representation of the string that will be searched for in `buf`.
+ * @return The index of the first occurrence of `value` in `buf`, or `-1` if `buf` does not contain `value`.
+ */
+ indexOf(value: string | number | Uint8Array, byteOffset?: number, encoding?: BufferEncoding): number;
+ indexOf(value: string | number | Uint8Array, encoding: BufferEncoding): number;
+ /**
+ * Identical to `buf.indexOf()`, except the last occurrence of `value` is found
+ * rather than the first occurrence.
+ *
+ * ```js
+ * import { Buffer } from 'node:buffer';
+ *
+ * const buf = Buffer.from('this buffer is a buffer');
+ *
+ * console.log(buf.lastIndexOf('this'));
+ * // Prints: 0
+ * console.log(buf.lastIndexOf('buffer'));
+ * // Prints: 17
+ * console.log(buf.lastIndexOf(Buffer.from('buffer')));
+ * // Prints: 17
+ * console.log(buf.lastIndexOf(97));
+ * // Prints: 15 (97 is the decimal ASCII value for 'a')
+ * console.log(buf.lastIndexOf(Buffer.from('yolo')));
+ * // Prints: -1
+ * console.log(buf.lastIndexOf('buffer', 5));
+ * // Prints: 5
+ * console.log(buf.lastIndexOf('buffer', 4));
+ * // Prints: -1
+ *
+ * const utf16Buffer = Buffer.from('\u039a\u0391\u03a3\u03a3\u0395', 'utf16le');
+ *
+ * console.log(utf16Buffer.lastIndexOf('\u03a3', undefined, 'utf16le'));
+ * // Prints: 6
+ * console.log(utf16Buffer.lastIndexOf('\u03a3', -5, 'utf16le'));
+ * // Prints: 4
+ * ```
+ *
+ * If `value` is not a string, number, or `Buffer`, this method will throw a `TypeError`. If `value` is a number, it will be coerced to a valid byte value,
+ * an integer between 0 and 255.
+ *
+ * If `byteOffset` is not a number, it will be coerced to a number. Any arguments
+ * that coerce to `NaN`, like `{}` or `undefined`, will search the whole buffer.
+ * This behavior matches [`String.prototype.lastIndexOf()`](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/lastIndexOf).
+ *
+ * ```js
+ * import { Buffer } from 'node:buffer';
+ *
+ * const b = Buffer.from('abcdef');
+ *
+ * // Passing a value that's a number, but not a valid byte.
+ * // Prints: 2, equivalent to searching for 99 or 'c'.
+ * console.log(b.lastIndexOf(99.9));
+ * console.log(b.lastIndexOf(256 + 99));
+ *
+ * // Passing a byteOffset that coerces to NaN.
+ * // Prints: 1, searching the whole buffer.
+ * console.log(b.lastIndexOf('b', undefined));
+ * console.log(b.lastIndexOf('b', {}));
+ *
+ * // Passing a byteOffset that coerces to 0.
+ * // Prints: -1, equivalent to passing 0.
+ * console.log(b.lastIndexOf('b', null));
+ * console.log(b.lastIndexOf('b', []));
+ * ```
+ *
+ * If `value` is an empty string or empty `Buffer`, `byteOffset` will be returned.
+ * @since v6.0.0
+ * @param value What to search for.
+ * @param [byteOffset=buf.length - 1] Where to begin searching in `buf`. If negative, then offset is calculated from the end of `buf`.
+ * @param [encoding='utf8'] If `value` is a string, this is the encoding used to determine the binary representation of the string that will be searched for in `buf`.
+ * @return The index of the last occurrence of `value` in `buf`, or `-1` if `buf` does not contain `value`.
+ */
+ lastIndexOf(value: string | number | Uint8Array, byteOffset?: number, encoding?: BufferEncoding): number;
+ lastIndexOf(value: string | number | Uint8Array, encoding: BufferEncoding): number;
+ /**
+ * Equivalent to `buf.indexOf() !== -1`.
+ *
+ * ```js
+ * import { Buffer } from 'node:buffer';
+ *
+ * const buf = Buffer.from('this is a buffer');
+ *
+ * console.log(buf.includes('this'));
+ * // Prints: true
+ * console.log(buf.includes('is'));
+ * // Prints: true
+ * console.log(buf.includes(Buffer.from('a buffer')));
+ * // Prints: true
+ * console.log(buf.includes(97));
+ * // Prints: true (97 is the decimal ASCII value for 'a')
+ * console.log(buf.includes(Buffer.from('a buffer example')));
+ * // Prints: false
+ * console.log(buf.includes(Buffer.from('a buffer example').slice(0, 8)));
+ * // Prints: true
+ * console.log(buf.includes('this', 4));
+ * // Prints: false
+ * ```
+ * @since v5.3.0
+ * @param value What to search for.
+ * @param [byteOffset=0] Where to begin searching in `buf`. If negative, then offset is calculated from the end of `buf`.
+ * @param [encoding='utf8'] If `value` is a string, this is its encoding.
+ * @return `true` if `value` was found in `buf`, `false` otherwise.
+ */
+ includes(value: string | number | Buffer, byteOffset?: number, encoding?: BufferEncoding): boolean;
+ includes(value: string | number | Buffer, encoding: BufferEncoding): boolean;
+ }
+ var Buffer: BufferConstructor;
+ }
+ // #region web types
+ export type BlobPart = NodeJS.BufferSource | Blob | string;
+ export interface BlobPropertyBag {
+ endings?: "native" | "transparent";
+ type?: string;
+ }
+ export interface FilePropertyBag extends BlobPropertyBag {
+ lastModified?: number;
+ }
+ export interface Blob {
+ readonly size: number;
+ readonly type: string;
+ arrayBuffer(): Promise;
+ bytes(): Promise;
+ slice(start?: number, end?: number, contentType?: string): Blob;
+ stream(): ReadableStream;
+ text(): Promise;
+ }
+ export var Blob: {
+ prototype: Blob;
+ new(blobParts?: BlobPart[], options?: BlobPropertyBag): Blob;
+ };
+ export interface File extends Blob {
+ readonly lastModified: number;
+ readonly name: string;
+ readonly webkitRelativePath: string;
+ }
+ export var File: {
+ prototype: File;
+ new(fileBits: BlobPart[], fileName: string, options?: FilePropertyBag): File;
+ };
+ export import atob = globalThis.atob;
+ export import btoa = globalThis.btoa;
+ // #endregion
+}
+declare module "buffer" {
+ export * from "node:buffer";
+}
diff --git a/.cursor/scripts/db-exec/node_modules/@types/node/child_process.d.ts b/.cursor/scripts/db-exec/node_modules/@types/node/child_process.d.ts
new file mode 100644
index 0000000..f081809
--- /dev/null
+++ b/.cursor/scripts/db-exec/node_modules/@types/node/child_process.d.ts
@@ -0,0 +1,1428 @@
+/**
+ * The `node:child_process` module provides the ability to spawn subprocesses in
+ * a manner that is similar, but not identical, to [`popen(3)`](http://man7.org/linux/man-pages/man3/popen.3.html). This capability
+ * is primarily provided by the {@link spawn} function:
+ *
+ * ```js
+ * import { spawn } from 'node:child_process';
+ * import { once } from 'node:events';
+ * const ls = spawn('ls', ['-lh', '/usr']);
+ *
+ * ls.stdout.on('data', (data) => {
+ * console.log(`stdout: ${data}`);
+ * });
+ *
+ * ls.stderr.on('data', (data) => {
+ * console.error(`stderr: ${data}`);
+ * });
+ *
+ * const [code] = await once(ls, 'close');
+ * console.log(`child process exited with code ${code}`);
+ * ```
+ *
+ * By default, pipes for `stdin`, `stdout`, and `stderr` are established between
+ * the parent Node.js process and the spawned subprocess. These pipes have
+ * limited (and platform-specific) capacity. If the subprocess writes to
+ * stdout in excess of that limit without the output being captured, the
+ * subprocess blocks, waiting for the pipe buffer to accept more data. This is
+ * identical to the behavior of pipes in the shell. Use the `{ stdio: 'ignore' }` option if the output will not be consumed.
+ *
+ * The command lookup is performed using the `options.env.PATH` environment
+ * variable if `env` is in the `options` object. Otherwise, `process.env.PATH` is
+ * used. If `options.env` is set without `PATH`, lookup on Unix is performed
+ * on a default search path search of `/usr/bin:/bin` (see your operating system's
+ * manual for execvpe/execvp), on Windows the current processes environment
+ * variable `PATH` is used.
+ *
+ * On Windows, environment variables are case-insensitive. Node.js
+ * lexicographically sorts the `env` keys and uses the first one that
+ * case-insensitively matches. Only first (in lexicographic order) entry will be
+ * passed to the subprocess. This might lead to issues on Windows when passing
+ * objects to the `env` option that have multiple variants of the same key, such as `PATH` and `Path`.
+ *
+ * The {@link spawn} method spawns the child process asynchronously,
+ * without blocking the Node.js event loop. The {@link spawnSync} function provides equivalent functionality in a synchronous manner that blocks
+ * the event loop until the spawned process either exits or is terminated.
+ *
+ * For convenience, the `node:child_process` module provides a handful of
+ * synchronous and asynchronous alternatives to {@link spawn} and {@link spawnSync}. Each of these alternatives are implemented on
+ * top of {@link spawn} or {@link spawnSync}.
+ *
+ * * {@link exec}: spawns a shell and runs a command within that
+ * shell, passing the `stdout` and `stderr` to a callback function when
+ * complete.
+ * * {@link execFile}: similar to {@link exec} except
+ * that it spawns the command directly without first spawning a shell by
+ * default.
+ * * {@link fork}: spawns a new Node.js process and invokes a
+ * specified module with an IPC communication channel established that allows
+ * sending messages between parent and child.
+ * * {@link execSync}: a synchronous version of {@link exec} that will block the Node.js event loop.
+ * * {@link execFileSync}: a synchronous version of {@link execFile} that will block the Node.js event loop.
+ *
+ * For certain use cases, such as automating shell scripts, the `synchronous counterparts` may be more convenient. In many cases, however,
+ * the synchronous methods can have significant impact on performance due to
+ * stalling the event loop while spawned processes complete.
+ * @see [source](https://github.com/nodejs/node/blob/v25.x/lib/child_process.js)
+ */
+declare module "node:child_process" {
+ import { NonSharedBuffer } from "node:buffer";
+ import * as dgram from "node:dgram";
+ import { Abortable, EventEmitter, InternalEventEmitter } from "node:events";
+ import * as net from "node:net";
+ import { Readable, Stream, Writable } from "node:stream";
+ import { URL } from "node:url";
+ type Serializable = string | object | number | boolean | bigint;
+ type SendHandle = net.Socket | net.Server | dgram.Socket | undefined;
+ interface ChildProcessEventMap {
+ "close": [code: number | null, signal: NodeJS.Signals | null];
+ "disconnect": [];
+ "error": [err: Error];
+ "exit": [code: number | null, signal: NodeJS.Signals | null];
+ "message": [message: Serializable, sendHandle: SendHandle];
+ "spawn": [];
+ }
+ /**
+ * Instances of the `ChildProcess` represent spawned child processes.
+ *
+ * Instances of `ChildProcess` are not intended to be created directly. Rather,
+ * use the {@link spawn}, {@link exec},{@link execFile}, or {@link fork} methods to create
+ * instances of `ChildProcess`.
+ * @since v2.2.0
+ */
+ class ChildProcess implements EventEmitter {
+ /**
+ * A `Writable Stream` that represents the child process's `stdin`.
+ *
+ * If a child process waits to read all of its input, the child will not continue
+ * until this stream has been closed via `end()`.
+ *
+ * If the child was spawned with `stdio[0]` set to anything other than `'pipe'`,
+ * then this will be `null`.
+ *
+ * `subprocess.stdin` is an alias for `subprocess.stdio[0]`. Both properties will
+ * refer to the same value.
+ *
+ * The `subprocess.stdin` property can be `null` or `undefined` if the child process could not be successfully spawned.
+ * @since v0.1.90
+ */
+ stdin: Writable | null;
+ /**
+ * A `Readable Stream` that represents the child process's `stdout`.
+ *
+ * If the child was spawned with `stdio[1]` set to anything other than `'pipe'`,
+ * then this will be `null`.
+ *
+ * `subprocess.stdout` is an alias for `subprocess.stdio[1]`. Both properties will
+ * refer to the same value.
+ *
+ * ```js
+ * import { spawn } from 'node:child_process';
+ *
+ * const subprocess = spawn('ls');
+ *
+ * subprocess.stdout.on('data', (data) => {
+ * console.log(`Received chunk ${data}`);
+ * });
+ * ```
+ *
+ * The `subprocess.stdout` property can be `null` or `undefined` if the child process could not be successfully spawned.
+ * @since v0.1.90
+ */
+ stdout: Readable | null;
+ /**
+ * A `Readable Stream` that represents the child process's `stderr`.
+ *
+ * If the child was spawned with `stdio[2]` set to anything other than `'pipe'`,
+ * then this will be `null`.
+ *
+ * `subprocess.stderr` is an alias for `subprocess.stdio[2]`. Both properties will
+ * refer to the same value.
+ *
+ * The `subprocess.stderr` property can be `null` or `undefined` if the child process could not be successfully spawned.
+ * @since v0.1.90
+ */
+ stderr: Readable | null;
+ /**
+ * The `subprocess.channel` property is a reference to the child's IPC channel. If
+ * no IPC channel exists, this property is `undefined`.
+ * @since v7.1.0
+ */
+ readonly channel?: Control | null;
+ /**
+ * A sparse array of pipes to the child process, corresponding with positions in
+ * the `stdio` option passed to {@link spawn} that have been set
+ * to the value `'pipe'`. `subprocess.stdio[0]`, `subprocess.stdio[1]`, and `subprocess.stdio[2]` are also available as `subprocess.stdin`, `subprocess.stdout`, and `subprocess.stderr`,
+ * respectively.
+ *
+ * In the following example, only the child's fd `1` (stdout) is configured as a
+ * pipe, so only the parent's `subprocess.stdio[1]` is a stream, all other values
+ * in the array are `null`.
+ *
+ * ```js
+ * import assert from 'node:assert';
+ * import fs from 'node:fs';
+ * import child_process from 'node:child_process';
+ *
+ * const subprocess = child_process.spawn('ls', {
+ * stdio: [
+ * 0, // Use parent's stdin for child.
+ * 'pipe', // Pipe child's stdout to parent.
+ * fs.openSync('err.out', 'w'), // Direct child's stderr to a file.
+ * ],
+ * });
+ *
+ * assert.strictEqual(subprocess.stdio[0], null);
+ * assert.strictEqual(subprocess.stdio[0], subprocess.stdin);
+ *
+ * assert(subprocess.stdout);
+ * assert.strictEqual(subprocess.stdio[1], subprocess.stdout);
+ *
+ * assert.strictEqual(subprocess.stdio[2], null);
+ * assert.strictEqual(subprocess.stdio[2], subprocess.stderr);
+ * ```
+ *
+ * The `subprocess.stdio` property can be `undefined` if the child process could
+ * not be successfully spawned.
+ * @since v0.7.10
+ */
+ readonly stdio: [
+ Writable | null,
+ // stdin
+ Readable | null,
+ // stdout
+ Readable | null,
+ // stderr
+ Readable | Writable | null | undefined,
+ // extra
+ Readable | Writable | null | undefined, // extra
+ ];
+ /**
+ * The `subprocess.killed` property indicates whether the child process
+ * successfully received a signal from `subprocess.kill()`. The `killed` property
+ * does not indicate that the child process has been terminated.
+ * @since v0.5.10
+ */
+ readonly killed: boolean;
+ /**
+ * Returns the process identifier (PID) of the child process. If the child process
+ * fails to spawn due to errors, then the value is `undefined` and `error` is
+ * emitted.
+ *
+ * ```js
+ * import { spawn } from 'node:child_process';
+ * const grep = spawn('grep', ['ssh']);
+ *
+ * console.log(`Spawned child pid: ${grep.pid}`);
+ * grep.stdin.end();
+ * ```
+ * @since v0.1.90
+ */
+ readonly pid?: number | undefined;
+ /**
+ * The `subprocess.connected` property indicates whether it is still possible to
+ * send and receive messages from a child process. When `subprocess.connected` is `false`, it is no longer possible to send or receive messages.
+ * @since v0.7.2
+ */
+ readonly connected: boolean;
+ /**
+ * The `subprocess.exitCode` property indicates the exit code of the child process.
+ * If the child process is still running, the field will be `null`.
+ */
+ readonly exitCode: number | null;
+ /**
+ * The `subprocess.signalCode` property indicates the signal received by
+ * the child process if any, else `null`.
+ */
+ readonly signalCode: NodeJS.Signals | null;
+ /**
+ * The `subprocess.spawnargs` property represents the full list of command-line
+ * arguments the child process was launched with.
+ */
+ readonly spawnargs: string[];
+ /**
+ * The `subprocess.spawnfile` property indicates the executable file name of
+ * the child process that is launched.
+ *
+ * For {@link fork}, its value will be equal to `process.execPath`.
+ * For {@link spawn}, its value will be the name of
+ * the executable file.
+ * For {@link exec}, its value will be the name of the shell
+ * in which the child process is launched.
+ */
+ readonly spawnfile: string;
+ /**
+ * The `subprocess.kill()` method sends a signal to the child process. If no
+ * argument is given, the process will be sent the `'SIGTERM'` signal. See [`signal(7)`](http://man7.org/linux/man-pages/man7/signal.7.html) for a list of available signals. This function
+ * returns `true` if [`kill(2)`](http://man7.org/linux/man-pages/man2/kill.2.html) succeeds, and `false` otherwise.
+ *
+ * ```js
+ * import { spawn } from 'node:child_process';
+ * const grep = spawn('grep', ['ssh']);
+ *
+ * grep.on('close', (code, signal) => {
+ * console.log(
+ * `child process terminated due to receipt of signal ${signal}`);
+ * });
+ *
+ * // Send SIGHUP to process.
+ * grep.kill('SIGHUP');
+ * ```
+ *
+ * The `ChildProcess` object may emit an `'error'` event if the signal
+ * cannot be delivered. Sending a signal to a child process that has already exited
+ * is not an error but may have unforeseen consequences. Specifically, if the
+ * process identifier (PID) has been reassigned to another process, the signal will
+ * be delivered to that process instead which can have unexpected results.
+ *
+ * While the function is called `kill`, the signal delivered to the child process
+ * may not actually terminate the process.
+ *
+ * See [`kill(2)`](http://man7.org/linux/man-pages/man2/kill.2.html) for reference.
+ *
+ * On Windows, where POSIX signals do not exist, the `signal` argument will be
+ * ignored, and the process will be killed forcefully and abruptly (similar to `'SIGKILL'`).
+ * See `Signal Events` for more details.
+ *
+ * On Linux, child processes of child processes will not be terminated
+ * when attempting to kill their parent. This is likely to happen when running a
+ * new process in a shell or with the use of the `shell` option of `ChildProcess`:
+ *
+ * ```js
+ * 'use strict';
+ * import { spawn } from 'node:child_process';
+ *
+ * const subprocess = spawn(
+ * 'sh',
+ * [
+ * '-c',
+ * `node -e "setInterval(() => {
+ * console.log(process.pid, 'is alive')
+ * }, 500);"`,
+ * ], {
+ * stdio: ['inherit', 'inherit', 'inherit'],
+ * },
+ * );
+ *
+ * setTimeout(() => {
+ * subprocess.kill(); // Does not terminate the Node.js process in the shell.
+ * }, 2000);
+ * ```
+ * @since v0.1.90
+ */
+ kill(signal?: NodeJS.Signals | number): boolean;
+ /**
+ * Calls {@link ChildProcess.kill} with `'SIGTERM'`.
+ * @since v20.5.0
+ */
+ [Symbol.dispose](): void;
+ /**
+ * When an IPC channel has been established between the parent and child (
+ * i.e. when using {@link fork}), the `subprocess.send()` method can
+ * be used to send messages to the child process. When the child process is a
+ * Node.js instance, these messages can be received via the `'message'` event.
+ *
+ * The message goes through serialization and parsing. The resulting
+ * message might not be the same as what is originally sent.
+ *
+ * For example, in the parent script:
+ *
+ * ```js
+ * import cp from 'node:child_process';
+ * const n = cp.fork(`${__dirname}/sub.js`);
+ *
+ * n.on('message', (m) => {
+ * console.log('PARENT got message:', m);
+ * });
+ *
+ * // Causes the child to print: CHILD got message: { hello: 'world' }
+ * n.send({ hello: 'world' });
+ * ```
+ *
+ * And then the child script, `'sub.js'` might look like this:
+ *
+ * ```js
+ * process.on('message', (m) => {
+ * console.log('CHILD got message:', m);
+ * });
+ *
+ * // Causes the parent to print: PARENT got message: { foo: 'bar', baz: null }
+ * process.send({ foo: 'bar', baz: NaN });
+ * ```
+ *
+ * Child Node.js processes will have a `process.send()` method of their own
+ * that allows the child to send messages back to the parent.
+ *
+ * There is a special case when sending a `{cmd: 'NODE_foo'}` message. Messages
+ * containing a `NODE_` prefix in the `cmd` property are reserved for use within
+ * Node.js core and will not be emitted in the child's `'message'` event. Rather, such messages are emitted using the `'internalMessage'` event and are consumed internally by Node.js.
+ * Applications should avoid using such messages or listening for `'internalMessage'` events as it is subject to change without notice.
+ *
+ * The optional `sendHandle` argument that may be passed to `subprocess.send()` is
+ * for passing a TCP server or socket object to the child process. The child will
+ * receive the object as the second argument passed to the callback function
+ * registered on the `'message'` event. Any data that is received and buffered in
+ * the socket will not be sent to the child. Sending IPC sockets is not supported on Windows.
+ *
+ * The optional `callback` is a function that is invoked after the message is
+ * sent but before the child may have received it. The function is called with a
+ * single argument: `null` on success, or an `Error` object on failure.
+ *
+ * If no `callback` function is provided and the message cannot be sent, an `'error'` event will be emitted by the `ChildProcess` object. This can
+ * happen, for instance, when the child process has already exited.
+ *
+ * `subprocess.send()` will return `false` if the channel has closed or when the
+ * backlog of unsent messages exceeds a threshold that makes it unwise to send
+ * more. Otherwise, the method returns `true`. The `callback` function can be
+ * used to implement flow control.
+ *
+ * #### Example: sending a server object
+ *
+ * The `sendHandle` argument can be used, for instance, to pass the handle of
+ * a TCP server object to the child process as illustrated in the example below:
+ *
+ * ```js
+ * import { createServer } from 'node:net';
+ * import { fork } from 'node:child_process';
+ * const subprocess = fork('subprocess.js');
+ *
+ * // Open up the server object and send the handle.
+ * const server = createServer();
+ * server.on('connection', (socket) => {
+ * socket.end('handled by parent');
+ * });
+ * server.listen(1337, () => {
+ * subprocess.send('server', server);
+ * });
+ * ```
+ *
+ * The child would then receive the server object as:
+ *
+ * ```js
+ * process.on('message', (m, server) => {
+ * if (m === 'server') {
+ * server.on('connection', (socket) => {
+ * socket.end('handled by child');
+ * });
+ * }
+ * });
+ * ```
+ *
+ * Once the server is now shared between the parent and child, some connections
+ * can be handled by the parent and some by the child.
+ *
+ * While the example above uses a server created using the `node:net` module, `node:dgram` module servers use exactly the same workflow with the exceptions of
+ * listening on a `'message'` event instead of `'connection'` and using `server.bind()` instead of `server.listen()`. This is, however, only
+ * supported on Unix platforms.
+ *
+ * #### Example: sending a socket object
+ *
+ * Similarly, the `sendHandler` argument can be used to pass the handle of a
+ * socket to the child process. The example below spawns two children that each
+ * handle connections with "normal" or "special" priority:
+ *
+ * ```js
+ * import { createServer } from 'node:net';
+ * import { fork } from 'node:child_process';
+ * const normal = fork('subprocess.js', ['normal']);
+ * const special = fork('subprocess.js', ['special']);
+ *
+ * // Open up the server and send sockets to child. Use pauseOnConnect to prevent
+ * // the sockets from being read before they are sent to the child process.
+ * const server = createServer({ pauseOnConnect: true });
+ * server.on('connection', (socket) => {
+ *
+ * // If this is special priority...
+ * if (socket.remoteAddress === '74.125.127.100') {
+ * special.send('socket', socket);
+ * return;
+ * }
+ * // This is normal priority.
+ * normal.send('socket', socket);
+ * });
+ * server.listen(1337);
+ * ```
+ *
+ * The `subprocess.js` would receive the socket handle as the second argument
+ * passed to the event callback function:
+ *
+ * ```js
+ * process.on('message', (m, socket) => {
+ * if (m === 'socket') {
+ * if (socket) {
+ * // Check that the client socket exists.
+ * // It is possible for the socket to be closed between the time it is
+ * // sent and the time it is received in the child process.
+ * socket.end(`Request handled with ${process.argv[2]} priority`);
+ * }
+ * }
+ * });
+ * ```
+ *
+ * Do not use `.maxConnections` on a socket that has been passed to a subprocess.
+ * The parent cannot track when the socket is destroyed.
+ *
+ * Any `'message'` handlers in the subprocess should verify that `socket` exists,
+ * as the connection may have been closed during the time it takes to send the
+ * connection to the child.
+ * @since v0.5.9
+ * @param sendHandle `undefined`, or a [`net.Socket`](https://nodejs.org/docs/latest-v25.x/api/net.html#class-netsocket), [`net.Server`](https://nodejs.org/docs/latest-v25.x/api/net.html#class-netserver), or [`dgram.Socket`](https://nodejs.org/docs/latest-v25.x/api/dgram.html#class-dgramsocket) object.
+ * @param options The `options` argument, if present, is an object used to parameterize the sending of certain types of handles. `options` supports the following properties:
+ */
+ send(message: Serializable, callback?: (error: Error | null) => void): boolean;
+ send(message: Serializable, sendHandle?: SendHandle, callback?: (error: Error | null) => void): boolean;
+ send(
+ message: Serializable,
+ sendHandle?: SendHandle,
+ options?: MessageOptions,
+ callback?: (error: Error | null) => void,
+ ): boolean;
+ /**
+ * Closes the IPC channel between parent and child, allowing the child to exit
+ * gracefully once there are no other connections keeping it alive. After calling
+ * this method the `subprocess.connected` and `process.connected` properties in
+ * both the parent and child (respectively) will be set to `false`, and it will be
+ * no longer possible to pass messages between the processes.
+ *
+ * The `'disconnect'` event will be emitted when there are no messages in the
+ * process of being received. This will most often be triggered immediately after
+ * calling `subprocess.disconnect()`.
+ *
+ * When the child process is a Node.js instance (e.g. spawned using {@link fork}), the `process.disconnect()` method can be invoked
+ * within the child process to close the IPC channel as well.
+ * @since v0.7.2
+ */
+ disconnect(): void;
+ /**
+ * By default, the parent will wait for the detached child to exit. To prevent the
+ * parent from waiting for a given `subprocess` to exit, use the `subprocess.unref()` method. Doing so will cause the parent's event loop to not
+ * include the child in its reference count, allowing the parent to exit
+ * independently of the child, unless there is an established IPC channel between
+ * the child and the parent.
+ *
+ * ```js
+ * import { spawn } from 'node:child_process';
+ *
+ * const subprocess = spawn(process.argv[0], ['child_program.js'], {
+ * detached: true,
+ * stdio: 'ignore',
+ * });
+ *
+ * subprocess.unref();
+ * ```
+ * @since v0.7.10
+ */
+ unref(): void;
+ /**
+ * Calling `subprocess.ref()` after making a call to `subprocess.unref()` will
+ * restore the removed reference count for the child process, forcing the parent
+ * to wait for the child to exit before exiting itself.
+ *
+ * ```js
+ * import { spawn } from 'node:child_process';
+ *
+ * const subprocess = spawn(process.argv[0], ['child_program.js'], {
+ * detached: true,
+ * stdio: 'ignore',
+ * });
+ *
+ * subprocess.unref();
+ * subprocess.ref();
+ * ```
+ * @since v0.7.10
+ */
+ ref(): void;
+ }
+ interface ChildProcess extends InternalEventEmitter {}
+ // return this object when stdio option is undefined or not specified
+ interface ChildProcessWithoutNullStreams extends ChildProcess {
+ stdin: Writable;
+ stdout: Readable;
+ stderr: Readable;
+ readonly stdio: [
+ Writable,
+ Readable,
+ Readable,
+ // stderr
+ Readable | Writable | null | undefined,
+ // extra, no modification
+ Readable | Writable | null | undefined, // extra, no modification
+ ];
+ }
+ // return this object when stdio option is a tuple of 3
+ interface ChildProcessByStdio
+ extends ChildProcess
+ {
+ stdin: I;
+ stdout: O;
+ stderr: E;
+ readonly stdio: [
+ I,
+ O,
+ E,
+ Readable | Writable | null | undefined,
+ // extra, no modification
+ Readable | Writable | null | undefined, // extra, no modification
+ ];
+ }
+ interface Control extends EventEmitter {
+ ref(): void;
+ unref(): void;
+ }
+ interface MessageOptions {
+ keepOpen?: boolean | undefined;
+ }
+ type IOType = "overlapped" | "pipe" | "ignore" | "inherit";
+ type StdioOptions = IOType | Array;
+ type SerializationType = "json" | "advanced";
+ interface MessagingOptions extends Abortable {
+ /**
+ * Specify the kind of serialization used for sending messages between processes.
+ * @default 'json'
+ */
+ serialization?: SerializationType | undefined;
+ /**
+ * The signal value to be used when the spawned process will be killed by the abort signal.
+ * @default 'SIGTERM'
+ */
+ killSignal?: NodeJS.Signals | number | undefined;
+ /**
+ * In milliseconds the maximum amount of time the process is allowed to run.
+ */
+ timeout?: number | undefined;
+ }
+ interface ProcessEnvOptions {
+ uid?: number | undefined;
+ gid?: number | undefined;
+ cwd?: string | URL | undefined;
+ env?: NodeJS.ProcessEnv | undefined;
+ }
+ interface CommonOptions extends ProcessEnvOptions {
+ /**
+ * @default false
+ */
+ windowsHide?: boolean | undefined;
+ /**
+ * @default 0
+ */
+ timeout?: number | undefined;
+ }
+ interface CommonSpawnOptions extends CommonOptions, MessagingOptions, Abortable {
+ argv0?: string | undefined;
+ /**
+ * Can be set to 'pipe', 'inherit', 'overlapped', or 'ignore', or an array of these strings.
+ * If passed as an array, the first element is used for `stdin`, the second for
+ * `stdout`, and the third for `stderr`. A fourth element can be used to
+ * specify the `stdio` behavior beyond the standard streams. See
+ * {@link ChildProcess.stdio} for more information.
+ *
+ * @default 'pipe'
+ */
+ stdio?: StdioOptions | undefined;
+ shell?: boolean | string | undefined;
+ windowsVerbatimArguments?: boolean | undefined;
+ }
+ interface SpawnOptions extends CommonSpawnOptions {
+ detached?: boolean | undefined;
+ }
+ interface SpawnOptionsWithoutStdio extends SpawnOptions {
+ stdio?: StdioPipeNamed | StdioPipe[] | undefined;
+ }
+ type StdioNull = "inherit" | "ignore" | Stream;
+ type StdioPipeNamed = "pipe" | "overlapped";
+ type StdioPipe = undefined | null | StdioPipeNamed;
+ interface SpawnOptionsWithStdioTuple<
+ Stdin extends StdioNull | StdioPipe,
+ Stdout extends StdioNull | StdioPipe,
+ Stderr extends StdioNull | StdioPipe,
+ > extends SpawnOptions {
+ stdio: [Stdin, Stdout, Stderr];
+ }
+ /**
+ * The `child_process.spawn()` method spawns a new process using the given `command`, with command-line arguments in `args`. If omitted, `args` defaults
+ * to an empty array.
+ *
+ * **If the `shell` option is enabled, do not pass unsanitized user input to this**
+ * **function. Any input containing shell metacharacters may be used to trigger**
+ * **arbitrary command execution.**
+ *
+ * A third argument may be used to specify additional options, with these defaults:
+ *
+ * ```js
+ * const defaults = {
+ * cwd: undefined,
+ * env: process.env,
+ * };
+ * ```
+ *
+ * Use `cwd` to specify the working directory from which the process is spawned.
+ * If not given, the default is to inherit the current working directory. If given,
+ * but the path does not exist, the child process emits an `ENOENT` error
+ * and exits immediately. `ENOENT` is also emitted when the command
+ * does not exist.
+ *
+ * Use `env` to specify environment variables that will be visible to the new
+ * process, the default is `process.env`.
+ *
+ * `undefined` values in `env` will be ignored.
+ *
+ * Example of running `ls -lh /usr`, capturing `stdout`, `stderr`, and the
+ * exit code:
+ *
+ * ```js
+ * import { spawn } from 'node:child_process';
+ * import { once } from 'node:events';
+ * const ls = spawn('ls', ['-lh', '/usr']);
+ *
+ * ls.stdout.on('data', (data) => {
+ * console.log(`stdout: ${data}`);
+ * });
+ *
+ * ls.stderr.on('data', (data) => {
+ * console.error(`stderr: ${data}`);
+ * });
+ *
+ * const [code] = await once(ls, 'close');
+ * console.log(`child process exited with code ${code}`);
+ * ```
+ *
+ * Example: A very elaborate way to run `ps ax | grep ssh`
+ *
+ * ```js
+ * import { spawn } from 'node:child_process';
+ * const ps = spawn('ps', ['ax']);
+ * const grep = spawn('grep', ['ssh']);
+ *
+ * ps.stdout.on('data', (data) => {
+ * grep.stdin.write(data);
+ * });
+ *
+ * ps.stderr.on('data', (data) => {
+ * console.error(`ps stderr: ${data}`);
+ * });
+ *
+ * ps.on('close', (code) => {
+ * if (code !== 0) {
+ * console.log(`ps process exited with code ${code}`);
+ * }
+ * grep.stdin.end();
+ * });
+ *
+ * grep.stdout.on('data', (data) => {
+ * console.log(data.toString());
+ * });
+ *
+ * grep.stderr.on('data', (data) => {
+ * console.error(`grep stderr: ${data}`);
+ * });
+ *
+ * grep.on('close', (code) => {
+ * if (code !== 0) {
+ * console.log(`grep process exited with code ${code}`);
+ * }
+ * });
+ * ```
+ *
+ * Example of checking for failed `spawn`:
+ *
+ * ```js
+ * import { spawn } from 'node:child_process';
+ * const subprocess = spawn('bad_command');
+ *
+ * subprocess.on('error', (err) => {
+ * console.error('Failed to start subprocess.');
+ * });
+ * ```
+ *
+ * Certain platforms (macOS, Linux) will use the value of `argv[0]` for the process
+ * title while others (Windows, SunOS) will use `command`.
+ *
+ * Node.js overwrites `argv[0]` with `process.execPath` on startup, so `process.argv[0]` in a Node.js child process will not match the `argv0` parameter passed to `spawn` from the parent. Retrieve
+ * it with the `process.argv0` property instead.
+ *
+ * If the `signal` option is enabled, calling `.abort()` on the corresponding `AbortController` is similar to calling `.kill()` on the child process except
+ * the error passed to the callback will be an `AbortError`:
+ *
+ * ```js
+ * import { spawn } from 'node:child_process';
+ * const controller = new AbortController();
+ * const { signal } = controller;
+ * const grep = spawn('grep', ['ssh'], { signal });
+ * grep.on('error', (err) => {
+ * // This will be called with err being an AbortError if the controller aborts
+ * });
+ * controller.abort(); // Stops the child process
+ * ```
+ * @since v0.1.90
+ * @param command The command to run.
+ * @param args List of string arguments.
+ */
+ function spawn(command: string, options?: SpawnOptionsWithoutStdio): ChildProcessWithoutNullStreams;
+ function spawn(
+ command: string,
+ options: SpawnOptionsWithStdioTuple,
+ ): ChildProcessByStdio;
+ function spawn(
+ command: string,
+ options: SpawnOptionsWithStdioTuple,
+ ): ChildProcessByStdio;
+ function spawn(
+ command: string,
+ options: SpawnOptionsWithStdioTuple,
+ ): ChildProcessByStdio;
+ function spawn(
+ command: string,
+ options: SpawnOptionsWithStdioTuple,
+ ): ChildProcessByStdio;
+ function spawn(
+ command: string,
+ options: SpawnOptionsWithStdioTuple,
+ ): ChildProcessByStdio;
+ function spawn(
+ command: string,
+ options: SpawnOptionsWithStdioTuple,
+ ): ChildProcessByStdio;
+ function spawn(
+ command: string,
+ options: SpawnOptionsWithStdioTuple,
+ ): ChildProcessByStdio;
+ function spawn(
+ command: string,
+ options: SpawnOptionsWithStdioTuple,
+ ): ChildProcessByStdio;
+ function spawn(command: string, options: SpawnOptions): ChildProcess;
+ // overloads of spawn with 'args'
+ function spawn(
+ command: string,
+ args?: readonly string[],
+ options?: SpawnOptionsWithoutStdio,
+ ): ChildProcessWithoutNullStreams;
+ function spawn(
+ command: string,
+ args: readonly string[],
+ options: SpawnOptionsWithStdioTuple,
+ ): ChildProcessByStdio;
+ function spawn(
+ command: string,
+ args: readonly string[],
+ options: SpawnOptionsWithStdioTuple,
+ ): ChildProcessByStdio;
+ function spawn(
+ command: string,
+ args: readonly string[],
+ options: SpawnOptionsWithStdioTuple,
+ ): ChildProcessByStdio;
+ function spawn(
+ command: string,
+ args: readonly string[],
+ options: SpawnOptionsWithStdioTuple,
+ ): ChildProcessByStdio;
+ function spawn(
+ command: string,
+ args: readonly string[],
+ options: SpawnOptionsWithStdioTuple,
+ ): ChildProcessByStdio;
+ function spawn(
+ command: string,
+ args: readonly string[],
+ options: SpawnOptionsWithStdioTuple,
+ ): ChildProcessByStdio;
+ function spawn(
+ command: string,
+ args: readonly string[],
+ options: SpawnOptionsWithStdioTuple,
+ ): ChildProcessByStdio;
+ function spawn(
+ command: string,
+ args: readonly string[],
+ options: SpawnOptionsWithStdioTuple,
+ ): ChildProcessByStdio;
+ function spawn(command: string, args: readonly string[], options: SpawnOptions): ChildProcess;
+ interface ExecOptions extends CommonOptions {
+ shell?: string | undefined;
+ signal?: AbortSignal | undefined;
+ maxBuffer?: number | undefined;
+ killSignal?: NodeJS.Signals | number | undefined;
+ encoding?: string | null | undefined;
+ }
+ interface ExecOptionsWithStringEncoding extends ExecOptions {
+ encoding?: BufferEncoding | undefined;
+ }
+ interface ExecOptionsWithBufferEncoding extends ExecOptions {
+ encoding: "buffer" | null; // specify `null`.
+ }
+ // TODO: Just Plain Wrong™ (see also nodejs/node#57392)
+ interface ExecException extends Error {
+ cmd?: string;
+ killed?: boolean;
+ code?: number;
+ signal?: NodeJS.Signals;
+ stdout?: string;
+ stderr?: string;
+ }
+ /**
+ * Spawns a shell then executes the `command` within that shell, buffering any
+ * generated output. The `command` string passed to the exec function is processed
+ * directly by the shell and special characters (vary based on [shell](https://en.wikipedia.org/wiki/List_of_command-line_interpreters))
+ * need to be dealt with accordingly:
+ *
+ * ```js
+ * import { exec } from 'node:child_process';
+ *
+ * exec('"/path/to/test file/test.sh" arg1 arg2');
+ * // Double quotes are used so that the space in the path is not interpreted as
+ * // a delimiter of multiple arguments.
+ *
+ * exec('echo "The \\$HOME variable is $HOME"');
+ * // The $HOME variable is escaped in the first instance, but not in the second.
+ * ```
+ *
+ * **Never pass unsanitized user input to this function. Any input containing shell**
+ * **metacharacters may be used to trigger arbitrary command execution.**
+ *
+ * If a `callback` function is provided, it is called with the arguments `(error, stdout, stderr)`. On success, `error` will be `null`. On error, `error` will be an instance of `Error`. The
+ * `error.code` property will be
+ * the exit code of the process. By convention, any exit code other than `0` indicates an error. `error.signal` will be the signal that terminated the
+ * process.
+ *
+ * The `stdout` and `stderr` arguments passed to the callback will contain the
+ * stdout and stderr output of the child process. By default, Node.js will decode
+ * the output as UTF-8 and pass strings to the callback. The `encoding` option
+ * can be used to specify the character encoding used to decode the stdout and
+ * stderr output. If `encoding` is `'buffer'`, or an unrecognized character
+ * encoding, `Buffer` objects will be passed to the callback instead.
+ *
+ * ```js
+ * import { exec } from 'node:child_process';
+ * exec('cat *.js missing_file | wc -l', (error, stdout, stderr) => {
+ * if (error) {
+ * console.error(`exec error: ${error}`);
+ * return;
+ * }
+ * console.log(`stdout: ${stdout}`);
+ * console.error(`stderr: ${stderr}`);
+ * });
+ * ```
+ *
+ * If `timeout` is greater than `0`, the parent will send the signal
+ * identified by the `killSignal` property (the default is `'SIGTERM'`) if the
+ * child runs longer than `timeout` milliseconds.
+ *
+ * Unlike the [`exec(3)`](http://man7.org/linux/man-pages/man3/exec.3.html) POSIX system call, `child_process.exec()` does not replace
+ * the existing process and uses a shell to execute the command.
+ *
+ * If this method is invoked as its `util.promisify()` ed version, it returns
+ * a `Promise` for an `Object` with `stdout` and `stderr` properties. The returned `ChildProcess` instance is attached to the `Promise` as a `child` property. In
+ * case of an error (including any error resulting in an exit code other than 0), a
+ * rejected promise is returned, with the same `error` object given in the
+ * callback, but with two additional properties `stdout` and `stderr`.
+ *
+ * ```js
+ * import util from 'node:util';
+ * import child_process from 'node:child_process';
+ * const exec = util.promisify(child_process.exec);
+ *
+ * async function lsExample() {
+ * const { stdout, stderr } = await exec('ls');
+ * console.log('stdout:', stdout);
+ * console.error('stderr:', stderr);
+ * }
+ * lsExample();
+ * ```
+ *
+ * If the `signal` option is enabled, calling `.abort()` on the corresponding `AbortController` is similar to calling `.kill()` on the child process except
+ * the error passed to the callback will be an `AbortError`:
+ *
+ * ```js
+ * import { exec } from 'node:child_process';
+ * const controller = new AbortController();
+ * const { signal } = controller;
+ * const child = exec('grep ssh', { signal }, (error) => {
+ * console.error(error); // an AbortError
+ * });
+ * controller.abort();
+ * ```
+ * @since v0.1.90
+ * @param command The command to run, with space-separated arguments.
+ * @param callback called with the output when process terminates.
+ */
+ function exec(
+ command: string,
+ callback?: (error: ExecException | null, stdout: string, stderr: string) => void,
+ ): ChildProcess;
+ // `options` with `"buffer"` or `null` for `encoding` means stdout/stderr are definitely `Buffer`.
+ function exec(
+ command: string,
+ options: ExecOptionsWithBufferEncoding,
+ callback?: (error: ExecException | null, stdout: NonSharedBuffer, stderr: NonSharedBuffer) => void,
+ ): ChildProcess;
+ // `options` with well-known or absent `encoding` means stdout/stderr are definitely `string`.
+ function exec(
+ command: string,
+ options: ExecOptionsWithStringEncoding,
+ callback?: (error: ExecException | null, stdout: string, stderr: string) => void,
+ ): ChildProcess;
+ // fallback if nothing else matches. Worst case is always `string | Buffer`.
+ function exec(
+ command: string,
+ options: ExecOptions | undefined | null,
+ callback?: (
+ error: ExecException | null,
+ stdout: string | NonSharedBuffer,
+ stderr: string | NonSharedBuffer,
+ ) => void,
+ ): ChildProcess;
+ interface PromiseWithChild extends Promise {
+ child: ChildProcess;
+ }
+ namespace exec {
+ function __promisify__(command: string): PromiseWithChild<{
+ stdout: string;
+ stderr: string;
+ }>;
+ function __promisify__(
+ command: string,
+ options: ExecOptionsWithBufferEncoding,
+ ): PromiseWithChild<{
+ stdout: NonSharedBuffer;
+ stderr: NonSharedBuffer;
+ }>;
+ function __promisify__(
+ command: string,
+ options: ExecOptionsWithStringEncoding,
+ ): PromiseWithChild<{
+ stdout: string;
+ stderr: string;
+ }>;
+ function __promisify__(
+ command: string,
+ options: ExecOptions | undefined | null,
+ ): PromiseWithChild<{
+ stdout: string | NonSharedBuffer;
+ stderr: string | NonSharedBuffer;
+ }>;
+ }
+ interface ExecFileOptions extends CommonOptions, Abortable {
+ maxBuffer?: number | undefined;
+ killSignal?: NodeJS.Signals | number | undefined;
+ windowsVerbatimArguments?: boolean | undefined;
+ shell?: boolean | string | undefined;
+ signal?: AbortSignal | undefined;
+ encoding?: string | null | undefined;
+ }
+ interface ExecFileOptionsWithStringEncoding extends ExecFileOptions {
+ encoding?: BufferEncoding | undefined;
+ }
+ interface ExecFileOptionsWithBufferEncoding extends ExecFileOptions {
+ encoding: "buffer" | null;
+ }
+ /** @deprecated Use `ExecFileOptions` instead. */
+ interface ExecFileOptionsWithOtherEncoding extends ExecFileOptions {}
+ // TODO: execFile exceptions can take many forms... this accurately describes none of them
+ type ExecFileException =
+ & Omit
+ & Omit
+ & { code?: string | number | null };
+ /**
+ * The `child_process.execFile()` function is similar to {@link exec} except that it does not spawn a shell by default. Rather, the specified
+ * executable `file` is spawned directly as a new process making it slightly more
+ * efficient than {@link exec}.
+ *
+ * The same options as {@link exec} are supported. Since a shell is
+ * not spawned, behaviors such as I/O redirection and file globbing are not
+ * supported.
+ *
+ * ```js
+ * import { execFile } from 'node:child_process';
+ * const child = execFile('node', ['--version'], (error, stdout, stderr) => {
+ * if (error) {
+ * throw error;
+ * }
+ * console.log(stdout);
+ * });
+ * ```
+ *
+ * The `stdout` and `stderr` arguments passed to the callback will contain the
+ * stdout and stderr output of the child process. By default, Node.js will decode
+ * the output as UTF-8 and pass strings to the callback. The `encoding` option
+ * can be used to specify the character encoding used to decode the stdout and
+ * stderr output. If `encoding` is `'buffer'`, or an unrecognized character
+ * encoding, `Buffer` objects will be passed to the callback instead.
+ *
+ * If this method is invoked as its `util.promisify()` ed version, it returns
+ * a `Promise` for an `Object` with `stdout` and `stderr` properties. The returned `ChildProcess` instance is attached to the `Promise` as a `child` property. In
+ * case of an error (including any error resulting in an exit code other than 0), a
+ * rejected promise is returned, with the same `error` object given in the
+ * callback, but with two additional properties `stdout` and `stderr`.
+ *
+ * ```js
+ * import util from 'node:util';
+ * import child_process from 'node:child_process';
+ * const execFile = util.promisify(child_process.execFile);
+ * async function getVersion() {
+ * const { stdout } = await execFile('node', ['--version']);
+ * console.log(stdout);
+ * }
+ * getVersion();
+ * ```
+ *
+ * **If the `shell` option is enabled, do not pass unsanitized user input to this**
+ * **function. Any input containing shell metacharacters may be used to trigger**
+ * **arbitrary command execution.**
+ *
+ * If the `signal` option is enabled, calling `.abort()` on the corresponding `AbortController` is similar to calling `.kill()` on the child process except
+ * the error passed to the callback will be an `AbortError`:
+ *
+ * ```js
+ * import { execFile } from 'node:child_process';
+ * const controller = new AbortController();
+ * const { signal } = controller;
+ * const child = execFile('node', ['--version'], { signal }, (error) => {
+ * console.error(error); // an AbortError
+ * });
+ * controller.abort();
+ * ```
+ * @since v0.1.91
+ * @param file The name or path of the executable file to run.
+ * @param args List of string arguments.
+ * @param callback Called with the output when process terminates.
+ */
+ // no `options` definitely means stdout/stderr are `string`.
+ function execFile(
+ file: string,
+ callback?: (error: ExecFileException | null, stdout: string, stderr: string) => void,
+ ): ChildProcess;
+ function execFile(
+ file: string,
+ args: readonly string[] | undefined | null,
+ callback?: (error: ExecFileException | null, stdout: string, stderr: string) => void,
+ ): ChildProcess;
+ // `options` with `"buffer"` or `null` for `encoding` means stdout/stderr are definitely `Buffer`.
+ function execFile(
+ file: string,
+ options: ExecFileOptionsWithBufferEncoding,
+ callback?: (error: ExecFileException | null, stdout: NonSharedBuffer, stderr: NonSharedBuffer) => void,
+ ): ChildProcess;
+ function execFile(
+ file: string,
+ args: readonly string[] | undefined | null,
+ options: ExecFileOptionsWithBufferEncoding,
+ callback?: (error: ExecFileException | null, stdout: NonSharedBuffer, stderr: NonSharedBuffer) => void,
+ ): ChildProcess;
+ // `options` with well-known or absent `encoding` means stdout/stderr are definitely `string`.
+ function execFile(
+ file: string,
+ options: ExecFileOptionsWithStringEncoding,
+ callback?: (error: ExecFileException | null, stdout: string, stderr: string) => void,
+ ): ChildProcess;
+ function execFile(
+ file: string,
+ args: readonly string[] | undefined | null,
+ options: ExecFileOptionsWithStringEncoding,
+ callback?: (error: ExecFileException | null, stdout: string, stderr: string) => void,
+ ): ChildProcess;
+ // fallback if nothing else matches. Worst case is always `string | Buffer`.
+ function execFile(
+ file: string,
+ options: ExecFileOptions | undefined | null,
+ callback:
+ | ((
+ error: ExecFileException | null,
+ stdout: string | NonSharedBuffer,
+ stderr: string | NonSharedBuffer,
+ ) => void)
+ | undefined
+ | null,
+ ): ChildProcess;
+ function execFile(
+ file: string,
+ args: readonly string[] | undefined | null,
+ options: ExecFileOptions | undefined | null,
+ callback:
+ | ((
+ error: ExecFileException | null,
+ stdout: string | NonSharedBuffer,
+ stderr: string | NonSharedBuffer,
+ ) => void)
+ | undefined
+ | null,
+ ): ChildProcess;
+ namespace execFile {
+ function __promisify__(file: string): PromiseWithChild<{
+ stdout: string;
+ stderr: string;
+ }>;
+ function __promisify__(
+ file: string,
+ args: readonly string[] | undefined | null,
+ ): PromiseWithChild<{
+ stdout: string;
+ stderr: string;
+ }>;
+ function __promisify__(
+ file: string,
+ options: ExecFileOptionsWithBufferEncoding,
+ ): PromiseWithChild<{
+ stdout: NonSharedBuffer;
+ stderr: NonSharedBuffer;
+ }>;
+ function __promisify__(
+ file: string,
+ args: readonly string[] | undefined | null,
+ options: ExecFileOptionsWithBufferEncoding,
+ ): PromiseWithChild<{
+ stdout: NonSharedBuffer;
+ stderr: NonSharedBuffer;
+ }>;
+ function __promisify__(
+ file: string,
+ options: ExecFileOptionsWithStringEncoding,
+ ): PromiseWithChild<{
+ stdout: string;
+ stderr: string;
+ }>;
+ function __promisify__(
+ file: string,
+ args: readonly string[] | undefined | null,
+ options: ExecFileOptionsWithStringEncoding,
+ ): PromiseWithChild<{
+ stdout: string;
+ stderr: string;
+ }>;
+ function __promisify__(
+ file: string,
+ options: ExecFileOptions | undefined | null,
+ ): PromiseWithChild<{
+ stdout: string | NonSharedBuffer;
+ stderr: string | NonSharedBuffer;
+ }>;
+ function __promisify__(
+ file: string,
+ args: readonly string[] | undefined | null,
+ options: ExecFileOptions | undefined | null,
+ ): PromiseWithChild<{
+ stdout: string | NonSharedBuffer;
+ stderr: string | NonSharedBuffer;
+ }>;
+ }
+ interface ForkOptions extends ProcessEnvOptions, MessagingOptions, Abortable {
+ execPath?: string | undefined;
+ execArgv?: string[] | undefined;
+ silent?: boolean | undefined;
+ /**
+ * Can be set to 'pipe', 'inherit', 'overlapped', or 'ignore', or an array of these strings.
+ * If passed as an array, the first element is used for `stdin`, the second for
+ * `stdout`, and the third for `stderr`. A fourth element can be used to
+ * specify the `stdio` behavior beyond the standard streams. See
+ * {@link ChildProcess.stdio} for more information.
+ *
+ * @default 'pipe'
+ */
+ stdio?: StdioOptions | undefined;
+ detached?: boolean | undefined;
+ windowsVerbatimArguments?: boolean | undefined;
+ }
+ /**
+ * The `child_process.fork()` method is a special case of {@link spawn} used specifically to spawn new Node.js processes.
+ * Like {@link spawn}, a `ChildProcess` object is returned. The
+ * returned `ChildProcess` will have an additional communication channel
+ * built-in that allows messages to be passed back and forth between the parent and
+ * child. See `subprocess.send()` for details.
+ *
+ * Keep in mind that spawned Node.js child processes are
+ * independent of the parent with exception of the IPC communication channel
+ * that is established between the two. Each process has its own memory, with
+ * their own V8 instances. Because of the additional resource allocations
+ * required, spawning a large number of child Node.js processes is not
+ * recommended.
+ *
+ * By default, `child_process.fork()` will spawn new Node.js instances using the `process.execPath` of the parent process. The `execPath` property in the `options` object allows for an alternative
+ * execution path to be used.
+ *
+ * Node.js processes launched with a custom `execPath` will communicate with the
+ * parent process using the file descriptor (fd) identified using the
+ * environment variable `NODE_CHANNEL_FD` on the child process.
+ *
+ * Unlike the [`fork(2)`](http://man7.org/linux/man-pages/man2/fork.2.html) POSIX system call, `child_process.fork()` does not clone the
+ * current process.
+ *
+ * The `shell` option available in {@link spawn} is not supported by `child_process.fork()` and will be ignored if set.
+ *
+ * If the `signal` option is enabled, calling `.abort()` on the corresponding `AbortController` is similar to calling `.kill()` on the child process except
+ * the error passed to the callback will be an `AbortError`:
+ *
+ * ```js
+ * if (process.argv[2] === 'child') {
+ * setTimeout(() => {
+ * console.log(`Hello from ${process.argv[2]}!`);
+ * }, 1_000);
+ * } else {
+ * import { fork } from 'node:child_process';
+ * const controller = new AbortController();
+ * const { signal } = controller;
+ * const child = fork(__filename, ['child'], { signal });
+ * child.on('error', (err) => {
+ * // This will be called with err being an AbortError if the controller aborts
+ * });
+ * controller.abort(); // Stops the child process
+ * }
+ * ```
+ * @since v0.5.0
+ * @param modulePath The module to run in the child.
+ * @param args List of string arguments.
+ */
+ function fork(modulePath: string | URL, options?: ForkOptions): ChildProcess;
+ function fork(modulePath: string | URL, args?: readonly string[], options?: ForkOptions): ChildProcess;
+ interface SpawnSyncOptions extends CommonSpawnOptions {
+ input?: string | NodeJS.ArrayBufferView | undefined;
+ maxBuffer?: number | undefined;
+ encoding?: BufferEncoding | "buffer" | null | undefined;
+ }
+ interface SpawnSyncOptionsWithStringEncoding extends SpawnSyncOptions {
+ encoding: BufferEncoding;
+ }
+ interface SpawnSyncOptionsWithBufferEncoding extends SpawnSyncOptions {
+ encoding?: "buffer" | null | undefined;
+ }
+ interface SpawnSyncReturns {
+ pid: number;
+ output: Array;
+ stdout: T;
+ stderr: T;
+ status: number | null;
+ signal: NodeJS.Signals | null;
+ error?: Error;
+ }
+ /**
+ * The `child_process.spawnSync()` method is generally identical to {@link spawn} with the exception that the function will not return
+ * until the child process has fully closed. When a timeout has been encountered
+ * and `killSignal` is sent, the method won't return until the process has
+ * completely exited. If the process intercepts and handles the `SIGTERM` signal
+ * and doesn't exit, the parent process will wait until the child process has
+ * exited.
+ *
+ * **If the `shell` option is enabled, do not pass unsanitized user input to this**
+ * **function. Any input containing shell metacharacters may be used to trigger**
+ * **arbitrary command execution.**
+ * @since v0.11.12
+ * @param command The command to run.
+ * @param args List of string arguments.
+ */
+ function spawnSync(command: string): SpawnSyncReturns;
+ function spawnSync(command: string, options: SpawnSyncOptionsWithStringEncoding): SpawnSyncReturns;
+ function spawnSync(command: string, options: SpawnSyncOptionsWithBufferEncoding): SpawnSyncReturns;
+ function spawnSync(command: string, options?: SpawnSyncOptions): SpawnSyncReturns;
+ function spawnSync(command: string, args: readonly string[]): SpawnSyncReturns;
+ function spawnSync(
+ command: string,
+ args: readonly string[],
+ options: SpawnSyncOptionsWithStringEncoding,
+ ): SpawnSyncReturns;
+ function spawnSync(
+ command: string,
+ args: readonly string[],
+ options: SpawnSyncOptionsWithBufferEncoding,
+ ): SpawnSyncReturns;
+ function spawnSync(
+ command: string,
+ args?: readonly string[],
+ options?: SpawnSyncOptions,
+ ): SpawnSyncReturns;
+ interface CommonExecOptions extends CommonOptions {
+ input?: string | NodeJS.ArrayBufferView | undefined;
+ /**
+ * Can be set to 'pipe', 'inherit, or 'ignore', or an array of these strings.
+ * If passed as an array, the first element is used for `stdin`, the second for
+ * `stdout`, and the third for `stderr`. A fourth element can be used to
+ * specify the `stdio` behavior beyond the standard streams. See
+ * {@link ChildProcess.stdio} for more information.
+ *
+ * @default 'pipe'
+ */
+ stdio?: StdioOptions | undefined;
+ killSignal?: NodeJS.Signals | number | undefined;
+ maxBuffer?: number | undefined;
+ encoding?: BufferEncoding | "buffer" | null | undefined;
+ }
+ interface ExecSyncOptions extends CommonExecOptions {
+ shell?: string | undefined;
+ }
+ interface ExecSyncOptionsWithStringEncoding extends ExecSyncOptions {
+ encoding: BufferEncoding;
+ }
+ interface ExecSyncOptionsWithBufferEncoding extends ExecSyncOptions {
+ encoding?: "buffer" | null | undefined;
+ }
+ /**
+ * The `child_process.execSync()` method is generally identical to {@link exec} with the exception that the method will not return
+ * until the child process has fully closed. When a timeout has been encountered
+ * and `killSignal` is sent, the method won't return until the process has
+ * completely exited. If the child process intercepts and handles the `SIGTERM` signal and doesn't exit, the parent process will wait until the child process
+ * has exited.
+ *
+ * If the process times out or has a non-zero exit code, this method will throw.
+ * The `Error` object will contain the entire result from {@link spawnSync}.
+ *
+ * **Never pass unsanitized user input to this function. Any input containing shell**
+ * **metacharacters may be used to trigger arbitrary command execution.**
+ * @since v0.11.12
+ * @param command The command to run.
+ * @return The stdout from the command.
+ */
+ function execSync(command: string): NonSharedBuffer;
+ function execSync(command: string, options: ExecSyncOptionsWithStringEncoding): string;
+ function execSync(command: string, options: ExecSyncOptionsWithBufferEncoding): NonSharedBuffer;
+ function execSync(command: string, options?: ExecSyncOptions): string | NonSharedBuffer;
+ interface ExecFileSyncOptions extends CommonExecOptions {
+ shell?: boolean | string | undefined;
+ }
+ interface ExecFileSyncOptionsWithStringEncoding extends ExecFileSyncOptions {
+ encoding: BufferEncoding;
+ }
+ interface ExecFileSyncOptionsWithBufferEncoding extends ExecFileSyncOptions {
+ encoding?: "buffer" | null | undefined; // specify `null`.
+ }
+ /**
+ * The `child_process.execFileSync()` method is generally identical to {@link execFile} with the exception that the method will not
+ * return until the child process has fully closed. When a timeout has been
+ * encountered and `killSignal` is sent, the method won't return until the process
+ * has completely exited.
+ *
+ * If the child process intercepts and handles the `SIGTERM` signal and
+ * does not exit, the parent process will still wait until the child process has
+ * exited.
+ *
+ * If the process times out or has a non-zero exit code, this method will throw an `Error` that will include the full result of the underlying {@link spawnSync}.
+ *
+ * **If the `shell` option is enabled, do not pass unsanitized user input to this**
+ * **function. Any input containing shell metacharacters may be used to trigger**
+ * **arbitrary command execution.**
+ * @since v0.11.12
+ * @param file The name or path of the executable file to run.
+ * @param args List of string arguments.
+ * @return The stdout from the command.
+ */
+ function execFileSync(file: string): NonSharedBuffer;
+ function execFileSync(file: string, options: ExecFileSyncOptionsWithStringEncoding): string;
+ function execFileSync(file: string, options: ExecFileSyncOptionsWithBufferEncoding): NonSharedBuffer;
+ function execFileSync(file: string, options?: ExecFileSyncOptions): string | NonSharedBuffer;
+ function execFileSync(file: string, args: readonly string[]): NonSharedBuffer;
+ function execFileSync(
+ file: string,
+ args: readonly string[],
+ options: ExecFileSyncOptionsWithStringEncoding,
+ ): string;
+ function execFileSync(
+ file: string,
+ args: readonly string[],
+ options: ExecFileSyncOptionsWithBufferEncoding,
+ ): NonSharedBuffer;
+ function execFileSync(
+ file: string,
+ args?: readonly string[],
+ options?: ExecFileSyncOptions,
+ ): string | NonSharedBuffer;
+}
+declare module "child_process" {
+ export * from "node:child_process";
+}
diff --git a/.cursor/scripts/db-exec/node_modules/@types/node/cluster.d.ts b/.cursor/scripts/db-exec/node_modules/@types/node/cluster.d.ts
new file mode 100644
index 0000000..4e5efbf
--- /dev/null
+++ b/.cursor/scripts/db-exec/node_modules/@types/node/cluster.d.ts
@@ -0,0 +1,486 @@
+/**
+ * Clusters of Node.js processes can be used to run multiple instances of Node.js
+ * that can distribute workloads among their application threads. When process isolation
+ * is not needed, use the [`worker_threads`](https://nodejs.org/docs/latest-v25.x/api/worker_threads.html)
+ * module instead, which allows running multiple application threads within a single Node.js instance.
+ *
+ * The cluster module allows easy creation of child processes that all share
+ * server ports.
+ *
+ * ```js
+ * import cluster from 'node:cluster';
+ * import http from 'node:http';
+ * import { availableParallelism } from 'node:os';
+ * import process from 'node:process';
+ *
+ * const numCPUs = availableParallelism();
+ *
+ * if (cluster.isPrimary) {
+ * console.log(`Primary ${process.pid} is running`);
+ *
+ * // Fork workers.
+ * for (let i = 0; i < numCPUs; i++) {
+ * cluster.fork();
+ * }
+ *
+ * cluster.on('exit', (worker, code, signal) => {
+ * console.log(`worker ${worker.process.pid} died`);
+ * });
+ * } else {
+ * // Workers can share any TCP connection
+ * // In this case it is an HTTP server
+ * http.createServer((req, res) => {
+ * res.writeHead(200);
+ * res.end('hello world\n');
+ * }).listen(8000);
+ *
+ * console.log(`Worker ${process.pid} started`);
+ * }
+ * ```
+ *
+ * Running Node.js will now share port 8000 between the workers:
+ *
+ * ```console
+ * $ node server.js
+ * Primary 3596 is running
+ * Worker 4324 started
+ * Worker 4520 started
+ * Worker 6056 started
+ * Worker 5644 started
+ * ```
+ *
+ * On Windows, it is not yet possible to set up a named pipe server in a worker.
+ * @see [source](https://github.com/nodejs/node/blob/v25.x/lib/cluster.js)
+ */
+declare module "node:cluster" {
+ import * as child_process from "node:child_process";
+ import { EventEmitter, InternalEventEmitter } from "node:events";
+ class Worker implements EventEmitter {
+ constructor(options?: cluster.WorkerOptions);
+ /**
+ * Each new worker is given its own unique id, this id is stored in the `id`.
+ *
+ * While a worker is alive, this is the key that indexes it in `cluster.workers`.
+ * @since v0.8.0
+ */
+ id: number;
+ /**
+ * All workers are created using [`child_process.fork()`](https://nodejs.org/docs/latest-v25.x/api/child_process.html#child_processforkmodulepath-args-options), the returned object
+ * from this function is stored as `.process`. In a worker, the global `process` is stored.
+ *
+ * See: [Child Process module](https://nodejs.org/docs/latest-v25.x/api/child_process.html#child_processforkmodulepath-args-options).
+ *
+ * Workers will call `process.exit(0)` if the `'disconnect'` event occurs
+ * on `process` and `.exitedAfterDisconnect` is not `true`. This protects against
+ * accidental disconnection.
+ * @since v0.7.0
+ */
+ process: child_process.ChildProcess;
+ /**
+ * Send a message to a worker or primary, optionally with a handle.
+ *
+ * In the primary, this sends a message to a specific worker. It is identical to [`ChildProcess.send()`](https://nodejs.org/docs/latest-v25.x/api/child_process.html#subprocesssendmessage-sendhandle-options-callback).
+ *
+ * In a worker, this sends a message to the primary. It is identical to `process.send()`.
+ *
+ * This example will echo back all messages from the primary:
+ *
+ * ```js
+ * if (cluster.isPrimary) {
+ * const worker = cluster.fork();
+ * worker.send('hi there');
+ *
+ * } else if (cluster.isWorker) {
+ * process.on('message', (msg) => {
+ * process.send(msg);
+ * });
+ * }
+ * ```
+ * @since v0.7.0
+ * @param options The `options` argument, if present, is an object used to parameterize the sending of certain types of handles.
+ */
+ send(message: child_process.Serializable, callback?: (error: Error | null) => void): boolean;
+ send(
+ message: child_process.Serializable,
+ sendHandle: child_process.SendHandle,
+ callback?: (error: Error | null) => void,
+ ): boolean;
+ send(
+ message: child_process.Serializable,
+ sendHandle: child_process.SendHandle,
+ options?: child_process.MessageOptions,
+ callback?: (error: Error | null) => void,
+ ): boolean;
+ /**
+ * This function will kill the worker. In the primary worker, it does this by
+ * disconnecting the `worker.process`, and once disconnected, killing with `signal`. In the worker, it does it by killing the process with `signal`.
+ *
+ * The `kill()` function kills the worker process without waiting for a graceful
+ * disconnect, it has the same behavior as `worker.process.kill()`.
+ *
+ * This method is aliased as `worker.destroy()` for backwards compatibility.
+ *
+ * In a worker, `process.kill()` exists, but it is not this function;
+ * it is [`kill()`](https://nodejs.org/docs/latest-v25.x/api/process.html#processkillpid-signal).
+ * @since v0.9.12
+ * @param [signal='SIGTERM'] Name of the kill signal to send to the worker process.
+ */
+ kill(signal?: string): void;
+ destroy(signal?: string): void;
+ /**
+ * In a worker, this function will close all servers, wait for the `'close'` event
+ * on those servers, and then disconnect the IPC channel.
+ *
+ * In the primary, an internal message is sent to the worker causing it to call `.disconnect()` on itself.
+ *
+ * Causes `.exitedAfterDisconnect` to be set.
+ *
+ * After a server is closed, it will no longer accept new connections,
+ * but connections may be accepted by any other listening worker. Existing
+ * connections will be allowed to close as usual. When no more connections exist,
+ * see `server.close()`, the IPC channel to the worker will close allowing it
+ * to die gracefully.
+ *
+ * The above applies _only_ to server connections, client connections are not
+ * automatically closed by workers, and disconnect does not wait for them to close
+ * before exiting.
+ *
+ * In a worker, `process.disconnect` exists, but it is not this function;
+ * it is `disconnect()`.
+ *
+ * Because long living server connections may block workers from disconnecting, it
+ * may be useful to send a message, so application specific actions may be taken to
+ * close them. It also may be useful to implement a timeout, killing a worker if
+ * the `'disconnect'` event has not been emitted after some time.
+ *
+ * ```js
+ * import net from 'node:net';
+ *
+ * if (cluster.isPrimary) {
+ * const worker = cluster.fork();
+ * let timeout;
+ *
+ * worker.on('listening', (address) => {
+ * worker.send('shutdown');
+ * worker.disconnect();
+ * timeout = setTimeout(() => {
+ * worker.kill();
+ * }, 2000);
+ * });
+ *
+ * worker.on('disconnect', () => {
+ * clearTimeout(timeout);
+ * });
+ *
+ * } else if (cluster.isWorker) {
+ * const server = net.createServer((socket) => {
+ * // Connections never end
+ * });
+ *
+ * server.listen(8000);
+ *
+ * process.on('message', (msg) => {
+ * if (msg === 'shutdown') {
+ * // Initiate graceful close of any connections to server
+ * }
+ * });
+ * }
+ * ```
+ * @since v0.7.7
+ * @return A reference to `worker`.
+ */
+ disconnect(): this;
+ /**
+ * This function returns `true` if the worker is connected to its primary via its
+ * IPC channel, `false` otherwise. A worker is connected to its primary after it
+ * has been created. It is disconnected after the `'disconnect'` event is emitted.
+ * @since v0.11.14
+ */
+ isConnected(): boolean;
+ /**
+ * This function returns `true` if the worker's process has terminated (either
+ * because of exiting or being signaled). Otherwise, it returns `false`.
+ *
+ * ```js
+ * import cluster from 'node:cluster';
+ * import http from 'node:http';
+ * import { availableParallelism } from 'node:os';
+ * import process from 'node:process';
+ *
+ * const numCPUs = availableParallelism();
+ *
+ * if (cluster.isPrimary) {
+ * console.log(`Primary ${process.pid} is running`);
+ *
+ * // Fork workers.
+ * for (let i = 0; i < numCPUs; i++) {
+ * cluster.fork();
+ * }
+ *
+ * cluster.on('fork', (worker) => {
+ * console.log('worker is dead:', worker.isDead());
+ * });
+ *
+ * cluster.on('exit', (worker, code, signal) => {
+ * console.log('worker is dead:', worker.isDead());
+ * });
+ * } else {
+ * // Workers can share any TCP connection. In this case, it is an HTTP server.
+ * http.createServer((req, res) => {
+ * res.writeHead(200);
+ * res.end(`Current process\n ${process.pid}`);
+ * process.kill(process.pid);
+ * }).listen(8000);
+ * }
+ * ```
+ * @since v0.11.14
+ */
+ isDead(): boolean;
+ /**
+ * This property is `true` if the worker exited due to `.disconnect()`.
+ * If the worker exited any other way, it is `false`. If the
+ * worker has not exited, it is `undefined`.
+ *
+ * The boolean `worker.exitedAfterDisconnect` allows distinguishing between
+ * voluntary and accidental exit, the primary may choose not to respawn a worker
+ * based on this value.
+ *
+ * ```js
+ * cluster.on('exit', (worker, code, signal) => {
+ * if (worker.exitedAfterDisconnect === true) {
+ * console.log('Oh, it was just voluntary – no need to worry');
+ * }
+ * });
+ *
+ * // kill worker
+ * worker.kill();
+ * ```
+ * @since v6.0.0
+ */
+ exitedAfterDisconnect: boolean;
+ }
+ interface Worker extends InternalEventEmitter {}
+ type _Worker = Worker;
+ namespace cluster {
+ interface Worker extends _Worker {}
+ interface WorkerOptions {
+ id?: number | undefined;
+ process?: child_process.ChildProcess | undefined;
+ state?: string | undefined;
+ }
+ interface WorkerEventMap {
+ "disconnect": [];
+ "error": [error: Error];
+ "exit": [code: number, signal: string];
+ "listening": [address: Address];
+ "message": [message: any, handle: child_process.SendHandle];
+ "online": [];
+ }
+ interface ClusterSettings {
+ /**
+ * List of string arguments passed to the Node.js executable.
+ * @default process.execArgv
+ */
+ execArgv?: string[] | undefined;
+ /**
+ * File path to worker file.
+ * @default process.argv[1]
+ */
+ exec?: string | undefined;
+ /**
+ * String arguments passed to worker.
+ * @default process.argv.slice(2)
+ */
+ args?: readonly string[] | undefined;
+ /**
+ * Whether or not to send output to parent's stdio.
+ * @default false
+ */
+ silent?: boolean | undefined;
+ /**
+ * Configures the stdio of forked processes. Because the cluster module relies on IPC to function, this configuration must
+ * contain an `'ipc'` entry. When this option is provided, it overrides `silent`. See [`child_prcess.spawn()`](https://nodejs.org/docs/latest-v25.x/api/child_process.html#child_processspawncommand-args-options)'s
+ * [`stdio`](https://nodejs.org/docs/latest-v25.x/api/child_process.html#optionsstdio).
+ */
+ stdio?: any[] | undefined;
+ /**
+ * Sets the user identity of the process. (See [`setuid(2)`](https://man7.org/linux/man-pages/man2/setuid.2.html).)
+ */
+ uid?: number | undefined;
+ /**
+ * Sets the group identity of the process. (See [`setgid(2)`](https://man7.org/linux/man-pages/man2/setgid.2.html).)
+ */
+ gid?: number | undefined;
+ /**
+ * Sets inspector port of worker. This can be a number, or a function that takes no arguments and returns a number.
+ * By default each worker gets its own port, incremented from the primary's `process.debugPort`.
+ */
+ inspectPort?: number | (() => number) | undefined;
+ /**
+ * Specify the kind of serialization used for sending messages between processes. Possible values are `'json'` and `'advanced'`.
+ * See [Advanced serialization for `child_process`](https://nodejs.org/docs/latest-v25.x/api/child_process.html#advanced-serialization) for more details.
+ * @default false
+ */
+ serialization?: "json" | "advanced" | undefined;
+ /**
+ * Current working directory of the worker process.
+ * @default undefined (inherits from parent process)
+ */
+ cwd?: string | undefined;
+ /**
+ * Hide the forked processes console window that would normally be created on Windows systems.
+ * @default false
+ */
+ windowsHide?: boolean | undefined;
+ }
+ interface Address {
+ address: string;
+ port: number;
+ /**
+ * The `addressType` is one of:
+ *
+ * * `4` (TCPv4)
+ * * `6` (TCPv6)
+ * * `-1` (Unix domain socket)
+ * * `'udp4'` or `'udp6'` (UDPv4 or UDPv6)
+ */
+ addressType: 4 | 6 | -1 | "udp4" | "udp6";
+ }
+ interface ClusterEventMap {
+ "disconnect": [worker: Worker];
+ "exit": [worker: Worker, code: number, signal: string];
+ "fork": [worker: Worker];
+ "listening": [worker: Worker, address: Address];
+ "message": [worker: Worker, message: any, handle: child_process.SendHandle];
+ "online": [worker: Worker];
+ "setup": [settings: ClusterSettings];
+ }
+ interface Cluster extends InternalEventEmitter {
+ /**
+ * A `Worker` object contains all public information and method about a worker.
+ * In the primary it can be obtained using `cluster.workers`. In a worker
+ * it can be obtained using `cluster.worker`.
+ * @since v0.7.0
+ */
+ Worker: typeof Worker;
+ disconnect(callback?: () => void): void;
+ /**
+ * Spawn a new worker process.
+ *
+ * This can only be called from the primary process.
+ * @param env Key/value pairs to add to worker process environment.
+ * @since v0.6.0
+ */
+ fork(env?: any): Worker;
+ /** @deprecated since v16.0.0 - use isPrimary. */
+ readonly isMaster: boolean;
+ /**
+ * True if the process is a primary. This is determined by the `process.env.NODE_UNIQUE_ID`. If `process.env.NODE_UNIQUE_ID`
+ * is undefined, then `isPrimary` is `true`.
+ * @since v16.0.0
+ */
+ readonly isPrimary: boolean;
+ /**
+ * True if the process is not a primary (it is the negation of `cluster.isPrimary`).
+ * @since v0.6.0
+ */
+ readonly isWorker: boolean;
+ /**
+ * The scheduling policy, either `cluster.SCHED_RR` for round-robin or `cluster.SCHED_NONE` to leave it to the operating system. This is a
+ * global setting and effectively frozen once either the first worker is spawned, or [`.setupPrimary()`](https://nodejs.org/docs/latest-v25.x/api/cluster.html#clustersetupprimarysettings)
+ * is called, whichever comes first.
+ *
+ * `SCHED_RR` is the default on all operating systems except Windows. Windows will change to `SCHED_RR` once libuv is able to effectively distribute
+ * IOCP handles without incurring a large performance hit.
+ *
+ * `cluster.schedulingPolicy` can also be set through the `NODE_CLUSTER_SCHED_POLICY` environment variable. Valid values are `'rr'` and `'none'`.
+ * @since v0.11.2
+ */
+ schedulingPolicy: number;
+ /**
+ * After calling [`.setupPrimary()`](https://nodejs.org/docs/latest-v25.x/api/cluster.html#clustersetupprimarysettings)
+ * (or [`.fork()`](https://nodejs.org/docs/latest-v25.x/api/cluster.html#clusterforkenv)) this settings object will contain
+ * the settings, including the default values.
+ *
+ * This object is not intended to be changed or set manually.
+ * @since v0.7.1
+ */
+ readonly settings: ClusterSettings;
+ /** @deprecated since v16.0.0 - use [`.setupPrimary()`](https://nodejs.org/docs/latest-v25.x/api/cluster.html#clustersetupprimarysettings) instead. */
+ setupMaster(settings?: ClusterSettings): void;
+ /**
+ * `setupPrimary` is used to change the default 'fork' behavior. Once called, the settings will be present in `cluster.settings`.
+ *
+ * Any settings changes only affect future calls to [`.fork()`](https://nodejs.org/docs/latest-v25.x/api/cluster.html#clusterforkenv)
+ * and have no effect on workers that are already running.
+ *
+ * The only attribute of a worker that cannot be set via `.setupPrimary()` is the `env` passed to
+ * [`.fork()`](https://nodejs.org/docs/latest-v25.x/api/cluster.html#clusterforkenv).
+ *
+ * The defaults above apply to the first call only; the defaults for later calls are the current values at the time of
+ * `cluster.setupPrimary()` is called.
+ *
+ * ```js
+ * import cluster from 'node:cluster';
+ *
+ * cluster.setupPrimary({
+ * exec: 'worker.js',
+ * args: ['--use', 'https'],
+ * silent: true,
+ * });
+ * cluster.fork(); // https worker
+ * cluster.setupPrimary({
+ * exec: 'worker.js',
+ * args: ['--use', 'http'],
+ * });
+ * cluster.fork(); // http worker
+ * ```
+ *
+ * This can only be called from the primary process.
+ * @since v16.0.0
+ */
+ setupPrimary(settings?: ClusterSettings): void;
+ /**
+ * A reference to the current worker object. Not available in the primary process.
+ *
+ * ```js
+ * import cluster from 'node:cluster';
+ *
+ * if (cluster.isPrimary) {
+ * console.log('I am primary');
+ * cluster.fork();
+ * cluster.fork();
+ * } else if (cluster.isWorker) {
+ * console.log(`I am worker #${cluster.worker.id}`);
+ * }
+ * ```
+ * @since v0.7.0
+ */
+ readonly worker?: Worker;
+ /**
+ * A hash that stores the active worker objects, keyed by `id` field. This makes it easy to loop through all the workers. It is only available in the primary process.
+ *
+ * A worker is removed from `cluster.workers` after the worker has disconnected _and_ exited. The order between these two events cannot be determined in advance. However, it
+ * is guaranteed that the removal from the `cluster.workers` list happens before the last `'disconnect'` or `'exit'` event is emitted.
+ *
+ * ```js
+ * import cluster from 'node:cluster';
+ *
+ * for (const worker of Object.values(cluster.workers)) {
+ * worker.send('big announcement to all workers');
+ * }
+ * ```
+ * @since v0.7.0
+ */
+ readonly workers?: NodeJS.Dict;
+ readonly SCHED_NONE: number;
+ readonly SCHED_RR: number;
+ }
+ }
+ var cluster: cluster.Cluster;
+ export = cluster;
+}
+declare module "cluster" {
+ import cluster = require("node:cluster");
+ export = cluster;
+}
diff --git a/.cursor/scripts/db-exec/node_modules/@types/node/compatibility/iterators.d.ts b/.cursor/scripts/db-exec/node_modules/@types/node/compatibility/iterators.d.ts
new file mode 100644
index 0000000..156e785
--- /dev/null
+++ b/.cursor/scripts/db-exec/node_modules/@types/node/compatibility/iterators.d.ts
@@ -0,0 +1,21 @@
+// Backwards-compatible iterator interfaces, augmented with iterator helper methods by lib.esnext.iterator in TypeScript 5.6.
+// The IterableIterator interface does not contain these methods, which creates assignability issues in places where IteratorObjects
+// are expected (eg. DOM-compatible APIs) if lib.esnext.iterator is loaded.
+// Also ensures that iterators returned by the Node API, which inherit from Iterator.prototype, correctly expose the iterator helper methods
+// if lib.esnext.iterator is loaded.
+// TODO: remove once this package no longer supports TS 5.5, and replace NodeJS.BuiltinIteratorReturn with BuiltinIteratorReturn.
+
+// Placeholders for TS <5.6
+interface IteratorObject {}
+interface AsyncIteratorObject {}
+
+declare namespace NodeJS {
+ // Populate iterator methods for TS <5.6
+ interface Iterator extends globalThis.Iterator {}
+ interface AsyncIterator extends globalThis.AsyncIterator {}
+
+ // Polyfill for TS 5.6's instrinsic BuiltinIteratorReturn type, required for DOM-compatible iterators
+ type BuiltinIteratorReturn = ReturnType extends
+ globalThis.Iterator ? TReturn
+ : any;
+}
diff --git a/.cursor/scripts/db-exec/node_modules/@types/node/console.d.ts b/.cursor/scripts/db-exec/node_modules/@types/node/console.d.ts
new file mode 100644
index 0000000..3943442
--- /dev/null
+++ b/.cursor/scripts/db-exec/node_modules/@types/node/console.d.ts
@@ -0,0 +1,151 @@
+/**
+ * The `node:console` module provides a simple debugging console that is similar to
+ * the JavaScript console mechanism provided by web browsers.
+ *
+ * The module exports two specific components:
+ *
+ * * A `Console` class with methods such as `console.log()`, `console.error()`, and `console.warn()` that can be used to write to any Node.js stream.
+ * * A global `console` instance configured to write to [`process.stdout`](https://nodejs.org/docs/latest-v25.x/api/process.html#processstdout) and
+ * [`process.stderr`](https://nodejs.org/docs/latest-v25.x/api/process.html#processstderr). The global `console` can be used without importing the `node:console` module.
+ *
+ * _**Warning**_: The global console object's methods are neither consistently
+ * synchronous like the browser APIs they resemble, nor are they consistently
+ * asynchronous like all other Node.js streams. See the [`note on process I/O`](https://nodejs.org/docs/latest-v25.x/api/process.html#a-note-on-process-io) for
+ * more information.
+ *
+ * Example using the global `console`:
+ *
+ * ```js
+ * console.log('hello world');
+ * // Prints: hello world, to stdout
+ * console.log('hello %s', 'world');
+ * // Prints: hello world, to stdout
+ * console.error(new Error('Whoops, something bad happened'));
+ * // Prints error message and stack trace to stderr:
+ * // Error: Whoops, something bad happened
+ * // at [eval]:5:15
+ * // at Script.runInThisContext (node:vm:132:18)
+ * // at Object.runInThisContext (node:vm:309:38)
+ * // at node:internal/process/execution:77:19
+ * // at [eval]-wrapper:6:22
+ * // at evalScript (node:internal/process/execution:76:60)
+ * // at node:internal/main/eval_string:23:3
+ *
+ * const name = 'Will Robinson';
+ * console.warn(`Danger ${name}! Danger!`);
+ * // Prints: Danger Will Robinson! Danger!, to stderr
+ * ```
+ *
+ * Example using the `Console` class:
+ *
+ * ```js
+ * const out = getStreamSomehow();
+ * const err = getStreamSomehow();
+ * const myConsole = new console.Console(out, err);
+ *
+ * myConsole.log('hello world');
+ * // Prints: hello world, to out
+ * myConsole.log('hello %s', 'world');
+ * // Prints: hello world, to out
+ * myConsole.error(new Error('Whoops, something bad happened'));
+ * // Prints: [Error: Whoops, something bad happened], to err
+ *
+ * const name = 'Will Robinson';
+ * myConsole.warn(`Danger ${name}! Danger!`);
+ * // Prints: Danger Will Robinson! Danger!, to err
+ * ```
+ * @see [source](https://github.com/nodejs/node/blob/v25.x/lib/console.js)
+ */
+declare module "node:console" {
+ import { InspectOptions } from "node:util";
+ namespace console {
+ interface ConsoleOptions {
+ stdout: NodeJS.WritableStream;
+ stderr?: NodeJS.WritableStream | undefined;
+ /**
+ * Ignore errors when writing to the underlying streams.
+ * @default true
+ */
+ ignoreErrors?: boolean | undefined;
+ /**
+ * Set color support for this `Console` instance. Setting to true enables coloring while inspecting
+ * values. Setting to `false` disables coloring while inspecting values. Setting to `'auto'` makes color
+ * support depend on the value of the `isTTY` property and the value returned by `getColorDepth()` on the
+ * respective stream. This option can not be used, if `inspectOptions.colors` is set as well.
+ * @default 'auto'
+ */
+ colorMode?: boolean | "auto" | undefined;
+ /**
+ * Specifies options that are passed along to
+ * [`util.inspect()`](https://nodejs.org/docs/latest-v25.x/api/util.html#utilinspectobject-options).
+ */
+ inspectOptions?: InspectOptions | ReadonlyMap | undefined;
+ /**
+ * Set group indentation.
+ * @default 2
+ */
+ groupIndentation?: number | undefined;
+ }
+ interface Console {
+ readonly Console: {
+ prototype: Console;
+ new(stdout: NodeJS.WritableStream, stderr?: NodeJS.WritableStream, ignoreErrors?: boolean): Console;
+ new(options: ConsoleOptions): Console;
+ };
+ assert(condition?: unknown, ...data: any[]): void;
+ clear(): void;
+ count(label?: string): void;
+ countReset(label?: string): void;
+ debug(...data: any[]): void;
+ dir(item?: any, options?: InspectOptions): void;
+ dirxml(...data: any[]): void;
+ error(...data: any[]): void;
+ group(...data: any[]): void;
+ groupCollapsed(...data: any[]): void;
+ groupEnd(): void;
+ info(...data: any[]): void;
+ log(...data: any[]): void;
+ table(tabularData?: any, properties?: string[]): void;
+ time(label?: string): void;
+ timeEnd(label?: string): void;
+ timeLog(label?: string, ...data: any[]): void;
+ trace(...data: any[]): void;
+ warn(...data: any[]): void;
+ /**
+ * This method does not display anything unless used in the inspector. The `console.profile()`
+ * method starts a JavaScript CPU profile with an optional label until {@link profileEnd}
+ * is called. The profile is then added to the Profile panel of the inspector.
+ *
+ * ```js
+ * console.profile('MyLabel');
+ * // Some code
+ * console.profileEnd('MyLabel');
+ * // Adds the profile 'MyLabel' to the Profiles panel of the inspector.
+ * ```
+ * @since v8.0.0
+ */
+ profile(label?: string): void;
+ /**
+ * This method does not display anything unless used in the inspector. Stops the current
+ * JavaScript CPU profiling session if one has been started and prints the report to the
+ * Profiles panel of the inspector. See {@link profile} for an example.
+ *
+ * If this method is called without a label, the most recently started profile is stopped.
+ * @since v8.0.0
+ */
+ profileEnd(label?: string): void;
+ /**
+ * This method does not display anything unless used in the inspector. The `console.timeStamp()`
+ * method adds an event with the label `'label'` to the Timeline panel of the inspector.
+ * @since v8.0.0
+ */
+ timeStamp(label?: string): void;
+ }
+ }
+ var console: console.Console;
+ export = console;
+}
+declare module "console" {
+ import console = require("node:console");
+ export = console;
+}
diff --git a/.cursor/scripts/db-exec/node_modules/@types/node/constants.d.ts b/.cursor/scripts/db-exec/node_modules/@types/node/constants.d.ts
new file mode 100644
index 0000000..c24ad98
--- /dev/null
+++ b/.cursor/scripts/db-exec/node_modules/@types/node/constants.d.ts
@@ -0,0 +1,20 @@
+/**
+ * @deprecated The `node:constants` module is deprecated. When requiring access to constants
+ * relevant to specific Node.js builtin modules, developers should instead refer
+ * to the `constants` property exposed by the relevant module. For instance,
+ * `require('node:fs').constants` and `require('node:os').constants`.
+ */
+declare module "node:constants" {
+ const constants:
+ & typeof import("node:os").constants.dlopen
+ & typeof import("node:os").constants.errno
+ & typeof import("node:os").constants.priority
+ & typeof import("node:os").constants.signals
+ & typeof import("node:fs").constants
+ & typeof import("node:crypto").constants;
+ export = constants;
+}
+declare module "constants" {
+ import constants = require("node:constants");
+ export = constants;
+}
diff --git a/.cursor/scripts/db-exec/node_modules/@types/node/crypto.d.ts b/.cursor/scripts/db-exec/node_modules/@types/node/crypto.d.ts
new file mode 100644
index 0000000..15b46ce
--- /dev/null
+++ b/.cursor/scripts/db-exec/node_modules/@types/node/crypto.d.ts
@@ -0,0 +1,4065 @@
+/**
+ * The `node:crypto` module provides cryptographic functionality that includes a
+ * set of wrappers for OpenSSL's hash, HMAC, cipher, decipher, sign, and verify
+ * functions.
+ *
+ * ```js
+ * const { createHmac } = await import('node:crypto');
+ *
+ * const secret = 'abcdefg';
+ * const hash = createHmac('sha256', secret)
+ * .update('I love cupcakes')
+ * .digest('hex');
+ * console.log(hash);
+ * // Prints:
+ * // c0fa1bc00531bd78ef38c628449c5102aeabd49b5dc3a2a516ea6ea959d6658e
+ * ```
+ * @see [source](https://github.com/nodejs/node/blob/v25.x/lib/crypto.js)
+ */
+declare module "node:crypto" {
+ import { NonSharedBuffer } from "node:buffer";
+ import * as stream from "node:stream";
+ import { PeerCertificate } from "node:tls";
+ /**
+ * SPKAC is a Certificate Signing Request mechanism originally implemented by
+ * Netscape and was specified formally as part of HTML5's `keygen` element.
+ *
+ * `` is deprecated since [HTML 5.2](https://www.w3.org/TR/html52/changes.html#features-removed) and new projects
+ * should not use this element anymore.
+ *
+ * The `node:crypto` module provides the `Certificate` class for working with SPKAC
+ * data. The most common usage is handling output generated by the HTML5 `` element. Node.js uses [OpenSSL's SPKAC
+ * implementation](https://www.openssl.org/docs/man3.0/man1/openssl-spkac.html) internally.
+ * @since v0.11.8
+ */
+ class Certificate {
+ /**
+ * ```js
+ * const { Certificate } = await import('node:crypto');
+ * const spkac = getSpkacSomehow();
+ * const challenge = Certificate.exportChallenge(spkac);
+ * console.log(challenge.toString('utf8'));
+ * // Prints: the challenge as a UTF8 string
+ * ```
+ * @since v9.0.0
+ * @param encoding The `encoding` of the `spkac` string.
+ * @return The challenge component of the `spkac` data structure, which includes a public key and a challenge.
+ */
+ static exportChallenge(spkac: BinaryLike): NonSharedBuffer;
+ /**
+ * ```js
+ * const { Certificate } = await import('node:crypto');
+ * const spkac = getSpkacSomehow();
+ * const publicKey = Certificate.exportPublicKey(spkac);
+ * console.log(publicKey);
+ * // Prints: the public key as
+ * ```
+ * @since v9.0.0
+ * @param encoding The `encoding` of the `spkac` string.
+ * @return The public key component of the `spkac` data structure, which includes a public key and a challenge.
+ */
+ static exportPublicKey(spkac: BinaryLike, encoding?: string): NonSharedBuffer;
+ /**
+ * ```js
+ * import { Buffer } from 'node:buffer';
+ * const { Certificate } = await import('node:crypto');
+ *
+ * const spkac = getSpkacSomehow();
+ * console.log(Certificate.verifySpkac(Buffer.from(spkac)));
+ * // Prints: true or false
+ * ```
+ * @since v9.0.0
+ * @param encoding The `encoding` of the `spkac` string.
+ * @return `true` if the given `spkac` data structure is valid, `false` otherwise.
+ */
+ static verifySpkac(spkac: NodeJS.ArrayBufferView): boolean;
+ /**
+ * @deprecated
+ * @param spkac
+ * @returns The challenge component of the `spkac` data structure,
+ * which includes a public key and a challenge.
+ */
+ exportChallenge(spkac: BinaryLike): NonSharedBuffer;
+ /**
+ * @deprecated
+ * @param spkac
+ * @param encoding The encoding of the spkac string.
+ * @returns The public key component of the `spkac` data structure,
+ * which includes a public key and a challenge.
+ */
+ exportPublicKey(spkac: BinaryLike, encoding?: string): NonSharedBuffer;
+ /**
+ * @deprecated
+ * @param spkac
+ * @returns `true` if the given `spkac` data structure is valid,
+ * `false` otherwise.
+ */
+ verifySpkac(spkac: NodeJS.ArrayBufferView): boolean;
+ }
+ namespace constants {
+ // https://nodejs.org/dist/latest-v25.x/docs/api/crypto.html#crypto-constants
+ const OPENSSL_VERSION_NUMBER: number;
+ /** Applies multiple bug workarounds within OpenSSL. See https://www.openssl.org/docs/man1.0.2/ssl/SSL_CTX_set_options.html for detail. */
+ const SSL_OP_ALL: number;
+ /** Instructs OpenSSL to allow a non-[EC]DHE-based key exchange mode for TLS v1.3 */
+ const SSL_OP_ALLOW_NO_DHE_KEX: number;
+ /** Allows legacy insecure renegotiation between OpenSSL and unpatched clients or servers. See https://www.openssl.org/docs/man1.0.2/ssl/SSL_CTX_set_options.html. */
+ const SSL_OP_ALLOW_UNSAFE_LEGACY_RENEGOTIATION: number;
+ /** Attempts to use the server's preferences instead of the client's when selecting a cipher. See https://www.openssl.org/docs/man1.0.2/ssl/SSL_CTX_set_options.html. */
+ const SSL_OP_CIPHER_SERVER_PREFERENCE: number;
+ /** Instructs OpenSSL to use Cisco's version identifier of DTLS_BAD_VER. */
+ const SSL_OP_CISCO_ANYCONNECT: number;
+ /** Instructs OpenSSL to turn on cookie exchange. */
+ const SSL_OP_COOKIE_EXCHANGE: number;
+ /** Instructs OpenSSL to add server-hello extension from an early version of the cryptopro draft. */
+ const SSL_OP_CRYPTOPRO_TLSEXT_BUG: number;
+ /** Instructs OpenSSL to disable a SSL 3.0/TLS 1.0 vulnerability workaround added in OpenSSL 0.9.6d. */
+ const SSL_OP_DONT_INSERT_EMPTY_FRAGMENTS: number;
+ /** Allows initial connection to servers that do not support RI. */
+ const SSL_OP_LEGACY_SERVER_CONNECT: number;
+ /** Instructs OpenSSL to disable support for SSL/TLS compression. */
+ const SSL_OP_NO_COMPRESSION: number;
+ /** Instructs OpenSSL to disable encrypt-then-MAC. */
+ const SSL_OP_NO_ENCRYPT_THEN_MAC: number;
+ const SSL_OP_NO_QUERY_MTU: number;
+ /** Instructs OpenSSL to disable renegotiation. */
+ const SSL_OP_NO_RENEGOTIATION: number;
+ /** Instructs OpenSSL to always start a new session when performing renegotiation. */
+ const SSL_OP_NO_SESSION_RESUMPTION_ON_RENEGOTIATION: number;
+ /** Instructs OpenSSL to turn off SSL v2 */
+ const SSL_OP_NO_SSLv2: number;
+ /** Instructs OpenSSL to turn off SSL v3 */
+ const SSL_OP_NO_SSLv3: number;
+ /** Instructs OpenSSL to disable use of RFC4507bis tickets. */
+ const SSL_OP_NO_TICKET: number;
+ /** Instructs OpenSSL to turn off TLS v1 */
+ const SSL_OP_NO_TLSv1: number;
+ /** Instructs OpenSSL to turn off TLS v1.1 */
+ const SSL_OP_NO_TLSv1_1: number;
+ /** Instructs OpenSSL to turn off TLS v1.2 */
+ const SSL_OP_NO_TLSv1_2: number;
+ /** Instructs OpenSSL to turn off TLS v1.3 */
+ const SSL_OP_NO_TLSv1_3: number;
+ /** Instructs OpenSSL server to prioritize ChaCha20-Poly1305 when the client does. This option has no effect if `SSL_OP_CIPHER_SERVER_PREFERENCE` is not enabled. */
+ const SSL_OP_PRIORITIZE_CHACHA: number;
+ /** Instructs OpenSSL to disable version rollback attack detection. */
+ const SSL_OP_TLS_ROLLBACK_BUG: number;
+ const ENGINE_METHOD_RSA: number;
+ const ENGINE_METHOD_DSA: number;
+ const ENGINE_METHOD_DH: number;
+ const ENGINE_METHOD_RAND: number;
+ const ENGINE_METHOD_EC: number;
+ const ENGINE_METHOD_CIPHERS: number;
+ const ENGINE_METHOD_DIGESTS: number;
+ const ENGINE_METHOD_PKEY_METHS: number;
+ const ENGINE_METHOD_PKEY_ASN1_METHS: number;
+ const ENGINE_METHOD_ALL: number;
+ const ENGINE_METHOD_NONE: number;
+ const DH_CHECK_P_NOT_SAFE_PRIME: number;
+ const DH_CHECK_P_NOT_PRIME: number;
+ const DH_UNABLE_TO_CHECK_GENERATOR: number;
+ const DH_NOT_SUITABLE_GENERATOR: number;
+ const RSA_PKCS1_PADDING: number;
+ const RSA_SSLV23_PADDING: number;
+ const RSA_NO_PADDING: number;
+ const RSA_PKCS1_OAEP_PADDING: number;
+ const RSA_X931_PADDING: number;
+ const RSA_PKCS1_PSS_PADDING: number;
+ /** Sets the salt length for RSA_PKCS1_PSS_PADDING to the digest size when signing or verifying. */
+ const RSA_PSS_SALTLEN_DIGEST: number;
+ /** Sets the salt length for RSA_PKCS1_PSS_PADDING to the maximum permissible value when signing data. */
+ const RSA_PSS_SALTLEN_MAX_SIGN: number;
+ /** Causes the salt length for RSA_PKCS1_PSS_PADDING to be determined automatically when verifying a signature. */
+ const RSA_PSS_SALTLEN_AUTO: number;
+ const POINT_CONVERSION_COMPRESSED: number;
+ const POINT_CONVERSION_UNCOMPRESSED: number;
+ const POINT_CONVERSION_HYBRID: number;
+ /** Specifies the built-in default cipher list used by Node.js (colon-separated values). */
+ const defaultCoreCipherList: string;
+ /** Specifies the active default cipher list used by the current Node.js process (colon-separated values). */
+ const defaultCipherList: string;
+ }
+ interface HashOptions extends stream.TransformOptions {
+ /**
+ * For XOF hash functions such as `shake256`, the
+ * outputLength option can be used to specify the desired output length in bytes.
+ */
+ outputLength?: number | undefined;
+ }
+ /** @deprecated since v10.0.0 */
+ const fips: boolean;
+ /**
+ * Creates and returns a `Hash` object that can be used to generate hash digests
+ * using the given `algorithm`. Optional `options` argument controls stream
+ * behavior. For XOF hash functions such as `'shake256'`, the `outputLength` option
+ * can be used to specify the desired output length in bytes.
+ *
+ * The `algorithm` is dependent on the available algorithms supported by the
+ * version of OpenSSL on the platform. Examples are `'sha256'`, `'sha512'`, etc.
+ * On recent releases of OpenSSL, `openssl list -digest-algorithms` will
+ * display the available digest algorithms.
+ *
+ * Example: generating the sha256 sum of a file
+ *
+ * ```js
+ * import {
+ * createReadStream,
+ * } from 'node:fs';
+ * import { argv } from 'node:process';
+ * const {
+ * createHash,
+ * } = await import('node:crypto');
+ *
+ * const filename = argv[2];
+ *
+ * const hash = createHash('sha256');
+ *
+ * const input = createReadStream(filename);
+ * input.on('readable', () => {
+ * // Only one element is going to be produced by the
+ * // hash stream.
+ * const data = input.read();
+ * if (data)
+ * hash.update(data);
+ * else {
+ * console.log(`${hash.digest('hex')} ${filename}`);
+ * }
+ * });
+ * ```
+ * @since v0.1.92
+ * @param options `stream.transform` options
+ */
+ function createHash(algorithm: string, options?: HashOptions): Hash;
+ /**
+ * Creates and returns an `Hmac` object that uses the given `algorithm` and `key`.
+ * Optional `options` argument controls stream behavior.
+ *
+ * The `algorithm` is dependent on the available algorithms supported by the
+ * version of OpenSSL on the platform. Examples are `'sha256'`, `'sha512'`, etc.
+ * On recent releases of OpenSSL, `openssl list -digest-algorithms` will
+ * display the available digest algorithms.
+ *
+ * The `key` is the HMAC key used to generate the cryptographic HMAC hash. If it is
+ * a `KeyObject`, its type must be `secret`. If it is a string, please consider `caveats when using strings as inputs to cryptographic APIs`. If it was
+ * obtained from a cryptographically secure source of entropy, such as {@link randomBytes} or {@link generateKey}, its length should not
+ * exceed the block size of `algorithm` (e.g., 512 bits for SHA-256).
+ *
+ * Example: generating the sha256 HMAC of a file
+ *
+ * ```js
+ * import {
+ * createReadStream,
+ * } from 'node:fs';
+ * import { argv } from 'node:process';
+ * const {
+ * createHmac,
+ * } = await import('node:crypto');
+ *
+ * const filename = argv[2];
+ *
+ * const hmac = createHmac('sha256', 'a secret');
+ *
+ * const input = createReadStream(filename);
+ * input.on('readable', () => {
+ * // Only one element is going to be produced by the
+ * // hash stream.
+ * const data = input.read();
+ * if (data)
+ * hmac.update(data);
+ * else {
+ * console.log(`${hmac.digest('hex')} ${filename}`);
+ * }
+ * });
+ * ```
+ * @since v0.1.94
+ * @param options `stream.transform` options
+ */
+ function createHmac(algorithm: string, key: BinaryLike | KeyObject, options?: stream.TransformOptions): Hmac;
+ // https://nodejs.org/api/buffer.html#buffer_buffers_and_character_encodings
+ type BinaryToTextEncoding = "base64" | "base64url" | "hex" | "binary";
+ type CharacterEncoding = "utf8" | "utf-8" | "utf16le" | "utf-16le" | "latin1";
+ type LegacyCharacterEncoding = "ascii" | "binary" | "ucs2" | "ucs-2";
+ type Encoding = BinaryToTextEncoding | CharacterEncoding | LegacyCharacterEncoding;
+ type ECDHKeyFormat = "compressed" | "uncompressed" | "hybrid";
+ /**
+ * The `Hash` class is a utility for creating hash digests of data. It can be
+ * used in one of two ways:
+ *
+ * * As a `stream` that is both readable and writable, where data is written
+ * to produce a computed hash digest on the readable side, or
+ * * Using the `hash.update()` and `hash.digest()` methods to produce the
+ * computed hash.
+ *
+ * The {@link createHash} method is used to create `Hash` instances. `Hash`objects are not to be created directly using the `new` keyword.
+ *
+ * Example: Using `Hash` objects as streams:
+ *
+ * ```js
+ * const {
+ * createHash,
+ * } = await import('node:crypto');
+ *
+ * const hash = createHash('sha256');
+ *
+ * hash.on('readable', () => {
+ * // Only one element is going to be produced by the
+ * // hash stream.
+ * const data = hash.read();
+ * if (data) {
+ * console.log(data.toString('hex'));
+ * // Prints:
+ * // 6a2da20943931e9834fc12cfe5bb47bbd9ae43489a30726962b576f4e3993e50
+ * }
+ * });
+ *
+ * hash.write('some data to hash');
+ * hash.end();
+ * ```
+ *
+ * Example: Using `Hash` and piped streams:
+ *
+ * ```js
+ * import { createReadStream } from 'node:fs';
+ * import { stdout } from 'node:process';
+ * const { createHash } = await import('node:crypto');
+ *
+ * const hash = createHash('sha256');
+ *
+ * const input = createReadStream('test.js');
+ * input.pipe(hash).setEncoding('hex').pipe(stdout);
+ * ```
+ *
+ * Example: Using the `hash.update()` and `hash.digest()` methods:
+ *
+ * ```js
+ * const {
+ * createHash,
+ * } = await import('node:crypto');
+ *
+ * const hash = createHash('sha256');
+ *
+ * hash.update('some data to hash');
+ * console.log(hash.digest('hex'));
+ * // Prints:
+ * // 6a2da20943931e9834fc12cfe5bb47bbd9ae43489a30726962b576f4e3993e50
+ * ```
+ * @since v0.1.92
+ */
+ class Hash extends stream.Transform {
+ private constructor();
+ /**
+ * Creates a new `Hash` object that contains a deep copy of the internal state
+ * of the current `Hash` object.
+ *
+ * The optional `options` argument controls stream behavior. For XOF hash
+ * functions such as `'shake256'`, the `outputLength` option can be used to
+ * specify the desired output length in bytes.
+ *
+ * An error is thrown when an attempt is made to copy the `Hash` object after
+ * its `hash.digest()` method has been called.
+ *
+ * ```js
+ * // Calculate a rolling hash.
+ * const {
+ * createHash,
+ * } = await import('node:crypto');
+ *
+ * const hash = createHash('sha256');
+ *
+ * hash.update('one');
+ * console.log(hash.copy().digest('hex'));
+ *
+ * hash.update('two');
+ * console.log(hash.copy().digest('hex'));
+ *
+ * hash.update('three');
+ * console.log(hash.copy().digest('hex'));
+ *
+ * // Etc.
+ * ```
+ * @since v13.1.0
+ * @param options `stream.transform` options
+ */
+ copy(options?: HashOptions): Hash;
+ /**
+ * Updates the hash content with the given `data`, the encoding of which
+ * is given in `inputEncoding`.
+ * If `encoding` is not provided, and the `data` is a string, an
+ * encoding of `'utf8'` is enforced. If `data` is a `Buffer`, `TypedArray`, or`DataView`, then `inputEncoding` is ignored.
+ *
+ * This can be called many times with new data as it is streamed.
+ * @since v0.1.92
+ * @param inputEncoding The `encoding` of the `data` string.
+ */
+ update(data: BinaryLike): Hash;
+ update(data: string, inputEncoding: Encoding): Hash;
+ /**
+ * Calculates the digest of all of the data passed to be hashed (using the `hash.update()` method).
+ * If `encoding` is provided a string will be returned; otherwise
+ * a `Buffer` is returned.
+ *
+ * The `Hash` object can not be used again after `hash.digest()` method has been
+ * called. Multiple calls will cause an error to be thrown.
+ * @since v0.1.92
+ * @param encoding The `encoding` of the return value.
+ */
+ digest(): NonSharedBuffer;
+ digest(encoding: BinaryToTextEncoding): string;
+ }
+ /**
+ * The `Hmac` class is a utility for creating cryptographic HMAC digests. It can
+ * be used in one of two ways:
+ *
+ * * As a `stream` that is both readable and writable, where data is written
+ * to produce a computed HMAC digest on the readable side, or
+ * * Using the `hmac.update()` and `hmac.digest()` methods to produce the
+ * computed HMAC digest.
+ *
+ * The {@link createHmac} method is used to create `Hmac` instances. `Hmac`objects are not to be created directly using the `new` keyword.
+ *
+ * Example: Using `Hmac` objects as streams:
+ *
+ * ```js
+ * const {
+ * createHmac,
+ * } = await import('node:crypto');
+ *
+ * const hmac = createHmac('sha256', 'a secret');
+ *
+ * hmac.on('readable', () => {
+ * // Only one element is going to be produced by the
+ * // hash stream.
+ * const data = hmac.read();
+ * if (data) {
+ * console.log(data.toString('hex'));
+ * // Prints:
+ * // 7fd04df92f636fd450bc841c9418e5825c17f33ad9c87c518115a45971f7f77e
+ * }
+ * });
+ *
+ * hmac.write('some data to hash');
+ * hmac.end();
+ * ```
+ *
+ * Example: Using `Hmac` and piped streams:
+ *
+ * ```js
+ * import { createReadStream } from 'node:fs';
+ * import { stdout } from 'node:process';
+ * const {
+ * createHmac,
+ * } = await import('node:crypto');
+ *
+ * const hmac = createHmac('sha256', 'a secret');
+ *
+ * const input = createReadStream('test.js');
+ * input.pipe(hmac).pipe(stdout);
+ * ```
+ *
+ * Example: Using the `hmac.update()` and `hmac.digest()` methods:
+ *
+ * ```js
+ * const {
+ * createHmac,
+ * } = await import('node:crypto');
+ *
+ * const hmac = createHmac('sha256', 'a secret');
+ *
+ * hmac.update('some data to hash');
+ * console.log(hmac.digest('hex'));
+ * // Prints:
+ * // 7fd04df92f636fd450bc841c9418e5825c17f33ad9c87c518115a45971f7f77e
+ * ```
+ * @since v0.1.94
+ */
+ class Hmac extends stream.Transform {
+ private constructor();
+ /**
+ * Updates the `Hmac` content with the given `data`, the encoding of which
+ * is given in `inputEncoding`.
+ * If `encoding` is not provided, and the `data` is a string, an
+ * encoding of `'utf8'` is enforced. If `data` is a `Buffer`, `TypedArray`, or`DataView`, then `inputEncoding` is ignored.
+ *
+ * This can be called many times with new data as it is streamed.
+ * @since v0.1.94
+ * @param inputEncoding The `encoding` of the `data` string.
+ */
+ update(data: BinaryLike): Hmac;
+ update(data: string, inputEncoding: Encoding): Hmac;
+ /**
+ * Calculates the HMAC digest of all of the data passed using `hmac.update()`.
+ * If `encoding` is
+ * provided a string is returned; otherwise a `Buffer` is returned;
+ *
+ * The `Hmac` object can not be used again after `hmac.digest()` has been
+ * called. Multiple calls to `hmac.digest()` will result in an error being thrown.
+ * @since v0.1.94
+ * @param encoding The `encoding` of the return value.
+ */
+ digest(): NonSharedBuffer;
+ digest(encoding: BinaryToTextEncoding): string;
+ }
+ type KeyFormat = "pem" | "der" | "jwk";
+ type KeyObjectType = "secret" | "public" | "private";
+ type PublicKeyExportType = "pkcs1" | "spki";
+ type PrivateKeyExportType = "pkcs1" | "pkcs8" | "sec1";
+ type KeyExportOptions =
+ | SymmetricKeyExportOptions
+ | PublicKeyExportOptions
+ | PrivateKeyExportOptions
+ | JwkKeyExportOptions;
+ interface SymmetricKeyExportOptions {
+ format?: "buffer" | undefined;
+ }
+ interface PublicKeyExportOptions {
+ type: T;
+ format: Exclude;
+ }
+ interface PrivateKeyExportOptions {
+ type: T;
+ format: Exclude;
+ cipher?: string | undefined;
+ passphrase?: string | Buffer | undefined;
+ }
+ interface JwkKeyExportOptions {
+ format: "jwk";
+ }
+ interface KeyPairExportOptions<
+ TPublic extends PublicKeyExportType = PublicKeyExportType,
+ TPrivate extends PrivateKeyExportType = PrivateKeyExportType,
+ > {
+ publicKeyEncoding?: PublicKeyExportOptions | JwkKeyExportOptions | undefined;
+ privateKeyEncoding?: PrivateKeyExportOptions | JwkKeyExportOptions | undefined;
+ }
+ type KeyExportResult = T extends { format: infer F extends KeyFormat }
+ ? { der: NonSharedBuffer; jwk: webcrypto.JsonWebKey; pem: string }[F]
+ : Default;
+ interface KeyPairExportResult {
+ publicKey: KeyExportResult;
+ privateKey: KeyExportResult;
+ }
+ type KeyPairExportCallback = (
+ err: Error | null,
+ publicKey: KeyExportResult,
+ privateKey: KeyExportResult,
+ ) => void;
+ type MLDSAKeyType = `ml-dsa-${44 | 65 | 87}`;
+ type MLKEMKeyType = `ml-kem-${1024 | 512 | 768}`;
+ type SLHDSAKeyType = `slh-dsa-${"sha2" | "shake"}-${128 | 192 | 256}${"f" | "s"}`;
+ type AsymmetricKeyType =
+ | "dh"
+ | "dsa"
+ | "ec"
+ | "ed25519"
+ | "ed448"
+ | MLDSAKeyType
+ | MLKEMKeyType
+ | "rsa-pss"
+ | "rsa"
+ | SLHDSAKeyType
+ | "x25519"
+ | "x448";
+ interface AsymmetricKeyDetails {
+ /**
+ * Key size in bits (RSA, DSA).
+ */
+ modulusLength?: number;
+ /**
+ * Public exponent (RSA).
+ */
+ publicExponent?: bigint;
+ /**
+ * Name of the message digest (RSA-PSS).
+ */
+ hashAlgorithm?: string;
+ /**
+ * Name of the message digest used by MGF1 (RSA-PSS).
+ */
+ mgf1HashAlgorithm?: string;
+ /**
+ * Minimal salt length in bytes (RSA-PSS).
+ */
+ saltLength?: number;
+ /**
+ * Size of q in bits (DSA).
+ */
+ divisorLength?: number;
+ /**
+ * Name of the curve (EC).
+ */
+ namedCurve?: string;
+ }
+ /**
+ * Node.js uses a `KeyObject` class to represent a symmetric or asymmetric key,
+ * and each kind of key exposes different functions. The {@link createSecretKey}, {@link createPublicKey} and {@link createPrivateKey} methods are used to create `KeyObject`instances. `KeyObject`
+ * objects are not to be created directly using the `new`keyword.
+ *
+ * Most applications should consider using the new `KeyObject` API instead of
+ * passing keys as strings or `Buffer`s due to improved security features.
+ *
+ * `KeyObject` instances can be passed to other threads via `postMessage()`.
+ * The receiver obtains a cloned `KeyObject`, and the `KeyObject` does not need to
+ * be listed in the `transferList` argument.
+ * @since v11.6.0
+ */
+ class KeyObject {
+ private constructor();
+ /**
+ * Example: Converting a `CryptoKey` instance to a `KeyObject`:
+ *
+ * ```js
+ * const { KeyObject } = await import('node:crypto');
+ * const { subtle } = globalThis.crypto;
+ *
+ * const key = await subtle.generateKey({
+ * name: 'HMAC',
+ * hash: 'SHA-256',
+ * length: 256,
+ * }, true, ['sign', 'verify']);
+ *
+ * const keyObject = KeyObject.from(key);
+ * console.log(keyObject.symmetricKeySize);
+ * // Prints: 32 (symmetric key size in bytes)
+ * ```
+ * @since v15.0.0
+ */
+ static from(key: webcrypto.CryptoKey): KeyObject;
+ /**
+ * For asymmetric keys, this property represents the type of the key. See the
+ * supported [asymmetric key types](https://nodejs.org/docs/latest-v25.x/api/crypto.html#asymmetric-key-types).
+ *
+ * This property is `undefined` for unrecognized `KeyObject` types and symmetric
+ * keys.
+ * @since v11.6.0
+ */
+ asymmetricKeyType?: AsymmetricKeyType;
+ /**
+ * This property exists only on asymmetric keys. Depending on the type of the key,
+ * this object contains information about the key. None of the information obtained
+ * through this property can be used to uniquely identify a key or to compromise
+ * the security of the key.
+ *
+ * For RSA-PSS keys, if the key material contains a `RSASSA-PSS-params` sequence,
+ * the `hashAlgorithm`, `mgf1HashAlgorithm`, and `saltLength` properties will be
+ * set.
+ *
+ * Other key details might be exposed via this API using additional attributes.
+ * @since v15.7.0
+ */
+ asymmetricKeyDetails?: AsymmetricKeyDetails;
+ /**
+ * For symmetric keys, the following encoding options can be used:
+ *
+ * For public keys, the following encoding options can be used:
+ *
+ * For private keys, the following encoding options can be used:
+ *
+ * The result type depends on the selected encoding format, when PEM the
+ * result is a string, when DER it will be a buffer containing the data
+ * encoded as DER, when [JWK](https://tools.ietf.org/html/rfc7517) it will be an object.
+ *
+ * When [JWK](https://tools.ietf.org/html/rfc7517) encoding format was selected, all other encoding options are
+ * ignored.
+ *
+ * PKCS#1, SEC1, and PKCS#8 type keys can be encrypted by using a combination of
+ * the `cipher` and `format` options. The PKCS#8 `type` can be used with any`format` to encrypt any key algorithm (RSA, EC, or DH) by specifying a`cipher`. PKCS#1 and SEC1 can only be
+ * encrypted by specifying a `cipher`when the PEM `format` is used. For maximum compatibility, use PKCS#8 for
+ * encrypted private keys. Since PKCS#8 defines its own
+ * encryption mechanism, PEM-level encryption is not supported when encrypting
+ * a PKCS#8 key. See [RFC 5208](https://www.rfc-editor.org/rfc/rfc5208.txt) for PKCS#8 encryption and [RFC 1421](https://www.rfc-editor.org/rfc/rfc1421.txt) for
+ * PKCS#1 and SEC1 encryption.
+ * @since v11.6.0
+ */
+ export(options?: T): KeyExportResult;
+ /**
+ * Returns `true` or `false` depending on whether the keys have exactly the same
+ * type, value, and parameters. This method is not [constant time](https://en.wikipedia.org/wiki/Timing_attack).
+ * @since v17.7.0, v16.15.0
+ * @param otherKeyObject A `KeyObject` with which to compare `keyObject`.
+ */
+ equals(otherKeyObject: KeyObject): boolean;
+ /**
+ * For secret keys, this property represents the size of the key in bytes. This
+ * property is `undefined` for asymmetric keys.
+ * @since v11.6.0
+ */
+ symmetricKeySize?: number;
+ /**
+ * Converts a `KeyObject` instance to a `CryptoKey`.
+ * @since 22.10.0
+ */
+ toCryptoKey(
+ algorithm:
+ | webcrypto.AlgorithmIdentifier
+ | webcrypto.RsaHashedImportParams
+ | webcrypto.EcKeyImportParams
+ | webcrypto.HmacImportParams,
+ extractable: boolean,
+ keyUsages: readonly webcrypto.KeyUsage[],
+ ): webcrypto.CryptoKey;
+ /**
+ * Depending on the type of this `KeyObject`, this property is either`'secret'` for secret (symmetric) keys, `'public'` for public (asymmetric) keys
+ * or `'private'` for private (asymmetric) keys.
+ * @since v11.6.0
+ */
+ type: KeyObjectType;
+ }
+ type CipherCCMTypes = "aes-128-ccm" | "aes-192-ccm" | "aes-256-ccm";
+ type CipherGCMTypes = "aes-128-gcm" | "aes-192-gcm" | "aes-256-gcm";
+ type CipherOCBTypes = "aes-128-ocb" | "aes-192-ocb" | "aes-256-ocb";
+ type CipherChaCha20Poly1305Types = "chacha20-poly1305";
+ type BinaryLike = string | NodeJS.ArrayBufferView;
+ type CipherKey = BinaryLike | KeyObject;
+ interface CipherCCMOptions extends stream.TransformOptions {
+ authTagLength: number;
+ }
+ interface CipherGCMOptions extends stream.TransformOptions {
+ authTagLength?: number | undefined;
+ }
+ interface CipherOCBOptions extends stream.TransformOptions {
+ authTagLength: number;
+ }
+ interface CipherChaCha20Poly1305Options extends stream.TransformOptions {
+ /** @default 16 */
+ authTagLength?: number | undefined;
+ }
+ /**
+ * Creates and returns a `Cipher` object, with the given `algorithm`, `key` and
+ * initialization vector (`iv`).
+ *
+ * The `options` argument controls stream behavior and is optional except when a
+ * cipher in CCM or OCB mode (e.g. `'aes-128-ccm'`) is used. In that case, the`authTagLength` option is required and specifies the length of the
+ * authentication tag in bytes, see `CCM mode`. In GCM mode, the `authTagLength`option is not required but can be used to set the length of the authentication
+ * tag that will be returned by `getAuthTag()` and defaults to 16 bytes.
+ * For `chacha20-poly1305`, the `authTagLength` option defaults to 16 bytes.
+ *
+ * The `algorithm` is dependent on OpenSSL, examples are `'aes192'`, etc. On
+ * recent OpenSSL releases, `openssl list -cipher-algorithms` will
+ * display the available cipher algorithms.
+ *
+ * The `key` is the raw key used by the `algorithm` and `iv` is an [initialization vector](https://en.wikipedia.org/wiki/Initialization_vector). Both arguments must be `'utf8'` encoded
+ * strings,`Buffers`, `TypedArray`, or `DataView`s. The `key` may optionally be
+ * a `KeyObject` of type `secret`. If the cipher does not need
+ * an initialization vector, `iv` may be `null`.
+ *
+ * When passing strings for `key` or `iv`, please consider `caveats when using strings as inputs to cryptographic APIs`.
+ *
+ * Initialization vectors should be unpredictable and unique; ideally, they will be
+ * cryptographically random. They do not have to be secret: IVs are typically just
+ * added to ciphertext messages unencrypted. It may sound contradictory that
+ * something has to be unpredictable and unique, but does not have to be secret;
+ * remember that an attacker must not be able to predict ahead of time what a
+ * given IV will be.
+ * @since v0.1.94
+ * @param options `stream.transform` options
+ */
+ function createCipheriv(
+ algorithm: CipherCCMTypes,
+ key: CipherKey,
+ iv: BinaryLike,
+ options: CipherCCMOptions,
+ ): CipherCCM;
+ function createCipheriv(
+ algorithm: CipherOCBTypes,
+ key: CipherKey,
+ iv: BinaryLike,
+ options: CipherOCBOptions,
+ ): CipherOCB;
+ function createCipheriv(
+ algorithm: CipherGCMTypes,
+ key: CipherKey,
+ iv: BinaryLike,
+ options?: CipherGCMOptions,
+ ): CipherGCM;
+ function createCipheriv(
+ algorithm: CipherChaCha20Poly1305Types,
+ key: CipherKey,
+ iv: BinaryLike,
+ options?: CipherChaCha20Poly1305Options,
+ ): CipherChaCha20Poly1305;
+ function createCipheriv(
+ algorithm: string,
+ key: CipherKey,
+ iv: BinaryLike | null,
+ options?: stream.TransformOptions,
+ ): Cipheriv;
+ /**
+ * Instances of the `Cipheriv` class are used to encrypt data. The class can be
+ * used in one of two ways:
+ *
+ * * As a `stream` that is both readable and writable, where plain unencrypted
+ * data is written to produce encrypted data on the readable side, or
+ * * Using the `cipher.update()` and `cipher.final()` methods to produce
+ * the encrypted data.
+ *
+ * The {@link createCipheriv} method is
+ * used to create `Cipheriv` instances. `Cipheriv` objects are not to be created
+ * directly using the `new` keyword.
+ *
+ * Example: Using `Cipheriv` objects as streams:
+ *
+ * ```js
+ * const {
+ * scrypt,
+ * randomFill,
+ * createCipheriv,
+ * } = await import('node:crypto');
+ *
+ * const algorithm = 'aes-192-cbc';
+ * const password = 'Password used to generate key';
+ *
+ * // First, we'll generate the key. The key length is dependent on the algorithm.
+ * // In this case for aes192, it is 24 bytes (192 bits).
+ * scrypt(password, 'salt', 24, (err, key) => {
+ * if (err) throw err;
+ * // Then, we'll generate a random initialization vector
+ * randomFill(new Uint8Array(16), (err, iv) => {
+ * if (err) throw err;
+ *
+ * // Once we have the key and iv, we can create and use the cipher...
+ * const cipher = createCipheriv(algorithm, key, iv);
+ *
+ * let encrypted = '';
+ * cipher.setEncoding('hex');
+ *
+ * cipher.on('data', (chunk) => encrypted += chunk);
+ * cipher.on('end', () => console.log(encrypted));
+ *
+ * cipher.write('some clear text data');
+ * cipher.end();
+ * });
+ * });
+ * ```
+ *
+ * Example: Using `Cipheriv` and piped streams:
+ *
+ * ```js
+ * import {
+ * createReadStream,
+ * createWriteStream,
+ * } from 'node:fs';
+ *
+ * import {
+ * pipeline,
+ * } from 'node:stream';
+ *
+ * const {
+ * scrypt,
+ * randomFill,
+ * createCipheriv,
+ * } = await import('node:crypto');
+ *
+ * const algorithm = 'aes-192-cbc';
+ * const password = 'Password used to generate key';
+ *
+ * // First, we'll generate the key. The key length is dependent on the algorithm.
+ * // In this case for aes192, it is 24 bytes (192 bits).
+ * scrypt(password, 'salt', 24, (err, key) => {
+ * if (err) throw err;
+ * // Then, we'll generate a random initialization vector
+ * randomFill(new Uint8Array(16), (err, iv) => {
+ * if (err) throw err;
+ *
+ * const cipher = createCipheriv(algorithm, key, iv);
+ *
+ * const input = createReadStream('test.js');
+ * const output = createWriteStream('test.enc');
+ *
+ * pipeline(input, cipher, output, (err) => {
+ * if (err) throw err;
+ * });
+ * });
+ * });
+ * ```
+ *
+ * Example: Using the `cipher.update()` and `cipher.final()` methods:
+ *
+ * ```js
+ * const {
+ * scrypt,
+ * randomFill,
+ * createCipheriv,
+ * } = await import('node:crypto');
+ *
+ * const algorithm = 'aes-192-cbc';
+ * const password = 'Password used to generate key';
+ *
+ * // First, we'll generate the key. The key length is dependent on the algorithm.
+ * // In this case for aes192, it is 24 bytes (192 bits).
+ * scrypt(password, 'salt', 24, (err, key) => {
+ * if (err) throw err;
+ * // Then, we'll generate a random initialization vector
+ * randomFill(new Uint8Array(16), (err, iv) => {
+ * if (err) throw err;
+ *
+ * const cipher = createCipheriv(algorithm, key, iv);
+ *
+ * let encrypted = cipher.update('some clear text data', 'utf8', 'hex');
+ * encrypted += cipher.final('hex');
+ * console.log(encrypted);
+ * });
+ * });
+ * ```
+ * @since v0.1.94
+ */
+ class Cipheriv extends stream.Transform {
+ private constructor();
+ /**
+ * Updates the cipher with `data`. If the `inputEncoding` argument is given,
+ * the `data`argument is a string using the specified encoding. If the `inputEncoding`argument is not given, `data` must be a `Buffer`, `TypedArray`, or `DataView`. If `data` is a `Buffer`,
+ * `TypedArray`, or `DataView`, then `inputEncoding` is ignored.
+ *
+ * The `outputEncoding` specifies the output format of the enciphered
+ * data. If the `outputEncoding`is specified, a string using the specified encoding is returned. If no`outputEncoding` is provided, a `Buffer` is returned.
+ *
+ * The `cipher.update()` method can be called multiple times with new data until `cipher.final()` is called. Calling `cipher.update()` after `cipher.final()` will result in an error being
+ * thrown.
+ * @since v0.1.94
+ * @param inputEncoding The `encoding` of the data.
+ * @param outputEncoding The `encoding` of the return value.
+ */
+ update(data: BinaryLike): NonSharedBuffer;
+ update(data: string, inputEncoding: Encoding): NonSharedBuffer;
+ update(data: NodeJS.ArrayBufferView, inputEncoding: undefined, outputEncoding: Encoding): string;
+ update(data: string, inputEncoding: Encoding | undefined, outputEncoding: Encoding): string;
+ /**
+ * Once the `cipher.final()` method has been called, the `Cipheriv` object can no
+ * longer be used to encrypt data. Attempts to call `cipher.final()` more than
+ * once will result in an error being thrown.
+ * @since v0.1.94
+ * @param outputEncoding The `encoding` of the return value.
+ * @return Any remaining enciphered contents. If `outputEncoding` is specified, a string is returned. If an `outputEncoding` is not provided, a {@link Buffer} is returned.
+ */
+ final(): NonSharedBuffer;
+ final(outputEncoding: BufferEncoding): string;
+ /**
+ * When using block encryption algorithms, the `Cipheriv` class will automatically
+ * add padding to the input data to the appropriate block size. To disable the
+ * default padding call `cipher.setAutoPadding(false)`.
+ *
+ * When `autoPadding` is `false`, the length of the entire input data must be a
+ * multiple of the cipher's block size or `cipher.final()` will throw an error.
+ * Disabling automatic padding is useful for non-standard padding, for instance
+ * using `0x0` instead of PKCS padding.
+ *
+ * The `cipher.setAutoPadding()` method must be called before `cipher.final()`.
+ * @since v0.7.1
+ * @param [autoPadding=true]
+ * @return for method chaining.
+ */
+ setAutoPadding(autoPadding?: boolean): this;
+ }
+ interface CipherCCM extends Cipheriv {
+ setAAD(
+ buffer: NodeJS.ArrayBufferView,
+ options: {
+ plaintextLength: number;
+ },
+ ): this;
+ getAuthTag(): NonSharedBuffer;
+ }
+ interface CipherGCM extends Cipheriv {
+ setAAD(
+ buffer: NodeJS.ArrayBufferView,
+ options?: {
+ plaintextLength: number;
+ },
+ ): this;
+ getAuthTag(): NonSharedBuffer;
+ }
+ interface CipherOCB extends Cipheriv {
+ setAAD(
+ buffer: NodeJS.ArrayBufferView,
+ options?: {
+ plaintextLength: number;
+ },
+ ): this;
+ getAuthTag(): NonSharedBuffer;
+ }
+ interface CipherChaCha20Poly1305 extends Cipheriv {
+ setAAD(
+ buffer: NodeJS.ArrayBufferView,
+ options: {
+ plaintextLength: number;
+ },
+ ): this;
+ getAuthTag(): NonSharedBuffer;
+ }
+ /**
+ * Creates and returns a `Decipheriv` object that uses the given `algorithm`, `key` and initialization vector (`iv`).
+ *
+ * The `options` argument controls stream behavior and is optional except when a
+ * cipher in CCM or OCB mode (e.g. `'aes-128-ccm'`) is used. In that case, the `authTagLength` option is required and specifies the length of the
+ * authentication tag in bytes, see `CCM mode`. In GCM mode, the `authTagLength` option is not required but can be used to restrict accepted authentication tags
+ * to those with the specified length.
+ * For `chacha20-poly1305`, the `authTagLength` option defaults to 16 bytes.
+ *
+ * The `algorithm` is dependent on OpenSSL, examples are `'aes192'`, etc. On
+ * recent OpenSSL releases, `openssl list -cipher-algorithms` will
+ * display the available cipher algorithms.
+ *
+ * The `key` is the raw key used by the `algorithm` and `iv` is an [initialization vector](https://en.wikipedia.org/wiki/Initialization_vector). Both arguments must be `'utf8'` encoded
+ * strings,`Buffers`, `TypedArray`, or `DataView`s. The `key` may optionally be
+ * a `KeyObject` of type `secret`. If the cipher does not need
+ * an initialization vector, `iv` may be `null`.
+ *
+ * When passing strings for `key` or `iv`, please consider `caveats when using strings as inputs to cryptographic APIs`.
+ *
+ * Initialization vectors should be unpredictable and unique; ideally, they will be
+ * cryptographically random. They do not have to be secret: IVs are typically just
+ * added to ciphertext messages unencrypted. It may sound contradictory that
+ * something has to be unpredictable and unique, but does not have to be secret;
+ * remember that an attacker must not be able to predict ahead of time what a given
+ * IV will be.
+ * @since v0.1.94
+ * @param options `stream.transform` options
+ */
+ function createDecipheriv(
+ algorithm: CipherCCMTypes,
+ key: CipherKey,
+ iv: BinaryLike,
+ options: CipherCCMOptions,
+ ): DecipherCCM;
+ function createDecipheriv(
+ algorithm: CipherOCBTypes,
+ key: CipherKey,
+ iv: BinaryLike,
+ options: CipherOCBOptions,
+ ): DecipherOCB;
+ function createDecipheriv(
+ algorithm: CipherGCMTypes,
+ key: CipherKey,
+ iv: BinaryLike,
+ options?: CipherGCMOptions,
+ ): DecipherGCM;
+ function createDecipheriv(
+ algorithm: CipherChaCha20Poly1305Types,
+ key: CipherKey,
+ iv: BinaryLike,
+ options?: CipherChaCha20Poly1305Options,
+ ): DecipherChaCha20Poly1305;
+ function createDecipheriv(
+ algorithm: string,
+ key: CipherKey,
+ iv: BinaryLike | null,
+ options?: stream.TransformOptions,
+ ): Decipheriv;
+ /**
+ * Instances of the `Decipheriv` class are used to decrypt data. The class can be
+ * used in one of two ways:
+ *
+ * * As a `stream` that is both readable and writable, where plain encrypted
+ * data is written to produce unencrypted data on the readable side, or
+ * * Using the `decipher.update()` and `decipher.final()` methods to
+ * produce the unencrypted data.
+ *
+ * The {@link createDecipheriv} method is
+ * used to create `Decipheriv` instances. `Decipheriv` objects are not to be created
+ * directly using the `new` keyword.
+ *
+ * Example: Using `Decipheriv` objects as streams:
+ *
+ * ```js
+ * import { Buffer } from 'node:buffer';
+ * const {
+ * scryptSync,
+ * createDecipheriv,
+ * } = await import('node:crypto');
+ *
+ * const algorithm = 'aes-192-cbc';
+ * const password = 'Password used to generate key';
+ * // Key length is dependent on the algorithm. In this case for aes192, it is
+ * // 24 bytes (192 bits).
+ * // Use the async `crypto.scrypt()` instead.
+ * const key = scryptSync(password, 'salt', 24);
+ * // The IV is usually passed along with the ciphertext.
+ * const iv = Buffer.alloc(16, 0); // Initialization vector.
+ *
+ * const decipher = createDecipheriv(algorithm, key, iv);
+ *
+ * let decrypted = '';
+ * decipher.on('readable', () => {
+ * let chunk;
+ * while (null !== (chunk = decipher.read())) {
+ * decrypted += chunk.toString('utf8');
+ * }
+ * });
+ * decipher.on('end', () => {
+ * console.log(decrypted);
+ * // Prints: some clear text data
+ * });
+ *
+ * // Encrypted with same algorithm, key and iv.
+ * const encrypted =
+ * 'e5f79c5915c02171eec6b212d5520d44480993d7d622a7c4c2da32f6efda0ffa';
+ * decipher.write(encrypted, 'hex');
+ * decipher.end();
+ * ```
+ *
+ * Example: Using `Decipheriv` and piped streams:
+ *
+ * ```js
+ * import {
+ * createReadStream,
+ * createWriteStream,
+ * } from 'node:fs';
+ * import { Buffer } from 'node:buffer';
+ * const {
+ * scryptSync,
+ * createDecipheriv,
+ * } = await import('node:crypto');
+ *
+ * const algorithm = 'aes-192-cbc';
+ * const password = 'Password used to generate key';
+ * // Use the async `crypto.scrypt()` instead.
+ * const key = scryptSync(password, 'salt', 24);
+ * // The IV is usually passed along with the ciphertext.
+ * const iv = Buffer.alloc(16, 0); // Initialization vector.
+ *
+ * const decipher = createDecipheriv(algorithm, key, iv);
+ *
+ * const input = createReadStream('test.enc');
+ * const output = createWriteStream('test.js');
+ *
+ * input.pipe(decipher).pipe(output);
+ * ```
+ *
+ * Example: Using the `decipher.update()` and `decipher.final()` methods:
+ *
+ * ```js
+ * import { Buffer } from 'node:buffer';
+ * const {
+ * scryptSync,
+ * createDecipheriv,
+ * } = await import('node:crypto');
+ *
+ * const algorithm = 'aes-192-cbc';
+ * const password = 'Password used to generate key';
+ * // Use the async `crypto.scrypt()` instead.
+ * const key = scryptSync(password, 'salt', 24);
+ * // The IV is usually passed along with the ciphertext.
+ * const iv = Buffer.alloc(16, 0); // Initialization vector.
+ *
+ * const decipher = createDecipheriv(algorithm, key, iv);
+ *
+ * // Encrypted using same algorithm, key and iv.
+ * const encrypted =
+ * 'e5f79c5915c02171eec6b212d5520d44480993d7d622a7c4c2da32f6efda0ffa';
+ * let decrypted = decipher.update(encrypted, 'hex', 'utf8');
+ * decrypted += decipher.final('utf8');
+ * console.log(decrypted);
+ * // Prints: some clear text data
+ * ```
+ * @since v0.1.94
+ */
+ class Decipheriv extends stream.Transform {
+ private constructor();
+ /**
+ * Updates the decipher with `data`. If the `inputEncoding` argument is given,
+ * the `data` argument is a string using the specified encoding. If the `inputEncoding` argument is not given, `data` must be a `Buffer`. If `data` is a `Buffer` then `inputEncoding` is
+ * ignored.
+ *
+ * The `outputEncoding` specifies the output format of the enciphered
+ * data. If the `outputEncoding` is specified, a string using the specified encoding is returned. If no `outputEncoding` is provided, a `Buffer` is returned.
+ *
+ * The `decipher.update()` method can be called multiple times with new data until `decipher.final()` is called. Calling `decipher.update()` after `decipher.final()` will result in an error
+ * being thrown.
+ * @since v0.1.94
+ * @param inputEncoding The `encoding` of the `data` string.
+ * @param outputEncoding The `encoding` of the return value.
+ */
+ update(data: NodeJS.ArrayBufferView): NonSharedBuffer;
+ update(data: string, inputEncoding: Encoding): NonSharedBuffer;
+ update(data: NodeJS.ArrayBufferView, inputEncoding: undefined, outputEncoding: Encoding): string;
+ update(data: string, inputEncoding: Encoding | undefined, outputEncoding: Encoding): string;
+ /**
+ * Once the `decipher.final()` method has been called, the `Decipheriv` object can
+ * no longer be used to decrypt data. Attempts to call `decipher.final()` more
+ * than once will result in an error being thrown.
+ * @since v0.1.94
+ * @param outputEncoding The `encoding` of the return value.
+ * @return Any remaining deciphered contents. If `outputEncoding` is specified, a string is returned. If an `outputEncoding` is not provided, a {@link Buffer} is returned.
+ */
+ final(): NonSharedBuffer;
+ final(outputEncoding: BufferEncoding): string;
+ /**
+ * When data has been encrypted without standard block padding, calling `decipher.setAutoPadding(false)` will disable automatic padding to prevent `decipher.final()` from checking for and
+ * removing padding.
+ *
+ * Turning auto padding off will only work if the input data's length is a
+ * multiple of the ciphers block size.
+ *
+ * The `decipher.setAutoPadding()` method must be called before `decipher.final()`.
+ * @since v0.7.1
+ * @param [autoPadding=true]
+ * @return for method chaining.
+ */
+ setAutoPadding(auto_padding?: boolean): this;
+ }
+ interface DecipherCCM extends Decipheriv {
+ setAuthTag(buffer: NodeJS.ArrayBufferView): this;
+ setAAD(
+ buffer: NodeJS.ArrayBufferView,
+ options: {
+ plaintextLength: number;
+ },
+ ): this;
+ }
+ interface DecipherGCM extends Decipheriv {
+ setAuthTag(buffer: NodeJS.ArrayBufferView): this;
+ setAAD(
+ buffer: NodeJS.ArrayBufferView,
+ options?: {
+ plaintextLength: number;
+ },
+ ): this;
+ }
+ interface DecipherOCB extends Decipheriv {
+ setAuthTag(buffer: NodeJS.ArrayBufferView): this;
+ setAAD(
+ buffer: NodeJS.ArrayBufferView,
+ options?: {
+ plaintextLength: number;
+ },
+ ): this;
+ }
+ interface DecipherChaCha20Poly1305 extends Decipheriv {
+ setAuthTag(buffer: NodeJS.ArrayBufferView): this;
+ setAAD(
+ buffer: NodeJS.ArrayBufferView,
+ options: {
+ plaintextLength: number;
+ },
+ ): this;
+ }
+ interface PrivateKeyInput {
+ key: string | Buffer;
+ format?: KeyFormat | undefined;
+ type?: PrivateKeyExportType | undefined;
+ passphrase?: string | Buffer | undefined;
+ encoding?: string | undefined;
+ }
+ interface PublicKeyInput {
+ key: string | Buffer;
+ format?: KeyFormat | undefined;
+ type?: PublicKeyExportType | undefined;
+ encoding?: string | undefined;
+ }
+ /**
+ * Asynchronously generates a new random secret key of the given `length`. The `type` will determine which validations will be performed on the `length`.
+ *
+ * ```js
+ * const {
+ * generateKey,
+ * } = await import('node:crypto');
+ *
+ * generateKey('hmac', { length: 512 }, (err, key) => {
+ * if (err) throw err;
+ * console.log(key.export().toString('hex')); // 46e..........620
+ * });
+ * ```
+ *
+ * The size of a generated HMAC key should not exceed the block size of the
+ * underlying hash function. See {@link createHmac} for more information.
+ * @since v15.0.0
+ * @param type The intended use of the generated secret key. Currently accepted values are `'hmac'` and `'aes'`.
+ */
+ function generateKey(
+ type: "hmac" | "aes",
+ options: {
+ length: number;
+ },
+ callback: (err: Error | null, key: KeyObject) => void,
+ ): void;
+ /**
+ * Synchronously generates a new random secret key of the given `length`. The `type` will determine which validations will be performed on the `length`.
+ *
+ * ```js
+ * const {
+ * generateKeySync,
+ * } = await import('node:crypto');
+ *
+ * const key = generateKeySync('hmac', { length: 512 });
+ * console.log(key.export().toString('hex')); // e89..........41e
+ * ```
+ *
+ * The size of a generated HMAC key should not exceed the block size of the
+ * underlying hash function. See {@link createHmac} for more information.
+ * @since v15.0.0
+ * @param type The intended use of the generated secret key. Currently accepted values are `'hmac'` and `'aes'`.
+ */
+ function generateKeySync(
+ type: "hmac" | "aes",
+ options: {
+ length: number;
+ },
+ ): KeyObject;
+ interface JsonWebKeyInput {
+ key: webcrypto.JsonWebKey;
+ format: "jwk";
+ }
+ /**
+ * Creates and returns a new key object containing a private key. If `key` is a
+ * string or `Buffer`, `format` is assumed to be `'pem'`; otherwise, `key` must be an object with the properties described above.
+ *
+ * If the private key is encrypted, a `passphrase` must be specified. The length
+ * of the passphrase is limited to 1024 bytes.
+ * @since v11.6.0
+ */
+ function createPrivateKey(key: PrivateKeyInput | string | Buffer | JsonWebKeyInput): KeyObject;
+ /**
+ * Creates and returns a new key object containing a public key. If `key` is a
+ * string or `Buffer`, `format` is assumed to be `'pem'`; if `key` is a `KeyObject` with type `'private'`, the public key is derived from the given private key;
+ * otherwise, `key` must be an object with the properties described above.
+ *
+ * If the format is `'pem'`, the `'key'` may also be an X.509 certificate.
+ *
+ * Because public keys can be derived from private keys, a private key may be
+ * passed instead of a public key. In that case, this function behaves as if {@link createPrivateKey} had been called, except that the type of the
+ * returned `KeyObject` will be `'public'` and that the private key cannot be
+ * extracted from the returned `KeyObject`. Similarly, if a `KeyObject` with type `'private'` is given, a new `KeyObject` with type `'public'` will be returned
+ * and it will be impossible to extract the private key from the returned object.
+ * @since v11.6.0
+ */
+ function createPublicKey(key: PublicKeyInput | string | Buffer | KeyObject | JsonWebKeyInput): KeyObject;
+ /**
+ * Creates and returns a new key object containing a secret key for symmetric
+ * encryption or `Hmac`.
+ * @since v11.6.0
+ * @param encoding The string encoding when `key` is a string.
+ */
+ function createSecretKey(key: NodeJS.ArrayBufferView): KeyObject;
+ function createSecretKey(key: string, encoding: BufferEncoding): KeyObject;
+ /**
+ * Creates and returns a `Sign` object that uses the given `algorithm`. Use {@link getHashes} to obtain the names of the available digest algorithms.
+ * Optional `options` argument controls the `stream.Writable` behavior.
+ *
+ * In some cases, a `Sign` instance can be created using the name of a signature
+ * algorithm, such as `'RSA-SHA256'`, instead of a digest algorithm. This will use
+ * the corresponding digest algorithm. This does not work for all signature
+ * algorithms, such as `'ecdsa-with-SHA256'`, so it is best to always use digest
+ * algorithm names.
+ * @since v0.1.92
+ * @param options `stream.Writable` options
+ */
+ // TODO: signing algorithm type
+ function createSign(algorithm: string, options?: stream.WritableOptions): Sign;
+ type DSAEncoding = "der" | "ieee-p1363";
+ interface SigningOptions {
+ /**
+ * @see crypto.constants.RSA_PKCS1_PADDING
+ */
+ padding?: number | undefined;
+ saltLength?: number | undefined;
+ dsaEncoding?: DSAEncoding | undefined;
+ context?: ArrayBuffer | NodeJS.ArrayBufferView | undefined;
+ }
+ interface SignPrivateKeyInput extends PrivateKeyInput, SigningOptions {}
+ interface SignKeyObjectInput extends SigningOptions {
+ key: KeyObject;
+ }
+ interface SignJsonWebKeyInput extends JsonWebKeyInput, SigningOptions {}
+ interface VerifyPublicKeyInput extends PublicKeyInput, SigningOptions {}
+ interface VerifyKeyObjectInput extends SigningOptions {
+ key: KeyObject;
+ }
+ interface VerifyJsonWebKeyInput extends JsonWebKeyInput, SigningOptions {}
+ type KeyLike = string | Buffer | KeyObject;
+ /**
+ * The `Sign` class is a utility for generating signatures. It can be used in one
+ * of two ways:
+ *
+ * * As a writable `stream`, where data to be signed is written and the `sign.sign()` method is used to generate and return the signature, or
+ * * Using the `sign.update()` and `sign.sign()` methods to produce the
+ * signature.
+ *
+ * The {@link createSign} method is used to create `Sign` instances. The
+ * argument is the string name of the hash function to use. `Sign` objects are not
+ * to be created directly using the `new` keyword.
+ *
+ * Example: Using `Sign` and `Verify` objects as streams:
+ *
+ * ```js
+ * const {
+ * generateKeyPairSync,
+ * createSign,
+ * createVerify,
+ * } = await import('node:crypto');
+ *
+ * const { privateKey, publicKey } = generateKeyPairSync('ec', {
+ * namedCurve: 'sect239k1',
+ * });
+ *
+ * const sign = createSign('SHA256');
+ * sign.write('some data to sign');
+ * sign.end();
+ * const signature = sign.sign(privateKey, 'hex');
+ *
+ * const verify = createVerify('SHA256');
+ * verify.write('some data to sign');
+ * verify.end();
+ * console.log(verify.verify(publicKey, signature, 'hex'));
+ * // Prints: true
+ * ```
+ *
+ * Example: Using the `sign.update()` and `verify.update()` methods:
+ *
+ * ```js
+ * const {
+ * generateKeyPairSync,
+ * createSign,
+ * createVerify,
+ * } = await import('node:crypto');
+ *
+ * const { privateKey, publicKey } = generateKeyPairSync('rsa', {
+ * modulusLength: 2048,
+ * });
+ *
+ * const sign = createSign('SHA256');
+ * sign.update('some data to sign');
+ * sign.end();
+ * const signature = sign.sign(privateKey);
+ *
+ * const verify = createVerify('SHA256');
+ * verify.update('some data to sign');
+ * verify.end();
+ * console.log(verify.verify(publicKey, signature));
+ * // Prints: true
+ * ```
+ * @since v0.1.92
+ */
+ class Sign extends stream.Writable {
+ private constructor();
+ /**
+ * Updates the `Sign` content with the given `data`, the encoding of which
+ * is given in `inputEncoding`.
+ * If `encoding` is not provided, and the `data` is a string, an
+ * encoding of `'utf8'` is enforced. If `data` is a `Buffer`, `TypedArray`, or`DataView`, then `inputEncoding` is ignored.
+ *
+ * This can be called many times with new data as it is streamed.
+ * @since v0.1.92
+ * @param inputEncoding The `encoding` of the `data` string.
+ */
+ update(data: BinaryLike): this;
+ update(data: string, inputEncoding: Encoding): this;
+ /**
+ * Calculates the signature on all the data passed through using either `sign.update()` or `sign.write()`.
+ *
+ * If `privateKey` is not a `KeyObject`, this function behaves as if `privateKey` had been passed to {@link createPrivateKey}. If it is an
+ * object, the following additional properties can be passed:
+ *
+ * If `outputEncoding` is provided a string is returned; otherwise a `Buffer` is returned.
+ *
+ * The `Sign` object can not be again used after `sign.sign()` method has been
+ * called. Multiple calls to `sign.sign()` will result in an error being thrown.
+ * @since v0.1.92
+ */
+ sign(privateKey: KeyLike | SignKeyObjectInput | SignPrivateKeyInput | SignJsonWebKeyInput): NonSharedBuffer;
+ sign(
+ privateKey: KeyLike | SignKeyObjectInput | SignPrivateKeyInput | SignJsonWebKeyInput,
+ outputFormat: BinaryToTextEncoding,
+ ): string;
+ }
+ /**
+ * Creates and returns a `Verify` object that uses the given algorithm.
+ * Use {@link getHashes} to obtain an array of names of the available
+ * signing algorithms. Optional `options` argument controls the `stream.Writable` behavior.
+ *
+ * In some cases, a `Verify` instance can be created using the name of a signature
+ * algorithm, such as `'RSA-SHA256'`, instead of a digest algorithm. This will use
+ * the corresponding digest algorithm. This does not work for all signature
+ * algorithms, such as `'ecdsa-with-SHA256'`, so it is best to always use digest
+ * algorithm names.
+ * @since v0.1.92
+ * @param options `stream.Writable` options
+ */
+ function createVerify(algorithm: string, options?: stream.WritableOptions): Verify;
+ /**
+ * The `Verify` class is a utility for verifying signatures. It can be used in one
+ * of two ways:
+ *
+ * * As a writable `stream` where written data is used to validate against the
+ * supplied signature, or
+ * * Using the `verify.update()` and `verify.verify()` methods to verify
+ * the signature.
+ *
+ * The {@link createVerify} method is used to create `Verify` instances. `Verify` objects are not to be created directly using the `new` keyword.
+ *
+ * See `Sign` for examples.
+ * @since v0.1.92
+ */
+ class Verify extends stream.Writable {
+ private constructor();
+ /**
+ * Updates the `Verify` content with the given `data`, the encoding of which
+ * is given in `inputEncoding`.
+ * If `inputEncoding` is not provided, and the `data` is a string, an
+ * encoding of `'utf8'` is enforced. If `data` is a `Buffer`, `TypedArray`, or `DataView`, then `inputEncoding` is ignored.
+ *
+ * This can be called many times with new data as it is streamed.
+ * @since v0.1.92
+ * @param inputEncoding The `encoding` of the `data` string.
+ */
+ update(data: BinaryLike): Verify;
+ update(data: string, inputEncoding: Encoding): Verify;
+ /**
+ * Verifies the provided data using the given `object` and `signature`.
+ *
+ * If `object` is not a `KeyObject`, this function behaves as if `object` had been passed to {@link createPublicKey}. If it is an
+ * object, the following additional properties can be passed:
+ *
+ * The `signature` argument is the previously calculated signature for the data, in
+ * the `signatureEncoding`.
+ * If a `signatureEncoding` is specified, the `signature` is expected to be a
+ * string; otherwise `signature` is expected to be a `Buffer`, `TypedArray`, or `DataView`.
+ *
+ * The `verify` object can not be used again after `verify.verify()` has been
+ * called. Multiple calls to `verify.verify()` will result in an error being
+ * thrown.
+ *
+ * Because public keys can be derived from private keys, a private key may
+ * be passed instead of a public key.
+ * @since v0.1.92
+ */
+ verify(
+ object: KeyLike | VerifyKeyObjectInput | VerifyPublicKeyInput | VerifyJsonWebKeyInput,
+ signature: NodeJS.ArrayBufferView,
+ ): boolean;
+ verify(
+ object: KeyLike | VerifyKeyObjectInput | VerifyPublicKeyInput | VerifyJsonWebKeyInput,
+ signature: string,
+ signature_format?: BinaryToTextEncoding,
+ ): boolean;
+ }
+ /**
+ * Creates a `DiffieHellman` key exchange object using the supplied `prime` and an
+ * optional specific `generator`.
+ *
+ * The `generator` argument can be a number, string, or `Buffer`. If `generator` is not specified, the value `2` is used.
+ *
+ * If `primeEncoding` is specified, `prime` is expected to be a string; otherwise
+ * a `Buffer`, `TypedArray`, or `DataView` is expected.
+ *
+ * If `generatorEncoding` is specified, `generator` is expected to be a string;
+ * otherwise a number, `Buffer`, `TypedArray`, or `DataView` is expected.
+ * @since v0.11.12
+ * @param primeEncoding The `encoding` of the `prime` string.
+ * @param [generator=2]
+ * @param generatorEncoding The `encoding` of the `generator` string.
+ */
+ function createDiffieHellman(primeLength: number, generator?: number): DiffieHellman;
+ function createDiffieHellman(
+ prime: ArrayBuffer | NodeJS.ArrayBufferView,
+ generator?: number | ArrayBuffer | NodeJS.ArrayBufferView,
+ ): DiffieHellman;
+ function createDiffieHellman(
+ prime: ArrayBuffer | NodeJS.ArrayBufferView,
+ generator: string,
+ generatorEncoding: BinaryToTextEncoding,
+ ): DiffieHellman;
+ function createDiffieHellman(
+ prime: string,
+ primeEncoding: BinaryToTextEncoding,
+ generator?: number | ArrayBuffer | NodeJS.ArrayBufferView,
+ ): DiffieHellman;
+ function createDiffieHellman(
+ prime: string,
+ primeEncoding: BinaryToTextEncoding,
+ generator: string,
+ generatorEncoding: BinaryToTextEncoding,
+ ): DiffieHellman;
+ /**
+ * The `DiffieHellman` class is a utility for creating Diffie-Hellman key
+ * exchanges.
+ *
+ * Instances of the `DiffieHellman` class can be created using the {@link createDiffieHellman} function.
+ *
+ * ```js
+ * import assert from 'node:assert';
+ *
+ * const {
+ * createDiffieHellman,
+ * } = await import('node:crypto');
+ *
+ * // Generate Alice's keys...
+ * const alice = createDiffieHellman(2048);
+ * const aliceKey = alice.generateKeys();
+ *
+ * // Generate Bob's keys...
+ * const bob = createDiffieHellman(alice.getPrime(), alice.getGenerator());
+ * const bobKey = bob.generateKeys();
+ *
+ * // Exchange and generate the secret...
+ * const aliceSecret = alice.computeSecret(bobKey);
+ * const bobSecret = bob.computeSecret(aliceKey);
+ *
+ * // OK
+ * assert.strictEqual(aliceSecret.toString('hex'), bobSecret.toString('hex'));
+ * ```
+ * @since v0.5.0
+ */
+ class DiffieHellman {
+ private constructor();
+ /**
+ * Generates private and public Diffie-Hellman key values unless they have been
+ * generated or computed already, and returns
+ * the public key in the specified `encoding`. This key should be
+ * transferred to the other party.
+ * If `encoding` is provided a string is returned; otherwise a `Buffer` is returned.
+ *
+ * This function is a thin wrapper around [`DH_generate_key()`](https://www.openssl.org/docs/man3.0/man3/DH_generate_key.html). In particular,
+ * once a private key has been generated or set, calling this function only updates
+ * the public key but does not generate a new private key.
+ * @since v0.5.0
+ * @param encoding The `encoding` of the return value.
+ */
+ generateKeys(): NonSharedBuffer;
+ generateKeys(encoding: BinaryToTextEncoding): string;
+ /**
+ * Computes the shared secret using `otherPublicKey` as the other
+ * party's public key and returns the computed shared secret. The supplied
+ * key is interpreted using the specified `inputEncoding`, and secret is
+ * encoded using specified `outputEncoding`.
+ * If the `inputEncoding` is not
+ * provided, `otherPublicKey` is expected to be a `Buffer`, `TypedArray`, or `DataView`.
+ *
+ * If `outputEncoding` is given a string is returned; otherwise, a `Buffer` is returned.
+ * @since v0.5.0
+ * @param inputEncoding The `encoding` of an `otherPublicKey` string.
+ * @param outputEncoding The `encoding` of the return value.
+ */
+ computeSecret(
+ otherPublicKey: NodeJS.ArrayBufferView,
+ inputEncoding?: null,
+ outputEncoding?: null,
+ ): NonSharedBuffer;
+ computeSecret(
+ otherPublicKey: string,
+ inputEncoding: BinaryToTextEncoding,
+ outputEncoding?: null,
+ ): NonSharedBuffer;
+ computeSecret(
+ otherPublicKey: NodeJS.ArrayBufferView,
+ inputEncoding: null,
+ outputEncoding: BinaryToTextEncoding,
+ ): string;
+ computeSecret(
+ otherPublicKey: string,
+ inputEncoding: BinaryToTextEncoding,
+ outputEncoding: BinaryToTextEncoding,
+ ): string;
+ /**
+ * Returns the Diffie-Hellman prime in the specified `encoding`.
+ * If `encoding` is provided a string is
+ * returned; otherwise a `Buffer` is returned.
+ * @since v0.5.0
+ * @param encoding The `encoding` of the return value.
+ */
+ getPrime(): NonSharedBuffer;
+ getPrime(encoding: BinaryToTextEncoding): string;
+ /**
+ * Returns the Diffie-Hellman generator in the specified `encoding`.
+ * If `encoding` is provided a string is
+ * returned; otherwise a `Buffer` is returned.
+ * @since v0.5.0
+ * @param encoding The `encoding` of the return value.
+ */
+ getGenerator(): NonSharedBuffer;
+ getGenerator(encoding: BinaryToTextEncoding): string;
+ /**
+ * Returns the Diffie-Hellman public key in the specified `encoding`.
+ * If `encoding` is provided a
+ * string is returned; otherwise a `Buffer` is returned.
+ * @since v0.5.0
+ * @param encoding The `encoding` of the return value.
+ */
+ getPublicKey(): NonSharedBuffer;
+ getPublicKey(encoding: BinaryToTextEncoding): string;
+ /**
+ * Returns the Diffie-Hellman private key in the specified `encoding`.
+ * If `encoding` is provided a
+ * string is returned; otherwise a `Buffer` is returned.
+ * @since v0.5.0
+ * @param encoding The `encoding` of the return value.
+ */
+ getPrivateKey(): NonSharedBuffer;
+ getPrivateKey(encoding: BinaryToTextEncoding): string;
+ /**
+ * Sets the Diffie-Hellman public key. If the `encoding` argument is provided, `publicKey` is expected
+ * to be a string. If no `encoding` is provided, `publicKey` is expected
+ * to be a `Buffer`, `TypedArray`, or `DataView`.
+ * @since v0.5.0
+ * @param encoding The `encoding` of the `publicKey` string.
+ */
+ setPublicKey(publicKey: NodeJS.ArrayBufferView): void;
+ setPublicKey(publicKey: string, encoding: BufferEncoding): void;
+ /**
+ * Sets the Diffie-Hellman private key. If the `encoding` argument is provided,`privateKey` is expected
+ * to be a string. If no `encoding` is provided, `privateKey` is expected
+ * to be a `Buffer`, `TypedArray`, or `DataView`.
+ *
+ * This function does not automatically compute the associated public key. Either `diffieHellman.setPublicKey()` or `diffieHellman.generateKeys()` can be
+ * used to manually provide the public key or to automatically derive it.
+ * @since v0.5.0
+ * @param encoding The `encoding` of the `privateKey` string.
+ */
+ setPrivateKey(privateKey: NodeJS.ArrayBufferView): void;
+ setPrivateKey(privateKey: string, encoding: BufferEncoding): void;
+ /**
+ * A bit field containing any warnings and/or errors resulting from a check
+ * performed during initialization of the `DiffieHellman` object.
+ *
+ * The following values are valid for this property (as defined in `node:constants` module):
+ *
+ * * `DH_CHECK_P_NOT_SAFE_PRIME`
+ * * `DH_CHECK_P_NOT_PRIME`
+ * * `DH_UNABLE_TO_CHECK_GENERATOR`
+ * * `DH_NOT_SUITABLE_GENERATOR`
+ * @since v0.11.12
+ */
+ verifyError: number;
+ }
+ /**
+ * The `DiffieHellmanGroup` class takes a well-known modp group as its argument.
+ * It works the same as `DiffieHellman`, except that it does not allow changing its keys after creation.
+ * In other words, it does not implement `setPublicKey()` or `setPrivateKey()` methods.
+ *
+ * ```js
+ * const { createDiffieHellmanGroup } = await import('node:crypto');
+ * const dh = createDiffieHellmanGroup('modp1');
+ * ```
+ * The name (e.g. `'modp1'`) is taken from [RFC 2412](https://www.rfc-editor.org/rfc/rfc2412.txt) (modp1 and 2) and [RFC 3526](https://www.rfc-editor.org/rfc/rfc3526.txt):
+ * ```bash
+ * $ perl -ne 'print "$1\n" if /"(modp\d+)"/' src/node_crypto_groups.h
+ * modp1 # 768 bits
+ * modp2 # 1024 bits
+ * modp5 # 1536 bits
+ * modp14 # 2048 bits
+ * modp15 # etc.
+ * modp16
+ * modp17
+ * modp18
+ * ```
+ * @since v0.7.5
+ */
+ const DiffieHellmanGroup: DiffieHellmanGroupConstructor;
+ interface DiffieHellmanGroupConstructor {
+ new(name: string): DiffieHellmanGroup;
+ (name: string): DiffieHellmanGroup;
+ readonly prototype: DiffieHellmanGroup;
+ }
+ type DiffieHellmanGroup = Omit;
+ /**
+ * Creates a predefined `DiffieHellmanGroup` key exchange object. The
+ * supported groups are listed in the documentation for `DiffieHellmanGroup`.
+ *
+ * The returned object mimics the interface of objects created by {@link createDiffieHellman}, but will not allow changing
+ * the keys (with `diffieHellman.setPublicKey()`, for example). The
+ * advantage of using this method is that the parties do not have to
+ * generate nor exchange a group modulus beforehand, saving both processor
+ * and communication time.
+ *
+ * Example (obtaining a shared secret):
+ *
+ * ```js
+ * const {
+ * getDiffieHellman,
+ * } = await import('node:crypto');
+ * const alice = getDiffieHellman('modp14');
+ * const bob = getDiffieHellman('modp14');
+ *
+ * alice.generateKeys();
+ * bob.generateKeys();
+ *
+ * const aliceSecret = alice.computeSecret(bob.getPublicKey(), null, 'hex');
+ * const bobSecret = bob.computeSecret(alice.getPublicKey(), null, 'hex');
+ *
+ * // aliceSecret and bobSecret should be the same
+ * console.log(aliceSecret === bobSecret);
+ * ```
+ * @since v0.7.5
+ */
+ function getDiffieHellman(groupName: string): DiffieHellmanGroup;
+ /**
+ * An alias for {@link getDiffieHellman}
+ * @since v0.9.3
+ */
+ function createDiffieHellmanGroup(name: string): DiffieHellmanGroup;
+ /**
+ * Provides an asynchronous Password-Based Key Derivation Function 2 (PBKDF2)
+ * implementation. A selected HMAC digest algorithm specified by `digest` is
+ * applied to derive a key of the requested byte length (`keylen`) from the `password`, `salt` and `iterations`.
+ *
+ * The supplied `callback` function is called with two arguments: `err` and `derivedKey`. If an error occurs while deriving the key, `err` will be set;
+ * otherwise `err` will be `null`. By default, the successfully generated `derivedKey` will be passed to the callback as a `Buffer`. An error will be
+ * thrown if any of the input arguments specify invalid values or types.
+ *
+ * The `iterations` argument must be a number set as high as possible. The
+ * higher the number of iterations, the more secure the derived key will be,
+ * but will take a longer amount of time to complete.
+ *
+ * The `salt` should be as unique as possible. It is recommended that a salt is
+ * random and at least 16 bytes long. See [NIST SP 800-132](https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-132.pdf) for details.
+ *
+ * When passing strings for `password` or `salt`, please consider `caveats when using strings as inputs to cryptographic APIs`.
+ *
+ * ```js
+ * const {
+ * pbkdf2,
+ * } = await import('node:crypto');
+ *
+ * pbkdf2('secret', 'salt', 100000, 64, 'sha512', (err, derivedKey) => {
+ * if (err) throw err;
+ * console.log(derivedKey.toString('hex')); // '3745e48...08d59ae'
+ * });
+ * ```
+ *
+ * An array of supported digest functions can be retrieved using {@link getHashes}.
+ *
+ * This API uses libuv's threadpool, which can have surprising and
+ * negative performance implications for some applications; see the `UV_THREADPOOL_SIZE` documentation for more information.
+ * @since v0.5.5
+ */
+ function pbkdf2(
+ password: BinaryLike,
+ salt: BinaryLike,
+ iterations: number,
+ keylen: number,
+ digest: string,
+ callback: (err: Error | null, derivedKey: NonSharedBuffer) => void,
+ ): void;
+ /**
+ * Provides a synchronous Password-Based Key Derivation Function 2 (PBKDF2)
+ * implementation. A selected HMAC digest algorithm specified by `digest` is
+ * applied to derive a key of the requested byte length (`keylen`) from the `password`, `salt` and `iterations`.
+ *
+ * If an error occurs an `Error` will be thrown, otherwise the derived key will be
+ * returned as a `Buffer`.
+ *
+ * The `iterations` argument must be a number set as high as possible. The
+ * higher the number of iterations, the more secure the derived key will be,
+ * but will take a longer amount of time to complete.
+ *
+ * The `salt` should be as unique as possible. It is recommended that a salt is
+ * random and at least 16 bytes long. See [NIST SP 800-132](https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-132.pdf) for details.
+ *
+ * When passing strings for `password` or `salt`, please consider `caveats when using strings as inputs to cryptographic APIs`.
+ *
+ * ```js
+ * const {
+ * pbkdf2Sync,
+ * } = await import('node:crypto');
+ *
+ * const key = pbkdf2Sync('secret', 'salt', 100000, 64, 'sha512');
+ * console.log(key.toString('hex')); // '3745e48...08d59ae'
+ * ```
+ *
+ * An array of supported digest functions can be retrieved using {@link getHashes}.
+ * @since v0.9.3
+ */
+ function pbkdf2Sync(
+ password: BinaryLike,
+ salt: BinaryLike,
+ iterations: number,
+ keylen: number,
+ digest: string,
+ ): NonSharedBuffer;
+ /**
+ * Generates cryptographically strong pseudorandom data. The `size` argument
+ * is a number indicating the number of bytes to generate.
+ *
+ * If a `callback` function is provided, the bytes are generated asynchronously
+ * and the `callback` function is invoked with two arguments: `err` and `buf`.
+ * If an error occurs, `err` will be an `Error` object; otherwise it is `null`. The `buf` argument is a `Buffer` containing the generated bytes.
+ *
+ * ```js
+ * // Asynchronous
+ * const {
+ * randomBytes,
+ * } = await import('node:crypto');
+ *
+ * randomBytes(256, (err, buf) => {
+ * if (err) throw err;
+ * console.log(`${buf.length} bytes of random data: ${buf.toString('hex')}`);
+ * });
+ * ```
+ *
+ * If the `callback` function is not provided, the random bytes are generated
+ * synchronously and returned as a `Buffer`. An error will be thrown if
+ * there is a problem generating the bytes.
+ *
+ * ```js
+ * // Synchronous
+ * const {
+ * randomBytes,
+ * } = await import('node:crypto');
+ *
+ * const buf = randomBytes(256);
+ * console.log(
+ * `${buf.length} bytes of random data: ${buf.toString('hex')}`);
+ * ```
+ *
+ * The `crypto.randomBytes()` method will not complete until there is
+ * sufficient entropy available.
+ * This should normally never take longer than a few milliseconds. The only time
+ * when generating the random bytes may conceivably block for a longer period of
+ * time is right after boot, when the whole system is still low on entropy.
+ *
+ * This API uses libuv's threadpool, which can have surprising and
+ * negative performance implications for some applications; see the `UV_THREADPOOL_SIZE` documentation for more information.
+ *
+ * The asynchronous version of `crypto.randomBytes()` is carried out in a single
+ * threadpool request. To minimize threadpool task length variation, partition
+ * large `randomBytes` requests when doing so as part of fulfilling a client
+ * request.
+ * @since v0.5.8
+ * @param size The number of bytes to generate. The `size` must not be larger than `2**31 - 1`.
+ * @return if the `callback` function is not provided.
+ */
+ function randomBytes(size: number): NonSharedBuffer;
+ function randomBytes(size: number, callback: (err: Error | null, buf: NonSharedBuffer) => void): void;
+ function pseudoRandomBytes(size: number): NonSharedBuffer;
+ function pseudoRandomBytes(size: number, callback: (err: Error | null, buf: NonSharedBuffer) => void): void;
+ /**
+ * Return a random integer `n` such that `min <= n < max`. This
+ * implementation avoids [modulo bias](https://en.wikipedia.org/wiki/Fisher%E2%80%93Yates_shuffle#Modulo_bias).
+ *
+ * The range (`max - min`) must be less than 2**48. `min` and `max` must
+ * be [safe integers](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Number/isSafeInteger).
+ *
+ * If the `callback` function is not provided, the random integer is
+ * generated synchronously.
+ *
+ * ```js
+ * // Asynchronous
+ * const {
+ * randomInt,
+ * } = await import('node:crypto');
+ *
+ * randomInt(3, (err, n) => {
+ * if (err) throw err;
+ * console.log(`Random number chosen from (0, 1, 2): ${n}`);
+ * });
+ * ```
+ *
+ * ```js
+ * // Synchronous
+ * const {
+ * randomInt,
+ * } = await import('node:crypto');
+ *
+ * const n = randomInt(3);
+ * console.log(`Random number chosen from (0, 1, 2): ${n}`);
+ * ```
+ *
+ * ```js
+ * // With `min` argument
+ * const {
+ * randomInt,
+ * } = await import('node:crypto');
+ *
+ * const n = randomInt(1, 7);
+ * console.log(`The dice rolled: ${n}`);
+ * ```
+ * @since v14.10.0, v12.19.0
+ * @param [min=0] Start of random range (inclusive).
+ * @param max End of random range (exclusive).
+ * @param callback `function(err, n) {}`.
+ */
+ function randomInt(max: number): number;
+ function randomInt(min: number, max: number): number;
+ function randomInt(max: number, callback: (err: Error | null, value: number) => void): void;
+ function randomInt(min: number, max: number, callback: (err: Error | null, value: number) => void): void;
+ /**
+ * Synchronous version of {@link randomFill}.
+ *
+ * ```js
+ * import { Buffer } from 'node:buffer';
+ * const { randomFillSync } = await import('node:crypto');
+ *
+ * const buf = Buffer.alloc(10);
+ * console.log(randomFillSync(buf).toString('hex'));
+ *
+ * randomFillSync(buf, 5);
+ * console.log(buf.toString('hex'));
+ *
+ * // The above is equivalent to the following:
+ * randomFillSync(buf, 5, 5);
+ * console.log(buf.toString('hex'));
+ * ```
+ *
+ * Any `ArrayBuffer`, `TypedArray` or `DataView` instance may be passed as`buffer`.
+ *
+ * ```js
+ * import { Buffer } from 'node:buffer';
+ * const { randomFillSync } = await import('node:crypto');
+ *
+ * const a = new Uint32Array(10);
+ * console.log(Buffer.from(randomFillSync(a).buffer,
+ * a.byteOffset, a.byteLength).toString('hex'));
+ *
+ * const b = new DataView(new ArrayBuffer(10));
+ * console.log(Buffer.from(randomFillSync(b).buffer,
+ * b.byteOffset, b.byteLength).toString('hex'));
+ *
+ * const c = new ArrayBuffer(10);
+ * console.log(Buffer.from(randomFillSync(c)).toString('hex'));
+ * ```
+ * @since v7.10.0, v6.13.0
+ * @param buffer Must be supplied. The size of the provided `buffer` must not be larger than `2**31 - 1`.
+ * @param [offset=0]
+ * @param [size=buffer.length - offset]
+ * @return The object passed as `buffer` argument.
+ */
+ function randomFillSync(buffer: T, offset?: number, size?: number): T;
+ /**
+ * This function is similar to {@link randomBytes} but requires the first
+ * argument to be a `Buffer` that will be filled. It also
+ * requires that a callback is passed in.
+ *
+ * If the `callback` function is not provided, an error will be thrown.
+ *
+ * ```js
+ * import { Buffer } from 'node:buffer';
+ * const { randomFill } = await import('node:crypto');
+ *
+ * const buf = Buffer.alloc(10);
+ * randomFill(buf, (err, buf) => {
+ * if (err) throw err;
+ * console.log(buf.toString('hex'));
+ * });
+ *
+ * randomFill(buf, 5, (err, buf) => {
+ * if (err) throw err;
+ * console.log(buf.toString('hex'));
+ * });
+ *
+ * // The above is equivalent to the following:
+ * randomFill(buf, 5, 5, (err, buf) => {
+ * if (err) throw err;
+ * console.log(buf.toString('hex'));
+ * });
+ * ```
+ *
+ * Any `ArrayBuffer`, `TypedArray`, or `DataView` instance may be passed as `buffer`.
+ *
+ * While this includes instances of `Float32Array` and `Float64Array`, this
+ * function should not be used to generate random floating-point numbers. The
+ * result may contain `+Infinity`, `-Infinity`, and `NaN`, and even if the array
+ * contains finite numbers only, they are not drawn from a uniform random
+ * distribution and have no meaningful lower or upper bounds.
+ *
+ * ```js
+ * import { Buffer } from 'node:buffer';
+ * const { randomFill } = await import('node:crypto');
+ *
+ * const a = new Uint32Array(10);
+ * randomFill(a, (err, buf) => {
+ * if (err) throw err;
+ * console.log(Buffer.from(buf.buffer, buf.byteOffset, buf.byteLength)
+ * .toString('hex'));
+ * });
+ *
+ * const b = new DataView(new ArrayBuffer(10));
+ * randomFill(b, (err, buf) => {
+ * if (err) throw err;
+ * console.log(Buffer.from(buf.buffer, buf.byteOffset, buf.byteLength)
+ * .toString('hex'));
+ * });
+ *
+ * const c = new ArrayBuffer(10);
+ * randomFill(c, (err, buf) => {
+ * if (err) throw err;
+ * console.log(Buffer.from(buf).toString('hex'));
+ * });
+ * ```
+ *
+ * This API uses libuv's threadpool, which can have surprising and
+ * negative performance implications for some applications; see the `UV_THREADPOOL_SIZE` documentation for more information.
+ *
+ * The asynchronous version of `crypto.randomFill()` is carried out in a single
+ * threadpool request. To minimize threadpool task length variation, partition
+ * large `randomFill` requests when doing so as part of fulfilling a client
+ * request.
+ * @since v7.10.0, v6.13.0
+ * @param buffer Must be supplied. The size of the provided `buffer` must not be larger than `2**31 - 1`.
+ * @param [offset=0]
+ * @param [size=buffer.length - offset]
+ * @param callback `function(err, buf) {}`.
+ */
+ function randomFill(
+ buffer: T,
+ callback: (err: Error | null, buf: T) => void,
+ ): void;
+ function randomFill(
+ buffer: T,
+ offset: number,
+ callback: (err: Error | null, buf: T) => void,
+ ): void;
+ function randomFill(
+ buffer: T,
+ offset: number,
+ size: number,
+ callback: (err: Error | null, buf: T) => void,
+ ): void;
+ interface ScryptOptions {
+ cost?: number | undefined;
+ blockSize?: number | undefined;
+ parallelization?: number | undefined;
+ N?: number | undefined;
+ r?: number | undefined;
+ p?: number | undefined;
+ maxmem?: number | undefined;
+ }
+ /**
+ * Provides an asynchronous [scrypt](https://en.wikipedia.org/wiki/Scrypt) implementation. Scrypt is a password-based
+ * key derivation function that is designed to be expensive computationally and
+ * memory-wise in order to make brute-force attacks unrewarding.
+ *
+ * The `salt` should be as unique as possible. It is recommended that a salt is
+ * random and at least 16 bytes long. See [NIST SP 800-132](https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-132.pdf) for details.
+ *
+ * When passing strings for `password` or `salt`, please consider `caveats when using strings as inputs to cryptographic APIs`.
+ *
+ * The `callback` function is called with two arguments: `err` and `derivedKey`. `err` is an exception object when key derivation fails, otherwise `err` is `null`. `derivedKey` is passed to the
+ * callback as a `Buffer`.
+ *
+ * An exception is thrown when any of the input arguments specify invalid values
+ * or types.
+ *
+ * ```js
+ * const {
+ * scrypt,
+ * } = await import('node:crypto');
+ *
+ * // Using the factory defaults.
+ * scrypt('password', 'salt', 64, (err, derivedKey) => {
+ * if (err) throw err;
+ * console.log(derivedKey.toString('hex')); // '3745e48...08d59ae'
+ * });
+ * // Using a custom N parameter. Must be a power of two.
+ * scrypt('password', 'salt', 64, { N: 1024 }, (err, derivedKey) => {
+ * if (err) throw err;
+ * console.log(derivedKey.toString('hex')); // '3745e48...aa39b34'
+ * });
+ * ```
+ * @since v10.5.0
+ */
+ function scrypt(
+ password: BinaryLike,
+ salt: BinaryLike,
+ keylen: number,
+ callback: (err: Error | null, derivedKey: NonSharedBuffer) => void,
+ ): void;
+ function scrypt(
+ password: BinaryLike,
+ salt: BinaryLike,
+ keylen: number,
+ options: ScryptOptions,
+ callback: (err: Error | null, derivedKey: NonSharedBuffer) => void,
+ ): void;
+ /**
+ * Provides a synchronous [scrypt](https://en.wikipedia.org/wiki/Scrypt) implementation. Scrypt is a password-based
+ * key derivation function that is designed to be expensive computationally and
+ * memory-wise in order to make brute-force attacks unrewarding.
+ *
+ * The `salt` should be as unique as possible. It is recommended that a salt is
+ * random and at least 16 bytes long. See [NIST SP 800-132](https://nvlpubs.nist.gov/nistpubs/Legacy/SP/nistspecialpublication800-132.pdf) for details.
+ *
+ * When passing strings for `password` or `salt`, please consider `caveats when using strings as inputs to cryptographic APIs`.
+ *
+ * An exception is thrown when key derivation fails, otherwise the derived key is
+ * returned as a `Buffer`.
+ *
+ * An exception is thrown when any of the input arguments specify invalid values
+ * or types.
+ *
+ * ```js
+ * const {
+ * scryptSync,
+ * } = await import('node:crypto');
+ * // Using the factory defaults.
+ *
+ * const key1 = scryptSync('password', 'salt', 64);
+ * console.log(key1.toString('hex')); // '3745e48...08d59ae'
+ * // Using a custom N parameter. Must be a power of two.
+ * const key2 = scryptSync('password', 'salt', 64, { N: 1024 });
+ * console.log(key2.toString('hex')); // '3745e48...aa39b34'
+ * ```
+ * @since v10.5.0
+ */
+ function scryptSync(
+ password: BinaryLike,
+ salt: BinaryLike,
+ keylen: number,
+ options?: ScryptOptions,
+ ): NonSharedBuffer;
+ interface RsaPublicKey {
+ key: KeyLike;
+ padding?: number | undefined;
+ }
+ interface RsaPrivateKey {
+ key: KeyLike;
+ passphrase?: string | undefined;
+ /**
+ * @default 'sha1'
+ */
+ oaepHash?: string | undefined;
+ oaepLabel?: NodeJS.TypedArray | undefined;
+ padding?: number | undefined;
+ }
+ /**
+ * Encrypts the content of `buffer` with `key` and returns a new `Buffer` with encrypted content. The returned data can be decrypted using
+ * the corresponding private key, for example using {@link privateDecrypt}.
+ *
+ * If `key` is not a `KeyObject`, this function behaves as if `key` had been passed to {@link createPublicKey}. If it is an
+ * object, the `padding` property can be passed. Otherwise, this function uses `RSA_PKCS1_OAEP_PADDING`.
+ *
+ * Because RSA public keys can be derived from private keys, a private key may
+ * be passed instead of a public key.
+ * @since v0.11.14
+ */
+ function publicEncrypt(
+ key: RsaPublicKey | RsaPrivateKey | KeyLike,
+ buffer: NodeJS.ArrayBufferView | string,
+ ): NonSharedBuffer;
+ /**
+ * Decrypts `buffer` with `key`.`buffer` was previously encrypted using
+ * the corresponding private key, for example using {@link privateEncrypt}.
+ *
+ * If `key` is not a `KeyObject`, this function behaves as if `key` had been passed to {@link createPublicKey}. If it is an
+ * object, the `padding` property can be passed. Otherwise, this function uses `RSA_PKCS1_PADDING`.
+ *
+ * Because RSA public keys can be derived from private keys, a private key may
+ * be passed instead of a public key.
+ * @since v1.1.0
+ */
+ function publicDecrypt(
+ key: RsaPublicKey | RsaPrivateKey | KeyLike,
+ buffer: NodeJS.ArrayBufferView | string,
+ ): NonSharedBuffer;
+ /**
+ * Decrypts `buffer` with `privateKey`. `buffer` was previously encrypted using
+ * the corresponding public key, for example using {@link publicEncrypt}.
+ *
+ * If `privateKey` is not a `KeyObject`, this function behaves as if `privateKey` had been passed to {@link createPrivateKey}. If it is an
+ * object, the `padding` property can be passed. Otherwise, this function uses `RSA_PKCS1_OAEP_PADDING`.
+ * @since v0.11.14
+ */
+ function privateDecrypt(
+ privateKey: RsaPrivateKey | KeyLike,
+ buffer: NodeJS.ArrayBufferView | string,
+ ): NonSharedBuffer;
+ /**
+ * Encrypts `buffer` with `privateKey`. The returned data can be decrypted using
+ * the corresponding public key, for example using {@link publicDecrypt}.
+ *
+ * If `privateKey` is not a `KeyObject`, this function behaves as if `privateKey` had been passed to {@link createPrivateKey}. If it is an
+ * object, the `padding` property can be passed. Otherwise, this function uses `RSA_PKCS1_PADDING`.
+ * @since v1.1.0
+ */
+ function privateEncrypt(
+ privateKey: RsaPrivateKey | KeyLike,
+ buffer: NodeJS.ArrayBufferView | string,
+ ): NonSharedBuffer;
+ /**
+ * ```js
+ * const {
+ * getCiphers,
+ * } = await import('node:crypto');
+ *
+ * console.log(getCiphers()); // ['aes-128-cbc', 'aes-128-ccm', ...]
+ * ```
+ * @since v0.9.3
+ * @return An array with the names of the supported cipher algorithms.
+ */
+ function getCiphers(): string[];
+ /**
+ * ```js
+ * const {
+ * getCurves,
+ * } = await import('node:crypto');
+ *
+ * console.log(getCurves()); // ['Oakley-EC2N-3', 'Oakley-EC2N-4', ...]
+ * ```
+ * @since v2.3.0
+ * @return An array with the names of the supported elliptic curves.
+ */
+ function getCurves(): string[];
+ /**
+ * @since v10.0.0
+ * @return `1` if and only if a FIPS compliant crypto provider is currently in use, `0` otherwise. A future semver-major release may change the return type of this API to a {boolean}.
+ */
+ function getFips(): 1 | 0;
+ /**
+ * Enables the FIPS compliant crypto provider in a FIPS-enabled Node.js build.
+ * Throws an error if FIPS mode is not available.
+ * @since v10.0.0
+ * @param bool `true` to enable FIPS mode.
+ */
+ function setFips(bool: boolean): void;
+ /**
+ * ```js
+ * const {
+ * getHashes,
+ * } = await import('node:crypto');
+ *
+ * console.log(getHashes()); // ['DSA', 'DSA-SHA', 'DSA-SHA1', ...]
+ * ```
+ * @since v0.9.3
+ * @return An array of the names of the supported hash algorithms, such as `'RSA-SHA256'`. Hash algorithms are also called "digest" algorithms.
+ */
+ function getHashes(): string[];
+ /**
+ * The `ECDH` class is a utility for creating Elliptic Curve Diffie-Hellman (ECDH)
+ * key exchanges.
+ *
+ * Instances of the `ECDH` class can be created using the {@link createECDH} function.
+ *
+ * ```js
+ * import assert from 'node:assert';
+ *
+ * const {
+ * createECDH,
+ * } = await import('node:crypto');
+ *
+ * // Generate Alice's keys...
+ * const alice = createECDH('secp521r1');
+ * const aliceKey = alice.generateKeys();
+ *
+ * // Generate Bob's keys...
+ * const bob = createECDH('secp521r1');
+ * const bobKey = bob.generateKeys();
+ *
+ * // Exchange and generate the secret...
+ * const aliceSecret = alice.computeSecret(bobKey);
+ * const bobSecret = bob.computeSecret(aliceKey);
+ *
+ * assert.strictEqual(aliceSecret.toString('hex'), bobSecret.toString('hex'));
+ * // OK
+ * ```
+ * @since v0.11.14
+ */
+ class ECDH {
+ private constructor();
+ /**
+ * Converts the EC Diffie-Hellman public key specified by `key` and `curve` to the
+ * format specified by `format`. The `format` argument specifies point encoding
+ * and can be `'compressed'`, `'uncompressed'` or `'hybrid'`. The supplied key is
+ * interpreted using the specified `inputEncoding`, and the returned key is encoded
+ * using the specified `outputEncoding`.
+ *
+ * Use {@link getCurves} to obtain a list of available curve names.
+ * On recent OpenSSL releases, `openssl ecparam -list_curves` will also display
+ * the name and description of each available elliptic curve.
+ *
+ * If `format` is not specified the point will be returned in `'uncompressed'` format.
+ *
+ * If the `inputEncoding` is not provided, `key` is expected to be a `Buffer`, `TypedArray`, or `DataView`.
+ *
+ * Example (uncompressing a key):
+ *
+ * ```js
+ * const {
+ * createECDH,
+ * ECDH,
+ * } = await import('node:crypto');
+ *
+ * const ecdh = createECDH('secp256k1');
+ * ecdh.generateKeys();
+ *
+ * const compressedKey = ecdh.getPublicKey('hex', 'compressed');
+ *
+ * const uncompressedKey = ECDH.convertKey(compressedKey,
+ * 'secp256k1',
+ * 'hex',
+ * 'hex',
+ * 'uncompressed');
+ *
+ * // The converted key and the uncompressed public key should be the same
+ * console.log(uncompressedKey === ecdh.getPublicKey('hex'));
+ * ```
+ * @since v10.0.0
+ * @param inputEncoding The `encoding` of the `key` string.
+ * @param outputEncoding The `encoding` of the return value.
+ * @param [format='uncompressed']
+ */
+ static convertKey(
+ key: BinaryLike,
+ curve: string,
+ inputEncoding?: BinaryToTextEncoding,
+ outputEncoding?: "latin1" | "hex" | "base64" | "base64url",
+ format?: "uncompressed" | "compressed" | "hybrid",
+ ): NonSharedBuffer | string;
+ /**
+ * Generates private and public EC Diffie-Hellman key values, and returns
+ * the public key in the specified `format` and `encoding`. This key should be
+ * transferred to the other party.
+ *
+ * The `format` argument specifies point encoding and can be `'compressed'` or `'uncompressed'`. If `format` is not specified, the point will be returned in`'uncompressed'` format.
+ *
+ * If `encoding` is provided a string is returned; otherwise a `Buffer` is returned.
+ * @since v0.11.14
+ * @param encoding The `encoding` of the return value.
+ * @param [format='uncompressed']
+ */
+ generateKeys(): NonSharedBuffer;
+ generateKeys(encoding: BinaryToTextEncoding, format?: ECDHKeyFormat): string;
+ /**
+ * Computes the shared secret using `otherPublicKey` as the other
+ * party's public key and returns the computed shared secret. The supplied
+ * key is interpreted using specified `inputEncoding`, and the returned secret
+ * is encoded using the specified `outputEncoding`.
+ * If the `inputEncoding` is not
+ * provided, `otherPublicKey` is expected to be a `Buffer`, `TypedArray`, or `DataView`.
+ *
+ * If `outputEncoding` is given a string will be returned; otherwise a `Buffer` is returned.
+ *
+ * `ecdh.computeSecret` will throw an`ERR_CRYPTO_ECDH_INVALID_PUBLIC_KEY` error when `otherPublicKey` lies outside of the elliptic curve. Since `otherPublicKey` is
+ * usually supplied from a remote user over an insecure network,
+ * be sure to handle this exception accordingly.
+ * @since v0.11.14
+ * @param inputEncoding The `encoding` of the `otherPublicKey` string.
+ * @param outputEncoding The `encoding` of the return value.
+ */
+ computeSecret(otherPublicKey: NodeJS.ArrayBufferView): NonSharedBuffer;
+ computeSecret(otherPublicKey: string, inputEncoding: BinaryToTextEncoding): NonSharedBuffer;
+ computeSecret(otherPublicKey: NodeJS.ArrayBufferView, outputEncoding: BinaryToTextEncoding): string;
+ computeSecret(
+ otherPublicKey: string,
+ inputEncoding: BinaryToTextEncoding,
+ outputEncoding: BinaryToTextEncoding,
+ ): string;
+ /**
+ * If `encoding` is specified, a string is returned; otherwise a `Buffer` is
+ * returned.
+ * @since v0.11.14
+ * @param encoding The `encoding` of the return value.
+ * @return The EC Diffie-Hellman in the specified `encoding`.
+ */
+ getPrivateKey(): NonSharedBuffer;
+ getPrivateKey(encoding: BinaryToTextEncoding): string;
+ /**
+ * The `format` argument specifies point encoding and can be `'compressed'` or `'uncompressed'`. If `format` is not specified the point will be returned in`'uncompressed'` format.
+ *
+ * If `encoding` is specified, a string is returned; otherwise a `Buffer` is
+ * returned.
+ * @since v0.11.14
+ * @param encoding The `encoding` of the return value.
+ * @param [format='uncompressed']
+ * @return The EC Diffie-Hellman public key in the specified `encoding` and `format`.
+ */
+ getPublicKey(encoding?: null, format?: ECDHKeyFormat): NonSharedBuffer;
+ getPublicKey(encoding: BinaryToTextEncoding, format?: ECDHKeyFormat): string;
+ /**
+ * Sets the EC Diffie-Hellman private key.
+ * If `encoding` is provided, `privateKey` is expected
+ * to be a string; otherwise `privateKey` is expected to be a `Buffer`, `TypedArray`, or `DataView`.
+ *
+ * If `privateKey` is not valid for the curve specified when the `ECDH` object was
+ * created, an error is thrown. Upon setting the private key, the associated
+ * public point (key) is also generated and set in the `ECDH` object.
+ * @since v0.11.14
+ * @param encoding The `encoding` of the `privateKey` string.
+ */
+ setPrivateKey(privateKey: NodeJS.ArrayBufferView): void;
+ setPrivateKey(privateKey: string, encoding: BinaryToTextEncoding): void;
+ }
+ /**
+ * Creates an Elliptic Curve Diffie-Hellman (`ECDH`) key exchange object using a
+ * predefined curve specified by the `curveName` string. Use {@link getCurves} to obtain a list of available curve names. On recent
+ * OpenSSL releases, `openssl ecparam -list_curves` will also display the name
+ * and description of each available elliptic curve.
+ * @since v0.11.14
+ */
+ function createECDH(curveName: string): ECDH;
+ /**
+ * This function compares the underlying bytes that represent the given `ArrayBuffer`, `TypedArray`, or `DataView` instances using a constant-time
+ * algorithm.
+ *
+ * This function does not leak timing information that
+ * would allow an attacker to guess one of the values. This is suitable for
+ * comparing HMAC digests or secret values like authentication cookies or [capability urls](https://www.w3.org/TR/capability-urls/).
+ *
+ * `a` and `b` must both be `Buffer`s, `TypedArray`s, or `DataView`s, and they
+ * must have the same byte length. An error is thrown if `a` and `b` have
+ * different byte lengths.
+ *
+ * If at least one of `a` and `b` is a `TypedArray` with more than one byte per
+ * entry, such as `Uint16Array`, the result will be computed using the platform
+ * byte order.
+ *
+ * **When both of the inputs are `Float32Array`s or `Float64Array`s, this function might return unexpected results due to IEEE 754**
+ * **encoding of floating-point numbers. In particular, neither `x === y` nor `Object.is(x, y)` implies that the byte representations of two floating-point**
+ * **numbers `x` and `y` are equal.**
+ *
+ * Use of `crypto.timingSafeEqual` does not guarantee that the _surrounding_ code
+ * is timing-safe. Care should be taken to ensure that the surrounding code does
+ * not introduce timing vulnerabilities.
+ * @since v6.6.0
+ */
+ function timingSafeEqual(a: NodeJS.ArrayBufferView, b: NodeJS.ArrayBufferView): boolean;
+ interface DHKeyPairOptions extends KeyPairExportOptions<"spki", "pkcs8"> {
+ /**
+ * The prime parameter
+ */
+ prime?: Buffer | undefined;
+ /**
+ * Prime length in bits
+ */
+ primeLength?: number | undefined;
+ /**
+ * Custom generator
+ * @default 2
+ */
+ generator?: number | undefined;
+ /**
+ * Diffie-Hellman group name
+ * @see {@link getDiffieHellman}
+ */
+ groupName?: string | undefined;
+ }
+ interface DSAKeyPairOptions extends KeyPairExportOptions<"spki", "pkcs8"> {
+ /**
+ * Key size in bits
+ */
+ modulusLength: number;
+ /**
+ * Size of q in bits
+ */
+ divisorLength: number;
+ }
+ interface ECKeyPairOptions extends KeyPairExportOptions<"spki", "pkcs8" | "sec1"> {
+ /**
+ * Name of the curve to use
+ */
+ namedCurve: string;
+ /**
+ * Must be `'named'` or `'explicit'`
+ * @default 'named'
+ */
+ paramEncoding?: "explicit" | "named" | undefined;
+ }
+ interface ED25519KeyPairOptions extends KeyPairExportOptions<"spki", "pkcs8"> {}
+ interface ED448KeyPairOptions extends KeyPairExportOptions<"spki", "pkcs8"> {}
+ interface MLDSAKeyPairOptions extends KeyPairExportOptions<"spki", "pkcs8"> {}
+ interface MLKEMKeyPairOptions extends KeyPairExportOptions<"spki", "pkcs8"> {}
+ interface RSAPSSKeyPairOptions extends KeyPairExportOptions<"spki", "pkcs8"> {
+ /**
+ * Key size in bits
+ */
+ modulusLength: number;
+ /**
+ * Public exponent
+ * @default 0x10001
+ */
+ publicExponent?: number | undefined;
+ /**
+ * Name of the message digest
+ */
+ hashAlgorithm?: string | undefined;
+ /**
+ * Name of the message digest used by MGF1
+ */
+ mgf1HashAlgorithm?: string | undefined;
+ /**
+ * Minimal salt length in bytes
+ */
+ saltLength?: string | undefined;
+ }
+ interface RSAKeyPairOptions extends KeyPairExportOptions<"pkcs1" | "spki", "pkcs1" | "pkcs8"> {
+ /**
+ * Key size in bits
+ */
+ modulusLength: number;
+ /**
+ * Public exponent
+ * @default 0x10001
+ */
+ publicExponent?: number | undefined;
+ }
+ interface SLHDSAKeyPairOptions extends KeyPairExportOptions<"spki", "pkcs8"> {}
+ interface X25519KeyPairOptions extends KeyPairExportOptions<"spki", "pkcs8"> {}
+ interface X448KeyPairOptions extends KeyPairExportOptions<"spki", "pkcs8"> {}
+ /**
+ * Generates a new asymmetric key pair of the given `type`. See the
+ * supported [asymmetric key types](https://nodejs.org/docs/latest-v25.x/api/crypto.html#asymmetric-key-types).
+ *
+ * If a `publicKeyEncoding` or `privateKeyEncoding` was specified, this function
+ * behaves as if `keyObject.export()` had been called on its result. Otherwise,
+ * the respective part of the key is returned as a `KeyObject`.
+ *
+ * When encoding public keys, it is recommended to use `'spki'`. When encoding
+ * private keys, it is recommended to use `'pkcs8'` with a strong passphrase,
+ * and to keep the passphrase confidential.
+ *
+ * ```js
+ * const {
+ * generateKeyPairSync,
+ * } = await import('node:crypto');
+ *
+ * const {
+ * publicKey,
+ * privateKey,
+ * } = generateKeyPairSync('rsa', {
+ * modulusLength: 4096,
+ * publicKeyEncoding: {
+ * type: 'spki',
+ * format: 'pem',
+ * },
+ * privateKeyEncoding: {
+ * type: 'pkcs8',
+ * format: 'pem',
+ * cipher: 'aes-256-cbc',
+ * passphrase: 'top secret',
+ * },
+ * });
+ * ```
+ *
+ * The return value `{ publicKey, privateKey }` represents the generated key pair.
+ * When PEM encoding was selected, the respective key will be a string, otherwise
+ * it will be a buffer containing the data encoded as DER.
+ * @since v10.12.0
+ * @param type The asymmetric key type to generate. See the
+ * supported [asymmetric key types](https://nodejs.org/docs/latest-v25.x/api/crypto.html#asymmetric-key-types).
+ */
+ function generateKeyPairSync(
+ type: "dh",
+ options: T,
+ ): KeyPairExportResult;
+ function generateKeyPairSync(
+ type: "dsa",
+ options: T,
+ ): KeyPairExportResult;
+ function generateKeyPairSync(
+ type: "ec",
+ options: T,
+ ): KeyPairExportResult;
+ function generateKeyPairSync(
+ type: "ed25519",
+ options?: T,
+ ): KeyPairExportResult;
+ function generateKeyPairSync(
+ type: "ed448",
+ options?: T,
+ ): KeyPairExportResult;
+ function generateKeyPairSync(
+ type: MLDSAKeyType,
+ options?: T,
+ ): KeyPairExportResult;
+ function generateKeyPairSync(
+ type: MLKEMKeyType,
+ options?: T,
+ ): KeyPairExportResult;
+ function generateKeyPairSync(
+ type: "rsa-pss",
+ options: T,
+ ): KeyPairExportResult;
+ function generateKeyPairSync(
+ type: "rsa",
+ options: T,
+ ): KeyPairExportResult;
+ function generateKeyPairSync(
+ type: SLHDSAKeyType,
+ options?: T,
+ ): KeyPairExportResult;
+ function generateKeyPairSync(
+ type: "x25519",
+ options?: T,
+ ): KeyPairExportResult;
+ function generateKeyPairSync(
+ type: "x448",
+ options?: T,
+ ): KeyPairExportResult;
+ /**
+ * Generates a new asymmetric key pair of the given `type`. See the
+ * supported [asymmetric key types](https://nodejs.org/docs/latest-v25.x/api/crypto.html#asymmetric-key-types).
+ *
+ * If a `publicKeyEncoding` or `privateKeyEncoding` was specified, this function
+ * behaves as if `keyObject.export()` had been called on its result. Otherwise,
+ * the respective part of the key is returned as a `KeyObject`.
+ *
+ * It is recommended to encode public keys as `'spki'` and private keys as `'pkcs8'` with encryption for long-term storage:
+ *
+ * ```js
+ * const {
+ * generateKeyPair,
+ * } = await import('node:crypto');
+ *
+ * generateKeyPair('rsa', {
+ * modulusLength: 4096,
+ * publicKeyEncoding: {
+ * type: 'spki',
+ * format: 'pem',
+ * },
+ * privateKeyEncoding: {
+ * type: 'pkcs8',
+ * format: 'pem',
+ * cipher: 'aes-256-cbc',
+ * passphrase: 'top secret',
+ * },
+ * }, (err, publicKey, privateKey) => {
+ * // Handle errors and use the generated key pair.
+ * });
+ * ```
+ *
+ * On completion, `callback` will be called with `err` set to `undefined` and `publicKey` / `privateKey` representing the generated key pair.
+ *
+ * If this method is invoked as its `util.promisify()` ed version, it returns
+ * a `Promise` for an `Object` with `publicKey` and `privateKey` properties.
+ * @since v10.12.0
+ * @param type The asymmetric key type to generate. See the
+ * supported [asymmetric key types](https://nodejs.org/docs/latest-v25.x/api/crypto.html#asymmetric-key-types).
+ */
+ function generateKeyPair