Merge remote-tracking branch 'github/devlop'

This commit is contained in:
卡若
2026-03-09 05:54:50 +08:00
476 changed files with 98088 additions and 0 deletions

127
.cursor/README.md Normal file
View File

@@ -0,0 +1,127 @@
# Soul 创业派对 - .cursor 配置说明
本目录按 **cursor标准模板** 重构rules、skills、agent 为**开发团队**服务,用于约束开发、防止互窜、经验升级。
---
## 目录结构
```
.cursor/
├── README.md # 本说明(入口)
├── config/ # 配置paths.py、workspace.txt、model_switch.json
├── rules/ # 规则boundary、checklist、助理、会议、老板分身-索引)
├── skills/ # Skills按角色分配
├── agent/ # 智能体(标准开发团队结构)
│ ├── 老板分身/ # 最高权限,协调所有角色
│ ├── 开发助理/ # 规则进化、通用经验、项目索引、bat 入口
│ │ ├── evolution/ # 通用经验池
│ │ ├── script/ # 一键-列出经验池.bat、一键-添加经验.bat 等
│ │ └── 项目索引/ # 各角色开发进度(小程序.md、管理端.md 等)
│ ├── 小程序开发工程师/
│ ├── 管理端开发工程师/
│ ├── 后端工程师/
│ ├── 产品经理/
│ ├── 软件测试/
│ └── 团队/ # 跨角色共享经验
├── scripts/ # 共享脚本evolution.py、经验模板.md、db-exec
├── docs/ # 文档(职责定义、边界、分析)
├── process/ # 工作流
├── meeting/ # 会议纪要(橙子生成)
└── archive/ # 历史归档
```
---
## 开发团队
| 角色 | 负责 | 主 Skill | Agent 目录 |
|------|------|----------|------------|
| 小程序开发工程师 | miniprogram/ | SKILL-小程序开发.md | agent/小程序开发工程师/ |
| 管理端开发工程师 | soul-admin/ | SKILL-管理端开发.md | agent/管理端开发工程师/ |
| 后端开发 | soul-api/ | SKILL-API开发.md | agent/后端工程师/ |
| 产品经理 | 开发文档/1、需求/、临时需求池/ | SKILL-产品经理.md | agent/产品经理/ |
| 测试人员 | miniprogram、soul-admin、soul-api | SKILL-测试.md | agent/软件测试/ |
| 助理橙子 | 讨论后记录、经验升级 | SKILL-助理橙子-文档同步.md | agent/开发助理/ |
**经验**:每角色 `agent/{角色}/evolution/`,团队共享 `agent/团队/evolution/`。用户说「吸收经验」「升级 skills」→ 入库 + 升级 Skill说「保存开发进度」「任务完成」→ 更新 `agent/开发助理/项目索引/{角色}.md`
---
## 快速决策(必须 Read = 使用 Read 工具读取完整内容)
| 编辑/场景 | 必须 Read 的 Skill | 自动加载的 Rule |
|-----------|-------------------|----------------|
| miniprogram/ | `SKILL-小程序开发.md` | soul-miniprogram-boundary |
| soul-admin/ | `SKILL-管理端开发.md` | soul-admin-boundary |
| soul-api/ | `SKILL-API开发.md` | soul-api |
| 开发文档/1、需求/、临时需求池/ | `SKILL-产品经理.md` | product-manager |
| 测试、测试用例、回归测试、功能测试、QA | `SKILL-测试.md` | - |
| 小橙、橙子、讨论完毕、记录、同步文档 | `SKILL-助理橙子-文档同步.md` | assistant-xiaofeng |
| 吸收经验、升级 skills、保存开发进度、任务完成、搞定了 | `SKILL-助理橙子-文档同步.md` | assistant-xiaofeng |
| 跨端功能开发 | `SKILL-角色流程控制.md` | - |
| 变更完成 | `SKILL-变更关联检查.md` | soul-change-checklist |
| 开个会、团队会议、需求评审、方案讨论 | `SKILL-团队会议.md` | soul-meeting |
| 会议结束、散会 | `SKILL-助理橙子-文档同步.md`(会议收尾) | soul-meeting |
---
## Rules 一览
| 规则 | 生效范围 | 用途 |
|------|----------|------|
| soul-project-boundary | `**`alwaysApply | 项目组成、核心原则、会话自检 |
| 老板分身-索引 | `**`alwaysApply | 经验自动收集、Soul 角色推断、编码习惯 |
| soul-change-checklist | miniprogram、soul-admin、soul-api | 变更后必过 |
| assistant-xiaofeng | 触发词 | 小橙触发器 → SKILL-助理橙子-文档同步 |
| soul-miniprogram-boundary | miniprogram/** | 只调 /api/miniprogram/* |
| soul-admin-boundary | soul-admin/** | 只调 /api/admin/*、/api/db/* |
| soul-api | soul-api/** | 路由边界 + 编码规范(合并版) |
| product-manager | 开发文档/1、需求/、临时需求池/ | 产品经理 glob 触发 |
| soul-meeting | 触发词 | 开个会、团队会议、需求评审 → SKILL-团队会议 |
---
## 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-助理橙子-文档同步 |
| **多角色会议** | **SKILL-团队会议** |
| next-project | SKILL-next-project仅预览 |
| 项目拆解 | SKILL-Next全栈拆解为前后端分离与小程序 |
---
## 文档与脚本
| 文档 | 说明 |
|------|------|
| [开发团队职责定义](./docs/开发团队职责定义.md) | 六角色职责、Skills 分配 |
| [三角色边界定义](./docs/三角色边界定义.md) | 开发三角色源码与业务边界 |
| [config/目录地图](./config/目录地图.md) | paths.py 路径别名 |
| [meeting/](./meeting/) | 会议纪要(橙子生成) |
| [经验清单](./agent/开发助理/经验清单.md) | 跨角色经验索引 |
| evolution 脚本 | `python .cursor/scripts/evolution.py list` 列出经验池;`add --stdin` 添加经验 |
| 一键 bat | `agent/开发助理/script/一键-列出经验池.bat` 等 |
---
## 会话启动自检
新 Cursor 打开本项目时,优先执行 soul-project-boundary 中的「会话启动自检」:仅沿用本项目的 rules、skills、开发风格与配置排除无关规则。

View File

@@ -0,0 +1,5 @@
# 2026-02-26 | 产品经验
> 本日经验条目,格式:类型 | 摘要 | 升级 Skill
---

View File

@@ -0,0 +1,19 @@
# 产品经理 经验记录 - 2026-02-28
## stitch_soul 需求评审会议
- **stitch_soul 串联「内容→会员→导师」变现路径**:临时需求池 10 个稿子覆盖目录、导师、会员、首页、资料编辑,需在正式需求文档中明确 73 章、导师、案例库、会员的业务定义与验收标准。
- **待澄清项**73 章与现有内容库是否同一套;导师与内容作者是否同一人;「案例库」是独立内容池还是章节分类;会员权益与价格策略。
- **优先级建议**:首页/目录/会员 > 导师列表/详情 > 资料编辑。
## 个人资料页实现评估会议
- **展示/编辑页一致性**展示页enhanced_professional_profile与编辑页comprehensive_profile_editor_v1_1需字段一一对应、配色统一。
- **skills「我擅长」**:展示页已有,编辑页必须补充;验收时确认两页数据一致。
## 文章类型(普通版/增值版)需求分析会议
- **普通版**9.9 元买断,与现有 fullbook 一致。
- **增值版**:基础价 + 后 N 章N 可配置)按章额外付费;每购一章价格累加该章单价。
- **已确认**:普通版与增值版**分开、互斥**,用户购买其一,非叠加。
- **待输出**:增值版基础价是否固定 9.9N 默认值。

View File

@@ -0,0 +1,12 @@
# 产品经理 经验记录 - 2026-03-05
## 分支冲突后功能完整性分析会议
- **分支冲突后需优先核对需求文档与实现一致性**:避免「文档在合并中丢失」导致需求与实现脱节。重点核对 `开发文档/1、需求/``临时需求池/` 与当前实现是否一致。
- **重点确认项**:个人资料页、增值版/普通版计价、找伙伴联系方式完善弹窗等近期需求是否按文档落地。
## 文章详情 @某人 高亮与一键加好友方案讨论
- **需求**:文章中支持 @某人,名称高亮、点击添加好友;编辑侧能插入 @用户并落库(含用户 id
- **验收**:详情页 @ 高亮可点击;点击调起添加好友并提示;管理端插入 @ 后保存再编辑不错位。
- **待确认**:添加好友接口 path、入参/出参及业务规则(已是好友、重复请求等);开发文档中接口放置位置。

View File

@@ -0,0 +1,6 @@
# 产品经理 经验索引
| 日期 | 摘要 | 文件 |
|------|------|------|
| 2026-03-05 | 分支冲突后需求文档与实现一致性核对 | [2026-03-05.md](./2026-03-05.md) |
| 2026-03-05 | 文章详情@某人高亮与一键加好友验收标准与待确认 | [2026-03-05.md](./2026-03-05.md) |

View File

@@ -0,0 +1,5 @@
# 2026-02-26 | 后端经验
> 本日经验条目,格式:类型 | 摘要 | 升级 Skill
---

View File

@@ -0,0 +1,23 @@
# 后端开发 经验记录 - 2026-02-28
## stitch_soul 需求评审会议
- **现有基础**soul-api 已有 chapter、book、vip 模型;导师能力需新建或扩展现有 match 体系(现有 mentor 为 match 类型,非独立导师实体)。
- **待设计**:导师列表/详情/搜索筛选、预约单、会员权益与预约支付打通;接口挂 `/api/miniprogram/*`
- **协同**:与产品核对 chapter/book/vip 现状后,给出导师/预约/会员权益的模型与接口方案。
## chapter/book/vip 模型补充(问题 5 作答)
- **chapters 表**每行一节id 如 1.1/prefacepart/chapter/section 三层73 章=行数统计。
- **book**:无独立表,= chapters 聚合;接口 `/api/book/all-chapters``/api/book/chapter/:id`
- **vip**vip_roles 配置角色users 存 is_vip/vip_expire_date 等;权益优先 users无则 orders 兜底¥1980。
## 个人资料页实现评估会议
- **profile API**`GET/POST /api/miniprogram/user/profile` 已覆盖 skills 等全部扩展字段;无需新增接口。
## 文章类型(普通版/增值版)需求分析会议
- **premium_config**system_config 新增 premium_base_price、premium_chapter_count后 N 个 section
- **增值章节购买**:沿用 product_type=section、product_id=section_id金额为 chapters.price。
- **purchase-status 扩展**:需返回 editionType、premiumPurchasedSections 等。

View File

@@ -0,0 +1,13 @@
# 后端工程师 经验记录 - 2026-03-05
## 分支冲突后功能完整性分析会议
- **soul-api 被 gitignore 时**:需在 soul-api 所在位置单独确认 git 状态与合并情况,无法从本仓库直接检查。
- **重点核对接口**`/api/miniprogram/orders`(购买记录)、`/api/db/distribution`(分销)是否已实现并挂载到对应路由组。
- **建议**:对照「三端需求业务对齐」文档逐项核对接口注册与实现;明确 soul-api 的版本管理与合并策略。
## 文章详情 @某人 高亮与一键加好友方案讨论
- **内容存储**:推荐正文内嵌 @ 标记,格式 `@[昵称](userId)`(或 `{{@userId:昵称}}`),后端只存不解析;章节/文章接口原样返回 content。
- **添加好友接口**:在 miniprogram 路由组新增,如 `POST /api/miniprogram/friend/add`,入参 `targetUserId`;与存客宝 api_v1 分离,在开发文档中单独说明或 api_v1 新增小节。
- **数据模型**:无需单独 mention 表,扩展内容表 content 存带 @ 标记字符串即可。

View File

@@ -0,0 +1,6 @@
# 后端工程师 经验索引
| 日期 | 摘要 | 文件 |
|------|------|------|
| 2026-03-05 | soul-api 合并状态确认orders、distribution 接口核对 | [2026-03-05.md](./2026-03-05.md) |
| 2026-03-05 | 文章详情@某人content 内嵌 @ 标记、miniprogram 添加好友接口 | [2026-03-05.md](./2026-03-05.md) |

View File

View File

@@ -0,0 +1,10 @@
# 2026-02-27 | 团队经验
> 本日跨角色共享的架构决策、业务规则。格式:类型 | 摘要 | 关联 Skill
---
## 输入框样式(前端通用)
- **最佳实践 | 输入框 padding**:设置 input 的 padding/背景/边框时,用 div 或 view 包裹 inputpadding 写在容器上input 仅做文字样式,可避免光标截断、布局异常。
- **适用**小程序、管理端React input 同理)

View File

@@ -0,0 +1,21 @@
# 团队共享 经验记录 - 2026-02-28
## stitch_soul 需求评审会议
- **stitch_soul 定位** stitch 产品线在 Soul 创业派对上的扩展,串联「内容阅读 + 导师咨询」变现路径。
- **架构协同**:需与现有 soul-api/soul-admin/miniprogram 架构协同;严禁混用 admin/miniprogram 路由;接口按使用方挂载。
- **开发顺序**:产品补充需求文档 → 后端给出模型与接口方案 → 管理端/小程序按优先级分阶段实现。
## 会议规则升级
- **问题与作答区**:开完会后必须将待确认/待澄清问题列出,在会议纪要中增加「问题与作答区」节,问题表含:序号、问题、责任角色、作答(留空供后续填写);便于追溯闭环。
## 个人资料页实现评估会议
- **展示/编辑页协同**profile-show 与 profile-edit 共用同一 APIskills 等扩展字段需双向同步;配色统一为 enhanced#5EEAD4)强化品牌一致。
## 文章类型(普通版/增值版)需求分析会议
- **增值版计价**:基础价 + Σ(增值章节单价),按章购买、按章累加。
- **后 N 章**:指全书最后 N 个 sectionN 可配置。
- **普通版与增值版**:分开、互斥的两套产品,用户购买其一。

View File

@@ -0,0 +1,17 @@
# 团队 经验记录 - 2026-03-05
## 分支冲突后功能完整性分析会议(跨角色共享)
- **分支冲突后各端需做完整性自查**
- 产品:核对需求文档与实现一致性
- 后端:在 soul-api 确认合并状态,核对 orders、distribution 等接口
- 管理端:全功能自测,记录 404/异常接口
- 小程序:核心流程自测,确认 orders 接口;修正 app.json 格式
- 测试:制定「分支合并后回归清单」
- **待确认**`完整的` 分支上的提交是否已全部并入 devlopsoul-api 的版本管理与合并策略。
## 文章详情 @某人 高亮与一键加好友方案讨论(跨角色共享)
- **内容格式**:正文内嵌 @ 标记,统一格式 `@[昵称](userId)`(或约定等价格式),后端/管理端/小程序共用。
- **添加好友接口**:归属 soul-api 的 miniprogram 组,与存客宝 api_v1 分离path、入参由后端/产品确认后写入开发文档。
- **分工**:管理端编辑侧插入 @ 并写入 content小程序解析、高亮、点击调添加好友后端提供章节 content 与添加好友接口。

View File

@@ -0,0 +1,7 @@
# 团队 经验索引(跨角色共享)
> 架构决策、业务规则、路由约定等跨角色共享内容。
| 日期 | 摘要 | 文件 |
|------|------|------|
| 2026-03-05 | 分支冲突后各端完整性自查流程 | [2026-03-05.md](./2026-03-05.md) |

View File

@@ -0,0 +1,5 @@
# 2026-02-26 | 小程序经验
> 本日经验条目,格式:类型 | 摘要 | 升级 Skill
---

View File

@@ -0,0 +1,19 @@
# 小程序开发工程师 经验记录 - 2026-02-27
---
## 输入框 padding 最佳实践
- **场景**:设置 input 的 padding、背景、边框等样式时直接作用于 input 可能导致光标被截断、布局异常。
- **方案**:用 `<view>` 包裹 `<input>`padding/背景/边框写在 view 上input 仅设置 width、font-size、color 等文字相关样式。
- **示例**
```xml
<view class="form-input">
<input placeholder="提示文字" value="{{value}}" bindinput="onInput" />
</view>
```
```css
.form-input { padding: 16rpx 24rpx; background: rgba(255,255,255,0.06); border: 1rpx solid rgba(255,255,255,0.1); border-radius: 12rpx; }
.form-input input { width: 100%; font-size: 28rpx; color: #fff; background: transparent; }
```
- **升级**:已写入 SKILL-小程序开发 §6 表单与输入框

View File

@@ -0,0 +1,31 @@
# 小程序开发工程师 经验记录 - 2026-02-28
## stitch_soul 需求评审会议
- **页面范围**:首页、目录、导师列表/详情、会员落地页、个人资料/编辑;全部接口走 `/api/miniprogram/*`
- **支付**:会员购买、导师预约支付需按微信支付规范实现。
- **时机**:待需求与接口确定后按优先级分阶段排期(建议:内容→会员→导师→资料编辑)。
## 个人资料页实现评估会议
- **profile-show**:已按 enhanced_professional_profile 完成accent #5EEAD4profile-edit 需与其视觉统一。
- **profile-edit 待做**:① 增加「我擅长」输入框skills 后端已支持);② 配色统一为 enhanced#5EEAD4 / #050B14 / #0F1720)。
- **流程**:我的 → profile-show → ⋯ 编辑 → profile-edit → 保存 → 返回;已打通,无需改。
## input/textarea padding 规范
- **原则**:给 input 或 textarea 设置 padding 时,必须用 view 包裹padding 写在 view 上;不在 input/textarea 自身上设 padding避免原生组件光标截断、布局异常。
- **口诀**:外边包 view内部 input width 100%。
- **已升级**miniprogram-dev SKILL §6 加入口诀admin-dev §4.1 同步补充。
## 找伙伴-资源对接弹窗 input 边距修正
- **问题**:弹窗内 input 文字贴边padding 直接写在 input 上导致布局异常。
- **正确做法**:按 Skill §6用 view 包裹padding 写 view内部 input 设 `width: 100%`
- **修改**match 页资源对接两个输入框 + 联系方式输入框,统一改为 `form-input-wrap` + `form-input-inner` / `input-field-wrap` + `input-field-inner` 结构。
## 文章类型(普通版/增值版)需求分析会议
- **选购**:目录/书籍页区分普通版 9.9 与增值版(基础价 + 增值章按章付费)。
- **解锁**:进入增值章节未购时展示「该章需额外 ¥X.X 解锁」,点击发起支付。
- **依赖**purchase-status 需返回 editionType、premiumPurchasedSections。

View File

@@ -0,0 +1,27 @@
# 2026-03-03 经验
## 我的页面卡片区边距优化
### 问题/场景
用户反馈「我的」页面的卡片区域左右边距过大,内容区在水平方向上显得较窄,未充分利用屏幕横向空间。
### 解决方案
将卡片区域及上下关联区块的左右边距统一缩小:
| 区块 | 修改前 | 修改后 |
|------|--------|--------|
| `.main-content` | 32rpx | 16rpx |
| `.header-block` | 40rpx | 16rpx |
| `.guest-block` | 48rpx | 16rpx |
| `.card` padding | 40rpx | 32rpx |
| `.card` margin-bottom | 32rpx | 24rpx |
### 提炼规则
个人中心、设置类页面(如「我的」)的卡片区左右边距宜紧凑,**推荐 16rpx**;卡片内边距 32rpx、卡片间距 24rpx以充分利用横向空间。
### 适用
- `miniprogram/pages/my/` 及类似个人中心、设置页

View File

@@ -0,0 +1,13 @@
# 小程序开发工程师 经验记录 - 2026-03-05
## 分支冲突后功能完整性分析会议
- **app.json 多页面配置**:建议拆行便于维护,避免多页面写在同一行(如第 21 行)。
- **分支合并后需做核心流程自测**登录→阅读→购买→提现→找伙伴→个人资料→VIP确认无白屏、无接口报错。
- **重点确认**`/api/miniprogram/orders`(购买记录)是否已实现;`完整的` 分支上的优化(如 5a5f0087 卡片边距)是否已并入 devlop。
## 文章详情 @某人 高亮与一键加好友方案讨论
- **展示**:阅读页 content 需解析「@ 标记」格式(如 `@[昵称](userId)`),将 @ 片段渲染为高亮可点击 `<text data-user-id>`,点击调 miniprogram 添加好友接口。
- **接口**:添加好友接口由后端在 miniprogram 组提供(如 `POST /api/miniprogram/friend/add`),入参 `targetUserId`;不调用 api_v1 存客宝接口。
- **实现要点**:在现有 contentParagraphs 按行渲染基础上,对每行再做 mention 解析,得到 text/mention 片段数组后 WXML 分支渲染;不改动现有阅读权限、进度、购买逻辑。

View File

@@ -0,0 +1,8 @@
# 小程序开发工程师 经验索引
| 日期 | 摘要 | 文件 |
|------|------|------|
| 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) |

View File

@@ -0,0 +1,12 @@
# 开发助理(橙子)
> 规则进化执行、通用经验池、项目索引。经验:`.cursor/agent/开发助理/evolution/`
## 目录结构
| 目录 | 说明 |
|------|------|
| evolution/ | 通用经验池 |
| archived/ | 已归档经验 |
| script/ | 规则进化 bat 入口 |
| 项目索引/ | 各角色开发进度索引 |

View File

@@ -0,0 +1,5 @@
# 2026-02-26 | 助理橙子经验
> 本日经验条目,格式:类型 | 摘要 | 升级 Skill
---

View File

@@ -0,0 +1,6 @@
# 开发助理 经验索引(通用)
> 无明确角色归属的通用经验。助理橙子执行时可按需参考。
| 日期 | 摘要 | 文件 |
|------|------|------|

View File

@@ -0,0 +1,46 @@
@echo off
chcp 65001 >nul
set PYTHONUTF8=1
set PYTHONIOENCODING=utf-8
cd /d "%~dp0..\..\..\.."
:menu
cls
echo ========================================
echo 开发助理 - 规则进化 一键操作
echo ========================================
echo.
echo 1. 添加经验(从 stdin 粘贴 JSON
echo 2. 列出经验池
echo 3. 执行进化(归档 + 可选应用新规则)
echo 4. 退出
echo.
set /p choice=请选择 (1-4):
if "%choice%"=="1" goto add
if "%choice%"=="2" goto list
if "%choice%"=="3" goto evolve
if "%choice%"=="4" exit
goto menu
:add
echo.
echo 请粘贴 AI 输出的 JSON按 Ctrl+Z 回车结束:
python .cursor\scripts\evolution.py add --stdin
pause
goto menu
:list
echo.
python .cursor\scripts\evolution.py list
pause
goto menu
:evolve
echo.
set /p RULE_FILE=新规则文件路径(留空则仅归档):
if "%RULE_FILE%"=="" (
python .cursor\scripts\evolution.py evolve --archive
) else (
python .cursor\scripts\evolution.py evolve --archive --rule "%RULE_FILE%"
)
pause
goto menu

View File

@@ -0,0 +1,6 @@
@echo off
chcp 65001 >nul
cd /d "%~dp0..\..\..\.."
echo 开发助理 - 列出经验池
python .cursor\scripts\evolution.py list
pause

View File

@@ -0,0 +1,6 @@
@echo off
chcp 65001 >nul
cd /d "%~dp0..\..\..\.."
echo 开发助理 - 执行进化(归档经验池)
python .cursor\scripts\evolution.py evolve --archive
pause

View File

@@ -0,0 +1,12 @@
@echo off
chcp 65001 >nul
set PYTHONUTF8=1
set PYTHONIOENCODING=utf-8
cd /d "%~dp0..\..\..\.."
echo 开发助理 - 添加经验
echo.
echo 请将 AI 输出的 JSON 粘贴到下方,按 Ctrl+Z 回车结束:
echo.
python .cursor\scripts\evolution.py add --stdin
echo.
pause

View File

@@ -0,0 +1,38 @@
# 开发团队经验清单
> 跨角色经验索引。经验在 `agent/*/evolution/`,项目索引在 `agent/开发助理/项目索引/`。
---
## 各角色经验库
| 角色 | 项目索引 | 按日经验 |
|------|----------|----------|
| 小程序 | [agent/开发助理/项目索引/小程序.md](./项目索引/小程序.md) | agent/小程序开发工程师/evolution/ |
| 管理端 | [agent/开发助理/项目索引/管理端.md](./项目索引/管理端.md) | agent/管理端开发工程师/evolution/ |
| 后端 | [agent/开发助理/项目索引/后端.md](./项目索引/后端.md) | agent/后端工程师/evolution/ |
| 产品 | [agent/开发助理/项目索引/产品.md](./项目索引/产品.md) | agent/产品经理/evolution/ |
| 测试 | [agent/开发助理/项目索引/测试.md](./项目索引/测试.md) | agent/软件测试/evolution/ |
| 助理橙子 | [agent/开发助理/项目索引/助理橙子.md](./项目索引/助理橙子.md) | agent/开发助理/evolution/ |
| 团队(跨角色共享) | [agent/开发助理/项目索引/团队.md](./项目索引/团队.md) | agent/团队/evolution/ |
---
## 索引表
| 日期 | 角色 | 类型 | 升级 Skill | 摘要 |
|------|------|------|------------|------|
| 2026-02-27 | 小程序、团队 | 最佳实践 | SKILL-小程序开发 §6、SKILL-管理端开发 §4.1 | 输入框 padding 用 view/div 包裹 |
| 2026-02-28 | 小程序、管理端 | 最佳实践 | miniprogram §6、admin §4.1 | input 边距口诀「外边包 view、内部 width 100%」match 弹窗已修正 |
| 2026-03-03 | 小程序 | 最佳实践 | miniprogram §8 | 我的页面卡片区边距 16rpx个人中心类页面布局规范 |
---
## 已吸收经验(历史)
- **SetVipModal**SKILL-管理端开发 4.1 表单弹窗
- **vip_roles**SKILL-API开发 3.2、SKILL-MySQL直接操作 8
---
**最后更新**2026-03-03

View File

@@ -0,0 +1,3 @@
# 项目索引
存放各角色子项目开发进度、状态。每角色对应一份索引。

View File

@@ -0,0 +1,27 @@
# 产品 - 项目索引
> 根据开发进度对项目做总结,保存开发进度,方便下次继续开发。**每次保存必须写日期**。
---
## 项目总结
Soul 创业派对产品定位:面向创业者的社区/工具型小程序。核心需求文档在 `开发文档/1、需求/需求汇总.md`,项目推进表在 `开发文档/10、项目管理/项目落地推进表.md`,临时需求/分析在 `临时需求池/`
---
## 开发进度
| 日期 | 摘要 | 状态 |
|------|------|------|
| 2026-02-26 | 项目索引初始化,.cursor 规则优化完成 | 已完成 |
| 2026-02-27 | 开发进度同步会议汇报进度待办「资料不解锁」补充、≥3 章弹窗明确 | 已完成 |
| 2026-02-28 | stitch_soul 需求评审:内容→会员→导师变现路径,待产品补充正式需求文档 | 待续 |
| 2026-03-05 | 分支冲突后功能完整性分析会议:核对需求文档与实现一致性 | 待续 |
| 2026-03-05 | 文章详情@某人加好友方案讨论:验收标准、添加好友接口 path 待确认 | 待续 |
> **格式说明**:每次开发后在此追加一行,日期格式 YYYY-MM-DD状态用已完成 / 进行中 / 待续 / 搁置
---
**最后更新**2026-03-05

View File

@@ -0,0 +1,24 @@
# 助理橙子 - 项目索引
> 根据开发进度对项目做总结,保存开发进度,方便下次继续开发。**每次保存必须写日期**。
---
## 项目总结
助理橙子负责开发团队文档同步与经验升级。触发词:小橙/橙子/橙橙/🍊。核心流程文件:`SKILL-助理橙子-文档同步.md`。经验结构agent/*/evolution 按角色 + 按日/按条存储 + 项目索引。
---
## 开发进度
| 日期 | 摘要 | 状态 |
|------|------|------|
| 2026-02-26 | 项目索引初始化经验库五角色目录结构搭建SKILL 补充角色映射表与跨端写入规则 | 已完成 |
| 2026-02-28 | .cursor 按 cursor标准模板 重构agent 目录、config、evolution.py、meeting | 已完成 |
> **格式说明**:每次开发后在此追加一行,日期格式 YYYY-MM-DD状态用已完成 / 进行中 / 待续 / 搁置
---
**最后更新**2026-02-28

View File

@@ -0,0 +1,28 @@
# 后端 - 项目索引
> 根据开发进度对项目做总结,保存开发进度,方便下次继续开发。**每次保存必须写日期**。
---
## 项目总结
soul-apiGo + Gin + GORM + MySQL提供三组路由`/api/miniprogram/*`(小程序)、`/api/admin/*` + `/api/db/*`(管理端)、`/api/payment/*`(支付回调)。微信生态通过 PowerWeChat 集成。当前核心模块用户、订单、分销、VIP、提现、章节、配置。
---
## 开发进度
| 日期 | 摘要 | 状态 |
|------|------|------|
| 2026-02-26 | 项目索引初始化,.cursor 规则优化完成 | 已完成 |
| 2026-02-27 | 开发进度汇报computeOrderCommission 会员分润差异化20%/10%已实现vip_roles、vip_activated_at、referral_config 扩展已完成miniprogram/admin/db 三组路由就绪 | 已完成 |
| 2026-02-27 | 开发进度同步会议:进度已同步至开发文档,待办资料完善校验 | 已完成 |
| 2026-02-28 | stitch_soul 需求评审:需梳理 chapter/book/vip设计导师/预约/会员权益模型与接口 | 待续 |
| 2026-03-05 | 分支冲突后功能完整性分析会议:在 soul-api 确认合并状态,核对 orders、distribution 接口 | 待续 |
| 2026-03-05 | 文章详情@某人加好友方案讨论content 内嵌 @ 标记、miniprogram 添加好友接口 | 待续 |
> **格式说明**:每次开发后在此追加一行,日期格式 YYYY-MM-DD状态用已完成 / 进行中 / 待续 / 搁置
---
**最后更新**2026-03-05

View File

@@ -0,0 +1,24 @@
# 团队 - 项目索引
> 跨角色共享的架构决策、业务规则、团队级经验。根据讨论进度更新,**每次保存必须写日期**。
---
## 项目总结
Soul 创业派对全项目架构与约定路由隔离miniprogram/admin/db、三端协同流程、经验按角色+按日存储、会议纪要独立存档、语义触发词驱动角色切换。
---
## 开发进度
| 日期 | 摘要 | 状态 |
|------|------|------|
| 2026-02-27 | 项目索引初始化;团队经验库目录建立 | 已完成 |
| 2026-02-28 | stitch_soul 需求评审:内容→会员→导师变现路径,需与现有三端架构协同 | 已完成 |
> **格式说明**:每次架构级讨论后在此追加一行,日期格式 YYYY-MM-DD
---
**最后更新**2026-02-28

View File

@@ -0,0 +1,31 @@
# 小程序 - 项目索引
> 根据开发进度对项目做总结,保存开发进度,方便下次继续开发。**每次保存必须写日期**。
---
## 项目总结
小程序微信原生C 端主要功能:用户注册/登录、VIP 购买、章节阅读、分销推荐、提现申请、找伙伴。当前已上线核心功能,持续迭代优化中。
---
## 开发进度
| 日期 | 摘要 | 状态 |
|------|------|------|
| 2026-02-26 | 项目索引初始化,.cursor 规则优化完成 | 已完成 |
| 2026-02-27 | 开发进度汇报:永平落地已完成(海报 scene、我的收益、推广中心、VIP 相关);找伙伴、提现、阅读、分销核心功能已上线 | 已完成 |
| 2026-02-27 | 开发进度同步会议进度已同步至开发文档待办资料完善弹窗、≥3 章弹窗 | 已完成 |
| 2026-02-27 | 吸收经验:输入框 padding 用 view 包裹,已升级 SKILL-小程序开发 §6 | 已完成 |
| 2026-02-28 | stitch_soul 需求评审:首页/目录/导师/会员/资料五类页面,待需求与接口确定后分阶段实现 | 待续 |
| 2026-02-28 | 吸收经验input 边距口诀「外边包 view、内部 width 100%」写入 Skill §6match 资源对接弹窗已按规范修正 | 已完成 |
| 2026-03-03 | 吸收经验我的页面卡片区边距优化16rpx 为个人中心类页面推荐值,已升级 SKILL §8 | 已完成 |
| 2026-03-05 | 分支冲突后功能完整性分析会议:修正 app.json 拆行、核心流程自测、确认 orders 接口 | 待续 |
| 2026-03-05 | 文章详情@某人加好友方案讨论:阅读页解析 @、高亮可点击、调添加好友接口 | 待续 |
> **格式说明**:每次开发后在此追加一行,日期格式 YYYY-MM-DD状态用已完成 / 进行中 / 待续 / 搁置
---
**最后更新**2026-03-05

View File

@@ -0,0 +1,25 @@
# 测试 - 项目索引
> 测试人员经验与测试进度。**每次保存必须写日期**。
---
## 项目总结
测试人员负责小程序、管理端、API 的功能测试、回归测试与三端联调。主 SkillSKILL-测试.md。
---
## 开发/测试进度
| 日期 | 进度摘要 | 状态 |
|------|----------|------|
| 2026-02-27 | 测试人员角色与 Skill 初始化 | 已完成 |
| 2026-02-27 | 小程序静态审查API 路径、页面接口、手工验证建议) | 已完成 |
| 2026-02-28 | stitch_soul 需求评审:关键场景为阅读/付费/会员/导师预约/资料;待需求确定后补充联调用例 | 待续 |
| 2026-03-05 | 分支冲突后功能完整性分析会议:制定「分支合并后回归清单」 | 待续 |
| 2026-03-05 | 文章详情@某人加好友方案讨论@ 展示与添加好友用例、联调与回归 | 待续 |
---
**最后更新**2026-03-05

View File

@@ -0,0 +1,28 @@
# 管理端 - 项目索引
> 根据开发进度对项目做总结,保存开发进度,方便下次继续开发。**每次保存必须写日期**。
---
## 项目总结
管理端React + Vite + Tailwind主要功能用户管理、订单管理、提现审核、VIP 管理、内容/章节管理、配置项管理、数据统计。调用 `/api/admin/*``/api/db/*` 接口JWT Bearer 鉴权。
---
## 开发进度
| 日期 | 摘要 | 状态 |
|------|------|------|
| 2026-02-26 | 项目索引初始化,.cursor 规则优化完成 | 已完成 |
| 2026-02-27 | 开发进度汇报:内容管理仅 API 按钮、推广中心、SetVipModal、VIP 角色管理、推广设置会员分润配置、VIP 排序等均已落地 | 已完成 |
| 2026-02-27 | 开发进度同步会议:进度已同步至运营与变更 | 已完成 |
| 2026-02-28 | stitch_soul 需求评审:待后端方案确定后规划章节/导师/会员/预约管理页面 | 待续 |
| 2026-03-05 | 分支冲突后功能完整性分析会议:全功能自测,记录 404/异常接口 | 待续 |
| 2026-03-05 | 文章详情@某人加好友方案讨论:编辑页插入 @用户、保存约定 content 格式 | 待续 |
> **格式说明**:每次开发后在此追加一行,日期格式 YYYY-MM-DD状态用已完成 / 进行中 / 待续 / 搁置
---
**最后更新**2026-03-05

View File

@@ -0,0 +1,5 @@
# 2026-02-26 | 管理端经验
> 本日经验条目,格式:类型 | 摘要 | 升级 Skill
---

View File

@@ -0,0 +1,16 @@
# 管理端开发工程师 经验记录 - 2026-02-28
## stitch_soul 需求评审会议
- **待支撑能力**:章节管理(增删改、排序、免费/付费/NEW、导师管理审核、标签、价格、展示、会员配置权益、价格、有效期、预约管理列表、状态
- **接口依赖**`/api/admin/*``/api/db/*`;字段需与 miniprogram 端统一。
- **时机**:待后端方案确定后规划管理端页面与接口对接。
## 个人资料页实现评估会议
- **无新增任务**:个人资料展示/编辑为 C 端能力,管理端沿用现有能力即可。
## 文章类型(普通版/增值版)需求分析会议
- **书籍版本配置**:支持选择普通版/增值版;配置「后 N 章」的 N。
- **章节标注**:章节列表需标注是否为增值章节;单价取自 chapters.price。

View File

@@ -0,0 +1,12 @@
# 管理端开发工程师 经验记录 - 2026-03-05
## 分支冲突后功能完整性分析会议
- **路由与页面结构完整时**:主要风险在接口可用性。全功能自测可快速暴露 404 或异常接口。
- **重点确认**`DistributionPage` 调用的 `GET /api/db/distribution` 是否已实现;`OrdersPage` 的订单接口路径是否与 soul-api 一致。
- **建议**分支合并后做一次管理端全流程自测登录→订单→提现→分销→VIP 角色→导师→配置),记录异常接口反馈后端。
## 文章详情 @某人 高亮与一键加好友方案讨论
- **编辑侧**:文章/章节编辑页增加「插入 @用户」,选择用户后插入到光标位置,保存时写入约定格式 content`@[昵称](userId)`);仅调 `/api/admin/*``/api/db/*`
- **一致性**:与小程序、后端共用同一 content 格式,避免多套标记;列表/预览可简单高亮或原样显示。

View File

@@ -0,0 +1,6 @@
# 管理端开发工程师 经验索引
| 日期 | 摘要 | 文件 |
|------|------|------|
| 2026-03-05 | 分支合并后全功能自测404/异常接口记录 | [2026-03-05.md](./2026-03-05.md) |
| 2026-03-05 | 文章详情@某人:编辑页插入 @用户、保存约定 content 格式 | [2026-03-05.md](./2026-03-05.md) |

View File

@@ -0,0 +1,4 @@
# 老板分身 经验索引
| 日期 | 摘要 | 文件 |
|------|------|------|

View File

@@ -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 路径隔离、规范遵从方面**通过静态审查**。建议在真机/模拟器中按上表手工验证核心流程。

View File

@@ -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=全书、章节无单价时的降级逻辑。

View File

@@ -0,0 +1,13 @@
# 软件测试 经验记录 - 2026-03-05
## 分支冲突后功能完整性分析会议
- **分支合并后应制定「回归清单」**覆盖三端联调关键路径登录、VIP、阅读、分销、提现、找伙伴、个人资料、导师、购买记录
- **soul-api 不在仓库时**:需与后端协作确认接口契约,无法在仓库内直接检查。
- **多分支合并**`完整的``soul-content``yongpxu-soul` 等分支合并结果需确认,各端自测 + 测试抽检。
## 文章详情 @某人 高亮与一键加好友方案讨论
- **用例**:无 @ 行为不变;有 @ 高亮且点击调起添加好友并提示;重复点击、未登录、无权限等边界;管理端插入 @ 后保存再编辑不错位。
- **联调**小程序↔章节接口content 含 @)、小程序↔添加好友接口;管理端↔内容保存与用户列表。
- **回归**:阅读页进度、购买、分享等不受 @ 功能影响。

View File

@@ -0,0 +1,6 @@
# 软件测试 经验索引
| 日期 | 摘要 | 文件 |
|------|------|------|
| 2026-03-05 | 分支合并后回归清单制定;三端联调验证 | [2026-03-05.md](./2026-03-05.md) |
| 2026-03-05 | 文章详情@某人@ 展示与添加好友用例、联调与回归点 | [2026-03-05.md](./2026-03-05.md) |

View File

@@ -0,0 +1,3 @@
# 历史归档
存放已归档的文档、分析报告等。

Binary file not shown.

View File

@@ -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"}
]
}

97
.cursor/config/paths.py Normal file
View File

@@ -0,0 +1,97 @@
#!/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"
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 = AGENT / "小程序开发工程师"
AGENT_ADMIN = AGENT / "管理端开发工程师"
AGENT_BACKEND = AGENT / "后端工程师"
AGENT_PRODUCT = AGENT / "产品经理"
AGENT_TEST = AGENT / "软件测试"
AGENT_TEAM = AGENT / "团队"
# ========== 常用文件 ==========
RULE_MAIN = RULES / "老板分身-索引.mdc"
LOG_EVOLUTION = SCRIPTS / "进化日志.md"
TEMPLATE_EXPERIENCE = SCRIPTS / "经验模板.md"
# ========== 角色 → agent 目录名映射 ==========
ROLE_TO_AGENT = {
# 管理层
"老板分身": "老板分身",
"开发助理": "开发助理",
"助理橙子": "开发助理",
"助手橙子": "开发助理",
# Soul 开发角色
"小程序开发工程师": "小程序开发工程师",
"小程序": "小程序开发工程师",
"管理端开发工程师": "管理端开发工程师",
"管理端": "管理端开发工程师",
"后端工程师": "后端工程师",
"后端": "后端工程师",
"后端开发": "后端工程师",
# 产品与质量
"产品经理": "产品经理",
"产品": "产品经理",
"软件测试": "软件测试",
"测试": "软件测试",
"测试人员": "软件测试",
# 通用
"团队": "团队",
}
def agent_evolution(role: str) -> Path:
"""获取角色对应的 evolution 目录。"""
agent_name = ROLE_TO_AGENT.get(role, role)
return AGENT / agent_name / "evolution"
def agent_script(role: str) -> Path:
"""获取角色对应的 script 目录。"""
agent_name = ROLE_TO_AGENT.get(role, role)
return AGENT / agent_name / "script"

View File

@@ -0,0 +1 @@
E:/Gongsi/Mycontent

View File

@@ -0,0 +1,2 @@
# 复制本文件为 workspace.txt取消下行注释并填入你的项目根绝对路径
# {{PROJECT_ROOT_PATH}}

View File

@@ -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/团队/` | 跨角色共享经验 |

View File

@@ -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

View File

@@ -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

View File

@@ -0,0 +1,140 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Soul 创业派对 - 角色协同流程图</title>
<script src="https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.min.js"></script>
<style>
* { box-sizing: border-box; }
body { font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif; margin: 0; padding: 24px; background: #f5f5f5; }
h1 { color: #333; margin-bottom: 8px; }
.subtitle { color: #666; font-size: 14px; margin-bottom: 24px; }
.diagram { background: #fff; border-radius: 8px; padding: 24px; margin-bottom: 24px; box-shadow: 0 1px 3px rgba(0,0,0,.1); }
.diagram h2 { margin-top: 0; color: #444; font-size: 16px; border-bottom: 1px solid #eee; padding-bottom: 8px; }
.mermaid { display: flex; justify-content: center; }
.mermaid svg { max-width: 100%; }
</style>
</head>
<body>
<h1>Soul 创业派对 - 角色协同流程图</h1>
<p class="subtitle">小程序功能开发(新增/优化)驱动的三端协同流程 · 线框图</p>
<div class="diagram">
<h2>1. 主流程图(阶段划分)</h2>
<div class="mermaid">
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 接口<br/>若管理端需要]
G[小程序开发者实现功能]
E --> G
end
阶段2 --> 阶段3
subgraph 阶段3["阶段 3小程序完成 → 管理端启动"]
H[小程序完成并自测 ✓]
H --> I{管理端需要?}
I -->|是| J[管理端开发者开始调整]
I -->|否| K[跳过]
J --> L[API 开发者补充 admin/db<br/>若有新增需求]
end
阶段3 --> 阶段4
subgraph 阶段4["阶段 4联调与收尾"]
M[三端联调]
N[过 soul-change-checklist]
O[提交]
M --> N --> O
end
</div>
</div>
<div class="diagram">
<h2>2. 角色时序图(谁在何时做什么)</h2>
<div class="mermaid">
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 → 提交
</div>
</div>
<div class="diagram">
<h2>3. 三角色职责与依赖</h2>
<div class="mermaid">
flowchart LR
subgraph 角色["三角色"]
MP[小程序开发者<br/>miniprogram/]
AD[管理端开发者<br/>soul-admin/]
API[API 开发者<br/>soul-api/]
end
subgraph 路径["API 路径"]
P1["/api/miniprogram/*"]
P2["/api/admin/*<br/>/api/db/*"]
end
MP -->|只调| P1
AD -->|只调| P2
API -->|提供| P1
API -->|提供| P2
MP -.->|依赖| API
AD -.->|依赖| API
AD -.->|小程序完成后启动| MP
</div>
</div>
<script>
mermaid.initialize({ startOnLoad: true, theme: 'neutral' });
</script>
</body>
</html>

View File

@@ -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 frontmatterdescription 含触发词miniprogram、soul-admin、soul-api
4. **SKILL-角色流程控制**补充「API 先行」「管理端先行」的简要流程说明。
---
## 五、使用流程(完善后)
```
1. 用户/Agent 在 miniprogram/ 下编辑
→ 推断:当前角色 = 小程序开发者
→ 加载:主 SkillSKILL-小程序开发)+ 对应 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

View File

@@ -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` 第七部分*

View File

@@ -0,0 +1,80 @@
# stitch_soul P0 测试清单
> 测试人员按此清单验证 P0 功能。
---
## 一、开发完成情况
| 阶段 | 状态 | 说明 |
|------|------|------|
| **P0** | ✅ 完成 | 首页/目录 + NEW + 精选推荐算法 |
| P1 | 未开始 | 会员落地页 |
| P2 | 未开始 | 导师 + 预约 |
| P3 | 未开始 | 资料编辑扩展 |
---
## 二、P0 接口测试
**前提**soul-api 已启动,数据库已执行 `add-chapters-is-new.sql`
### 2.1 后端接口(可用 PowerShell 脚本或 curl 验证)
```powershell
# 在 soul-api 目录下执行
cd e:\Gongsi\Mycontent\soul-api
.\scripts\test-p0-endpoints.ps1
```
或手动验证:
| 接口 | 期望 |
|------|------|
| `GET /api/miniprogram/book/all-chapters` | success: truedata 为数组,每项含 `isNew` 字段 |
| `GET /api/miniprogram/book/recommended` | success: truedata 为 13 条,每项含 `tag`(热门/推荐/精选) |
| `GET /api/miniprogram/book/latest-chapters` | success: truedata 为数组(按 updated_at 降序) |
| `GET /api/miniprogram/book/hot` | success: truedata 为数组(按阅读量或兜底排序) |
### 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 章」配置。
---
*测试完成后可更新本文件,标注通过/失败及问题。*

View File

@@ -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 配色统一为 enhancedaccent #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 共用同一套 APIskills 等扩展字段需双向同步。
- 本次会议决议已写入本纪要;各角色经验已同步至 `agent/{角色}/evolution/2026-02-28.md`
---
*会议纪要由助理橙子生成 | 2026-02-28*

View File

@@ -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_v173 章、篇章结构、NEW 标签、免费/¥1 付费
- **会员落地**premium_membership_landing_v1¥1980/年,内容权益(章节、案例库、智能纪要、会议纪要)+ 社交权益匹配、排行、资源、VIP 标识)
- **导师**:列表(搜索/分类)+ 详情(介绍/服务/价格/预约),单次咨询 ¥6002500
- **个人资料**:展示(基本信息/个人故事/互助需求/项目介绍)、编辑(完整表单)、手机号/微信号弹窗
- **我的**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
**Chapterchapters 表)**
- 每行 = 一节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 条)。兜底:无阅读数据时按购买次数;再兜底为默认列表。
- **排除**:序言、尾声、附录不参与排序与推荐。
- **管理端**:算法驱动,无需运营勾选「推荐」;固定兜底章节可产品预设或后台配置。
---
## 实现方案讨论(基于澄清后的需求)
> 各角色分析理解 15 题作答及后端补充说明后,发表实现看法。
### 【产品经理】
需求已厘清,可按以下 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. **资料**:编辑保存成功;手机/微信号未填时提现、找伙伴应拦截并引导弹窗。
**联调**小程序↔APIbook、vip、user、mentors管理端↔APIchapters、mentors、consultations
**实施顺序**:接口就绪后补充用例;优先阅读/会员,再导师、资料。
### 实现路线图(共识)
| 阶段 | 后端 | 管理端 | 小程序 | 测试 |
|-----|------|-------|--------|------|
| **P0** | chapters 支持 NEWbook/latest、book/recommended 确认或补齐 | 章节编辑支持 NEW | 首页/目录 UI 与数据对接 | 阅读/会员用例 |
| **P1** | 会员沿用现 vip无新增接口 | — | 会员落地页 | 会员开通验收 |
| **P2** | mentors 表 + consultations 表;列表/详情/预约接口 | 导师 CRUD、预约列表 | 导师列表+详情+预约 | 导师预约流程 |
| **P3** | users 扩展或 user_profilesprofile 接口扩展 | — | 资料编辑扩展、手机/微弹窗 | 资料+拦截校验 |
**启动条件**:产品确认 MVP 范围与验收标准后,后端先输出 P0 接口方案,管理端/小程序按路线图跟进。
---
## 各开发对新需求的看法
### 【后端开发】
需求清晰,与现有 chapter/book/vip 模型兼容度高,可复用为主、增量开发。导师和资料扩展是主要新增点,技术风险可控。建议产品尽早确认 P2 导师价格配置方式(固定/可配置)、预约状态流转,便于接口设计。
### 【管理端开发工程师】
见下方「管理端建设性与补充说明」。
### 【小程序开发工程师】
稿子完整、交互明确,实现难度主要在数据对接和组件复用。首页/目录 P0 已落地;会员、导师、资料按阶段推进即可。建议后端接口响应格式稳定后再做样式微调,减少返工。
### 【测试人员】
场景边界清楚,可分批补充用例。需关注:导师预约与支付的联调、资料未填时的拦截逻辑、会员与单节购买的权益优先级。
---
## 管理端建设性与补充说明(基于小程序需求)
> 管理端对应小程序各模块,除基础 CRUD 外,建议补充以下能力以更好支持运营与数据闭环。
| 小程序模块 | 管理端基础能力 | 建设性补充 | 说明 |
|-----------|----------------|------------|------|
| **首页/目录** | 章节 NEW 标记 | ① 精选推荐固定兜底章节配置<br>② 章节阅读量/点击数据看板 | 算法兜底可运营配置;运营需看到哪些章节受欢迎 |
| **会员落地** | 用户 VIP 开通 | ① 会员权益文案配置化<br>② 会员开通/续费统计 | 权益文案可随活动调整;统计支撑运营决策 |
| **导师/预约** | 导师 CRUD、预约列表 | ① 导师排序/推荐位<br>② 咨询项目与价格配置<br>③ 预约数据统计(按导师/按时间) | 小程序列表顺序可运营控制;价格可调;数据支撑导师运营 |
| **我的/分享收益** | (若已有收益逻辑) | ① 收益明细与分润规则配置<br>② 提现审核流程 | 小程序有分享收益、可提现金额,管理端需审核与配置 |
| **个人资料** | — | ① 用户资料完善率统计<br>② (若涉及敏感)资料审核 | 支撑找伙伴匹配质量;可选能力 |
### 管理端补充优先级建议
| 优先级 | 补充项 | 与小程序关联 |
|--------|--------|--------------|
| 高 | 导师排序/推荐位、咨询项目价格配置 | 小程序导师列表展示顺序、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 章节 NEWP2 导师/预约管理 | 测试 | 可用的管理端页面 |
| **小程序** | P0P3 按阶段实现 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/*`
---
---
## 开发团队重新分析:怎么实现(可执行方案)
> 在问题 15 作答、精选推荐算法、实现路线图基础上,结合现有代码梳理出的可执行实现方案。
### 现状与差异
| 能力 | 现状 | 与需求差异 |
|------|------|------------|
| 精选推荐 / 热门 | `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-1P0-5输出接口契约 → 管理端 P0-6、小程序 P0-7P0-8 并行。
3. P0 联调验收后P1 小程序独立完成。
4. 后端 P2-1P2-2 → 管理端 P2-3、小程序 P2-4 并行。
5. 后端 P3-1P3-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`*

View File

@@ -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.9N 默认值。
---
## 待办事项
| 责任角色 | 任务 | 优先级 | 截止建议 |
|---------|------|--------|---------|
| 产品经理 | 输出增值版 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`*

View File

@@ -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. **待确认**`完整的` 分支上的提交是否已全部并入 devlopsoul-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`*

View File

@@ -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 解析为「段落 + 片段」(普通文本 / mentionWXML 中对 mention 渲染为可点击 `<text data-user-id>` 并高亮,点击时调用 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`*

View File

@@ -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 → 直接解锁,不限次数
- [ ] 不跳转匹配页

67
.cursor/meeting/README.md Normal file
View File

@@ -0,0 +1,67 @@
# 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) |

111
.cursor/meeting/_模板.md Normal file
View File

@@ -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`*

View File

@@ -0,0 +1,3 @@
# 工作流
存放项目工作流、流程图等文档。

View File

@@ -0,0 +1,16 @@
---
description: 小橙/橙子/橙橙/🍊 - 文档同步与经验升级助理
globs: ["**"]
alwaysApply: false
---
# 小橙触发器
当用户提及**小橙、橙子、橙橙、🍊**,或说**「讨论完毕」「记录一下」「同步到开发文档」「更新文档」「吸收经验」「升级 skills」「记录经验」「保存开发进度」「更新项目索引」「记录开发进度」「任务完成」「搞定了」「完成了」「会议结束」「散会」「会开完了」**时:
**必须使用 Read 工具读取 `e:\Gongsi\Mycontent\.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

View File

@@ -0,0 +1,11 @@
---
description: 产品经理需求与验收。编辑需求文档时加载 SKILL-产品经理
globs: ["开发文档/1、需求/**", "临时需求池/**", "开发文档/10、项目管理/**"]
alwaysApply: false
---
# 产品经理
当编辑 **开发文档/1、需求/**、**临时需求池/**、**开发文档/10、项目管理/** 时,推断当前角色为**产品经理**。
**必须使用 Read 工具读取 `e:\Gongsi\Mycontent\.cursor\skills\product-manager\SKILL.md` 的完整内容**,然后按其规范执行需求分析、文档编写、验收标准制定。

View File

@@ -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_tokenBearer
## 目录与职责
- 仅修改 **soul-admin/** 内文件(含 src/pages、src/components、src/api、src/layouts 等)。
- 不在此处实现小程序逻辑;不在此处编写 soul-api 的 Go 代码或 miniprogram 的 WXML/WXSS/JS。
## Skill 加载(必须执行)
**必须使用 Read 工具读取 `e:\Gongsi\Mycontent\.cursor\skills\admin-dev\SKILL.md` 的完整内容**,按其规范进行开发。该 Skill 包含代码风格、业务逻辑、API 对接细节等完整约定。
接口实现与路由分组的规范在 `e:\Gongsi\Mycontent\.cursor\rules\soul-api.mdc`(编辑 soul-api 时自动加载)。
违反上述路径或职责边界即视为「互窜」,需纠正后再提交。

View File

@@ -0,0 +1,75 @@
---
description: soul-api 路由边界 + 编码规范(合并版,防互窜 + GORM/Gin/响应约定)
globs: soul-api/**/*
alwaysApply: false
---
# soul-api 开发规范
> **Skill 加载**:编辑 soul-api 代码时,**必须使用 Read 工具读取 `e:\Gongsi\Mycontent\.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`PowerWeChathandler 只做参数与结果转换。
- **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 或复杂逻辑处写清用途注释。

View File

@@ -0,0 +1,39 @@
---
description: 变更时关联层检查清单,防止漏改(前端/后端/管理端/表结构)
globs: ["miniprogram/**/*", "soul-admin/**/*", "soul-api/**/*"]
alwaysApply: false
---
# Soul 创业派对 - 变更关联检查清单(防漏改)
在 **miniprogram/**、**soul-admin/** 或 **soul-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 工具读取 `e:\Gongsi\Mycontent\.cursor\skills\change-checklist\SKILL.md` 的完整内容**,按其「以领域为单位思考」的方法逐项确认。
## 四、聊天中触发变更检查
编码完成后在聊天中说**「变更完成」「检查一下」「准备提交」**AI 会主动加载本清单 + change-checklist/SKILL.md 完成核对。**不需要正在编辑文件,直接说触发词即可。**
未通过上述检查即提交视为可能漏改,需补全后再提交。

View File

@@ -0,0 +1,32 @@
---
description: Soul 创业派对开发团队多角色会议触发器。开个会、团队会议、需求评审、方案讨论时加载 SKILL-团队会议
globs: ["**"]
alwaysApply: false
---
# Soul 创业派对 - 会议触发器
当用户表达**开会意图**时(包括但不限于以下触发词),**必须使用 Read 工具读取 `e:\Gongsi\Mycontent\.cursor\skills\team-meeting\SKILL.md` 的完整内容**,然后严格按其流程主持会议。
## 语义化触发词(理解意图,不必完全匹配)
| 触发词示例 | 类型 |
|-----------|------|
| 开发团队和产品经理现在开始开会 | 全员会议 |
| 开个会 / 开会讨论 / 我们开个会 | 快速会议 |
| 团队会议 / 多角色会议 / 开发会议 | 正式会议 |
| 需求评审 / 技术评审 / 方案讨论 | 专项会议 |
| 大家一起讨论 / 召集开会 / 叫大家过来 | 集体讨论 |
| 多角色讨论 / 各角色发言 | 多视角讨论 |
## 会议结束触发
当用户说**「会议结束」「散会」「会开完了」「结束会议」**时:
1. 助理橙子立即执行收尾流程
2. **生成会议纪要**`e:\Gongsi\Mycontent\.cursor\meeting\YYYY-MM-DD_主题.md`
3. **各角色经验入库**`e:\Gongsi\Mycontent\.cursor\agent\{角色}\evolution\YYYY-MM-DD.md`
4. **更新项目索引**`agent/开发助理/项目索引/{索引名}.md` 开发进度表追加一行
5. **更新会议记录索引**`e:\Gongsi\Mycontent\.cursor\meeting\README.md`
**必须使用 Read 工具读取 `e:\Gongsi\Mycontent\.cursor\skills\assistant-doc-sync\SKILL.md` 执行收尾。**

View File

@@ -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 工具读取 `e:\Gongsi\Mycontent\.cursor\skills\miniprogram-dev\SKILL.md` 的完整内容**,按其规范进行开发。该 Skill 包含代码风格、业务逻辑、API 对接细节等完整约定。
接口实现与路由分组的规范在 `e:\Gongsi\Mycontent\.cursor\rules\soul-api.mdc`(编辑 soul-api 时自动加载)。
违反上述路径或职责边界即视为「互窜」,需纠正后再提交。

View File

@@ -0,0 +1,64 @@
---
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/ | 仅预览,非线上 | 不依赖 |
## 核心原则
- 小程序只调 `/api/miniprogram/*`;管理端只调 `/api/admin/*`、`/api/db/*`;禁止混用。
- 变更完成必过 soul-change-checklist.mdc聊天中说「变更完成」「检查一下」「准备提交」时主动触发检查。
## 角色推断与 Skill 加载(必须执行)
根据**当前编辑目录**或**语义触发词****必须使用 Read 工具读取对应的主 Skill 文件完整内容**,然后按其规范执行开发:
### 按编辑目录
| 编辑目录 | 推断角色 | 必须 Read 的主 Skill 文件(绝对路径) |
|----------|----------|---------------------------------------|
| miniprogram/ | 小程序开发工程师 | `e:\Gongsi\Mycontent\.cursor\skills\miniprogram-dev\SKILL.md` |
| soul-admin/ | 管理端开发工程师 | `e:\Gongsi\Mycontent\.cursor\skills\admin-dev\SKILL.md` |
| soul-api/ | 后端开发 | `e:\Gongsi\Mycontent\.cursor\skills\api-dev\SKILL.md` |
| 开发文档/1、需求/、临时需求池/ | 产品经理 | `e:\Gongsi\Mycontent\.cursor\skills\product-manager\SKILL.md` |
| .cursor/ | 助理橙子 | `e:\Gongsi\Mycontent\.cursor\skills\assistant-doc-sync\SKILL.md` |
### 按语义触发词(说啥切角色,无需编辑文件)
用户说出以下词时,推断对应角色并 Read 其 Skill理解意图即可不必完全匹配
| 触发词 | 推断角色 | 必须 Read 的 Skill 文件 |
|--------|----------|-------------------------|
| 后端、API、soul-api、接口、Go、GORM | 后端开发 | `e:\Gongsi\Mycontent\.cursor\skills\api-dev\SKILL.md` |
| 管理端、soul-admin、React、后台管理 | 管理端开发工程师 | `e:\Gongsi\Mycontent\.cursor\skills\admin-dev\SKILL.md` |
| 小程序、miniprogram、C 端、微信小程序 | 小程序开发工程师 | `e:\Gongsi\Mycontent\.cursor\skills\miniprogram-dev\SKILL.md` |
| 产品、需求、验收、排期、需求文档 | 产品经理 | `e:\Gongsi\Mycontent\.cursor\skills\product-manager\SKILL.md` |
| 测试、测试用例、回归测试、功能测试、QA | 测试人员 | `e:\Gongsi\Mycontent\.cursor\skills\testing\SKILL.md` |
### 按场景触发词
| 场景触发词 | 必须 Read 的 Skill 文件(绝对路径) |
|------------|-------------------------------------|
| 小橙、橙子、橙橙、🍊、讨论完毕、记录一下、记录、同步文档 | `e:\Gongsi\Mycontent\.cursor\skills\assistant-doc-sync\SKILL.md` |
| 吸收经验、升级 skills、记录经验、保存开发进度、更新项目索引、记录开发进度、任务完成、搞定了、完成了 | `e:\Gongsi\Mycontent\.cursor\skills\assistant-doc-sync\SKILL.md` |
| 跨端功能开发 | `e:\Gongsi\Mycontent\.cursor\skills\role-flow-control\SKILL.md` |
| 变更完成、检查一下、准备提交 | `e:\Gongsi\Mycontent\.cursor\skills\change-checklist\SKILL.md` |
| 开个会、开会、团队会议、乘风开会、需求评审、方案讨论、大家一起讨论 | `e:\Gongsi\Mycontent\.cursor\skills\team-meeting\SKILL.md`(老板分身/乘风主持) |
| 会议结束、散会、会开完了 | `e:\Gongsi\Mycontent\.cursor\skills\assistant-doc-sync\SKILL.md`(会议收尾) |
**注意**:「必须 Read」= 使用 Read 工具读取**绝对路径**的完整文件内容后执行,不可跳过或仅凭记忆。

View File

@@ -0,0 +1,82 @@
---
description: 老板分身 - 最高权限智能体,协调 Soul 开发团队;编码习惯与思维模式总览
alwaysApply: true
---
# 老板分身 - 能力与约束Soul 创业派对)
> **老板分身权限最高**:协调所有智能体(小程序开发工程师、管理端开发工程师、后端工程师、产品经理、开发助理等)。其他 agent 执行任务时遵循本规则;老板分身可调度、协调、指派任一角色。
> **激活方式**:用户说「老板」「分身」「乘风」「架构」「帮我协调」时,从旁观者转为主动参与。**开会时**:用户说「开会」「开个会」「团队会议」「乘风开会」等,老板分身(乘风)作为主持人自动读取并执行 `.cursor/skills/team-meeting/SKILL.md` 中的会议协议。
> **会话自检**:仅沿用本项目 `.cursor/` 下的 rules、skills、agent忽略与本项目无关的全局 rules/skills。
> **角色驱动**Soul 角色与 agent 映射见 `config/paths.py` 的 ROLE_TO_AGENT。
### 领域特例优先(含合理性校验)
当某个 **skill** 或领域规则与通用规则冲突时,原则上以该 skill/领域规则为准。**但须先做合理性校验**
- 若 skill 的规则**明显不合理**(如违背安全、可维护性、行业惯例等),应**提醒用户**并说明原因,**确认后再覆盖**
- 若合理(如 Soul 三端路由隔离约定),可直接按 skill 执行
---
## 〇、经验自动收集(优先执行)
**在每次回复前判断**:本次会话是否完成了一次「问题 → 解决」的闭环?
### 判定条件(同时满足则触发)
1. 会话中出现了**技术问题**报错、bug、实现困难、配置问题等
2. 问题已**解决**:用户明确表示解决(如「解决了」「可以了」「搞定了」「好了」「跑通了」)
3. 解决过程有**可提炼价值**:有具体的问题描述、解决步骤或关键决策
### 触发后动作
1. 从对话中提取:问题描述、解决过程、关键决策、可提炼的规则方向
2. **推断目标角色**(可多选):
- 小程序/WXML/微信/微信原生→**小程序开发工程师**
- 管理端/React/admin/后台管理→**管理端开发工程师**
- 后端/Go/Gin/GORM/接口/API→**后端工程师**
- 产品/需求/config→**产品经理**
- 测试/自检/QA→**软件测试**
- 架构/选型/路由约定/三端协同→**团队**
- 无法判断→**通用**(写入开发助理)
3. **若可写文件**
- **有明确目标角色**:写入 `.cursor/agent/{角色}/evolution/YYYY-MM-DD-简短描述.md`,并更新该目录下的 `索引.md`
- **无法判断角色**:写入 `.cursor/agent/开发助理/evolution/`
4. **若无法写文件**:输出 JSON并提示用户双击 `agent/开发助理/script/一键-添加经验.bat`
### Soul 目标角色与 target_roles 取值
| 推断场景 | target_roles |
|----------|--------------|
| 小程序/WXML/微信 | `["小程序开发工程师"]` |
| 管理端/React/admin | `["管理端开发工程师"]` |
| 后端/Go/Gin/API | `["后端工程师"]` |
| 产品/需求 | `["产品经理"]` |
| 测试/QA | `["软件测试"]` |
| 架构/三端协同 | `["团队"]` |
| 跨端(小程序+管理端) | `["小程序开发工程师","管理端开发工程师"]` |
### 不触发情况
- 纯咨询、无实际问题
- 问题未解决或用户未确认
- 用户明确说「不要记录」「不用沉淀」
---
## 一、编码习惯
- 先理解需求,再动手写代码
- 小步迭代,可读性优先
- 函数保持单一职责,避免深层嵌套
---
## 二、Soul 三端分工
- **小程序**:只调 `/api/miniprogram/*`,禁止调 admin/db
- **管理端**:只调 `/api/admin/*`、`/api/db/*`
- **后端**:路由分组 miniprogram/admin/db响应格式统一
跨端任务时先分解:后端任务 / 管理端任务 / 小程序任务,再分阶段执行。

View File

@@ -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` 读取。

149
.cursor/scripts/db-exec/node_modules/.package-lock.json generated vendored Normal file
View File

@@ -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
}
}
}

View File

@@ -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

View File

@@ -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).

View File

@@ -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<typeof assert, AssertMethodNames> {
readonly [kOptions]: AssertOptions & { strict: false };
}
interface AssertStrict extends Pick<typeof strict, AssertMethodNames> {
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<T>(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<T>(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<unknown>) | Promise<unknown>, message?: string | Error): Promise<void>;
function rejects(
block: (() => Promise<unknown>) | Promise<unknown>,
error: AssertPredicate,
message?: string | Error,
): Promise<void>;
/**
* 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<unknown>) | Promise<unknown>,
message?: string | Error,
): Promise<void>;
function doesNotReject(
block: (() => Promise<unknown>) | Promise<unknown>,
error: AssertPredicate,
message?: string | Error,
): Promise<void>;
/**
* 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;
}

View File

@@ -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;
}

View File

@@ -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<Func extends (this: ThisArg, ...args: any[]) => 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<Func extends (...args: any[]) => 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<This, Result>(
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<T> {
/**
* 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<Func extends (...args: any[]) => 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(): <R, TArgs extends any[]>(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<R>(store: T, callback: () => R): R;
run<R, TArgs extends any[]>(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<R, TArgs extends any[]>(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";
}

View File

@@ -0,0 +1,466 @@
declare module "node:buffer" {
type ImplicitArrayBuffer<T extends WithImplicitCoercion<ArrayBufferLike>> = 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<ArrayBuffer>;
/**
* 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<ArrayBuffer>;
/**
* 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<number>): Buffer<ArrayBuffer>;
/**
* 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<TArrayBuffer extends ArrayBufferLike = ArrayBuffer>(arrayBuffer: TArrayBuffer): Buffer<TArrayBuffer>;
/**
* 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<ArrayLike<number>>): Buffer<ArrayBuffer>;
/**
* 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: <Buffer 88 13 a0 0f>
*
* // Changing the original Uint16Array changes the Buffer also.
* arr[1] = 6000;
*
* console.log(buf);
* // Prints: <Buffer 88 13 70 17>
* ```
*
* 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: <Buffer 63 64 65 66>
* ```
* @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<TArrayBuffer extends WithImplicitCoercion<ArrayBufferLike>>(
arrayBuffer: TArrayBuffer,
byteOffset?: number,
length?: number,
): Buffer<ImplicitArrayBuffer<TArrayBuffer>>;
/**
* 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<string>, encoding?: BufferEncoding): Buffer<ArrayBuffer>;
from(arrayOrString: WithImplicitCoercion<ArrayLike<number> | string>): Buffer<ArrayBuffer>;
/**
* Creates a new Buffer using the passed {data}
* @param values to create a new Buffer
*/
of(...items: number[]): Buffer<ArrayBuffer>;
/**
* 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: <Buffer 00 00 00 00 ...>
* 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<ArrayBuffer>;
/**
* 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<ArrayBuffer>;
/**
* 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: <Buffer 00 00 00 00 00>
* ```
*
* 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: <Buffer 61 61 61 61 61>
* ```
*
* 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: <Buffer 68 65 6c 6c 6f 20 77 6f 72 6c 64>
* ```
*
* 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<ArrayBuffer>;
/**
* 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): <Buffer a0 8b 28 3f 01 00 00 00 50 32>
*
* buf.fill(0);
*
* console.log(buf);
* // Prints: <Buffer 00 00 00 00 00 00 00 00 00 00>
* ```
*
* 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<ArrayBuffer>;
/**
* 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<ArrayBuffer>;
}
interface Buffer<TArrayBuffer extends ArrayBufferLike = ArrayBufferLike> extends Uint8Array<TArrayBuffer> {
// 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<ArrayBuffer>;
/**
* 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<TArrayBuffer>;
}
// 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<ArrayBuffer>;
/**
* @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<ArrayBufferLike>;
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -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<cluster.WorkerEventMap> {}
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<ClusterEventMap> {
/**
* 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<Worker>;
readonly SCHED_NONE: number;
readonly SCHED_RR: number;
}
}
var cluster: cluster.Cluster;
export = cluster;
}
declare module "cluster" {
import cluster = require("node:cluster");
export = cluster;
}

View File

@@ -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<T, TReturn, TNext> {}
interface AsyncIteratorObject<T, TReturn, TNext> {}
declare namespace NodeJS {
// Populate iterator methods for TS <5.6
interface Iterator<T, TReturn, TNext> extends globalThis.Iterator<T, TReturn, TNext> {}
interface AsyncIterator<T, TReturn, TNext> extends globalThis.AsyncIterator<T, TReturn, TNext> {}
// Polyfill for TS 5.6's instrinsic BuiltinIteratorReturn type, required for DOM-compatible iterators
type BuiltinIteratorReturn = ReturnType<any[][typeof Symbol.iterator]> extends
globalThis.Iterator<any, infer TReturn> ? TReturn
: any;
}

View File

@@ -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<NodeJS.WritableStream, InspectOptions> | 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;
}

View File

@@ -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;
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,564 @@
/**
* The `node:dgram` module provides an implementation of UDP datagram sockets.
*
* ```js
* import dgram from 'node:dgram';
*
* const server = dgram.createSocket('udp4');
*
* server.on('error', (err) => {
* console.error(`server error:\n${err.stack}`);
* server.close();
* });
*
* server.on('message', (msg, rinfo) => {
* console.log(`server got: ${msg} from ${rinfo.address}:${rinfo.port}`);
* });
*
* server.on('listening', () => {
* const address = server.address();
* console.log(`server listening ${address.address}:${address.port}`);
* });
*
* server.bind(41234);
* // Prints: server listening 0.0.0.0:41234
* ```
* @see [source](https://github.com/nodejs/node/blob/v25.x/lib/dgram.js)
*/
declare module "node:dgram" {
import { NonSharedBuffer } from "node:buffer";
import * as dns from "node:dns";
import { Abortable, EventEmitter, InternalEventEmitter } from "node:events";
import { AddressInfo, BlockList } from "node:net";
interface RemoteInfo {
address: string;
family: "IPv4" | "IPv6";
port: number;
size: number;
}
interface BindOptions {
port?: number | undefined;
address?: string | undefined;
exclusive?: boolean | undefined;
fd?: number | undefined;
}
type SocketType = "udp4" | "udp6";
interface SocketOptions extends Abortable {
type: SocketType;
reuseAddr?: boolean | undefined;
reusePort?: boolean | undefined;
/**
* @default false
*/
ipv6Only?: boolean | undefined;
recvBufferSize?: number | undefined;
sendBufferSize?: number | undefined;
lookup?:
| ((
hostname: string,
options: dns.LookupOneOptions,
callback: (err: NodeJS.ErrnoException | null, address: string, family: number) => void,
) => void)
| undefined;
receiveBlockList?: BlockList | undefined;
sendBlockList?: BlockList | undefined;
}
/**
* Creates a `dgram.Socket` object. Once the socket is created, calling `socket.bind()` will instruct the socket to begin listening for datagram
* messages. When `address` and `port` are not passed to `socket.bind()` the
* method will bind the socket to the "all interfaces" address on a random port
* (it does the right thing for both `udp4` and `udp6` sockets). The bound address
* and port can be retrieved using `socket.address().address` and `socket.address().port`.
*
* If the `signal` option is enabled, calling `.abort()` on the corresponding `AbortController` is similar to calling `.close()` on the socket:
*
* ```js
* const controller = new AbortController();
* const { signal } = controller;
* const server = dgram.createSocket({ type: 'udp4', signal });
* server.on('message', (msg, rinfo) => {
* console.log(`server got: ${msg} from ${rinfo.address}:${rinfo.port}`);
* });
* // Later, when you want to close the server.
* controller.abort();
* ```
* @since v0.11.13
* @param options Available options are:
* @param callback Attached as a listener for `'message'` events. Optional.
*/
function createSocket(type: SocketType, callback?: (msg: NonSharedBuffer, rinfo: RemoteInfo) => void): Socket;
function createSocket(options: SocketOptions, callback?: (msg: NonSharedBuffer, rinfo: RemoteInfo) => void): Socket;
interface SocketEventMap {
"close": [];
"connect": [];
"error": [err: Error];
"listening": [];
"message": [msg: NonSharedBuffer, rinfo: RemoteInfo];
}
/**
* Encapsulates the datagram functionality.
*
* New instances of `dgram.Socket` are created using {@link createSocket}.
* The `new` keyword is not to be used to create `dgram.Socket` instances.
* @since v0.1.99
*/
class Socket implements EventEmitter {
/**
* Tells the kernel to join a multicast group at the given `multicastAddress` and `multicastInterface` using the `IP_ADD_MEMBERSHIP` socket option. If the `multicastInterface` argument is not
* specified, the operating system will choose
* one interface and will add membership to it. To add membership to every
* available interface, call `addMembership` multiple times, once per interface.
*
* When called on an unbound socket, this method will implicitly bind to a random
* port, listening on all interfaces.
*
* When sharing a UDP socket across multiple `cluster` workers, the`socket.addMembership()` function must be called only once or an`EADDRINUSE` error will occur:
*
* ```js
* import cluster from 'node:cluster';
* import dgram from 'node:dgram';
*
* if (cluster.isPrimary) {
* cluster.fork(); // Works ok.
* cluster.fork(); // Fails with EADDRINUSE.
* } else {
* const s = dgram.createSocket('udp4');
* s.bind(1234, () => {
* s.addMembership('224.0.0.114');
* });
* }
* ```
* @since v0.6.9
*/
addMembership(multicastAddress: string, multicastInterface?: string): void;
/**
* Returns an object containing the address information for a socket.
* For UDP sockets, this object will contain `address`, `family`, and `port` properties.
*
* This method throws `EBADF` if called on an unbound socket.
* @since v0.1.99
*/
address(): AddressInfo;
/**
* For UDP sockets, causes the `dgram.Socket` to listen for datagram
* messages on a named `port` and optional `address`. If `port` is not
* specified or is `0`, the operating system will attempt to bind to a
* random port. If `address` is not specified, the operating system will
* attempt to listen on all addresses. Once binding is complete, a `'listening'` event is emitted and the optional `callback` function is
* called.
*
* Specifying both a `'listening'` event listener and passing a `callback` to the `socket.bind()` method is not harmful but not very
* useful.
*
* A bound datagram socket keeps the Node.js process running to receive
* datagram messages.
*
* If binding fails, an `'error'` event is generated. In rare case (e.g.
* attempting to bind with a closed socket), an `Error` may be thrown.
*
* Example of a UDP server listening on port 41234:
*
* ```js
* import dgram from 'node:dgram';
*
* const server = dgram.createSocket('udp4');
*
* server.on('error', (err) => {
* console.error(`server error:\n${err.stack}`);
* server.close();
* });
*
* server.on('message', (msg, rinfo) => {
* console.log(`server got: ${msg} from ${rinfo.address}:${rinfo.port}`);
* });
*
* server.on('listening', () => {
* const address = server.address();
* console.log(`server listening ${address.address}:${address.port}`);
* });
*
* server.bind(41234);
* // Prints: server listening 0.0.0.0:41234
* ```
* @since v0.1.99
* @param callback with no parameters. Called when binding is complete.
*/
bind(port?: number, address?: string, callback?: () => void): this;
bind(port?: number, callback?: () => void): this;
bind(callback?: () => void): this;
bind(options: BindOptions, callback?: () => void): this;
/**
* Close the underlying socket and stop listening for data on it. If a callback is
* provided, it is added as a listener for the `'close'` event.
* @since v0.1.99
* @param callback Called when the socket has been closed.
*/
close(callback?: () => void): this;
/**
* Associates the `dgram.Socket` to a remote address and port. Every
* message sent by this handle is automatically sent to that destination. Also,
* the socket will only receive messages from that remote peer.
* Trying to call `connect()` on an already connected socket will result
* in an `ERR_SOCKET_DGRAM_IS_CONNECTED` exception. If `address` is not
* provided, `'127.0.0.1'` (for `udp4` sockets) or `'::1'` (for `udp6` sockets)
* will be used by default. Once the connection is complete, a `'connect'` event
* is emitted and the optional `callback` function is called. In case of failure,
* the `callback` is called or, failing this, an `'error'` event is emitted.
* @since v12.0.0
* @param callback Called when the connection is completed or on error.
*/
connect(port: number, address?: string, callback?: () => void): void;
connect(port: number, callback: () => void): void;
/**
* A synchronous function that disassociates a connected `dgram.Socket` from
* its remote address. Trying to call `disconnect()` on an unbound or already
* disconnected socket will result in an `ERR_SOCKET_DGRAM_NOT_CONNECTED` exception.
* @since v12.0.0
*/
disconnect(): void;
/**
* Instructs the kernel to leave a multicast group at `multicastAddress` using the `IP_DROP_MEMBERSHIP` socket option. This method is automatically called by the
* kernel when the socket is closed or the process terminates, so most apps will
* never have reason to call this.
*
* If `multicastInterface` is not specified, the operating system will attempt to
* drop membership on all valid interfaces.
* @since v0.6.9
*/
dropMembership(multicastAddress: string, multicastInterface?: string): void;
/**
* This method throws `ERR_SOCKET_BUFFER_SIZE` if called on an unbound socket.
* @since v8.7.0
* @return the `SO_RCVBUF` socket receive buffer size in bytes.
*/
getRecvBufferSize(): number;
/**
* This method throws `ERR_SOCKET_BUFFER_SIZE` if called on an unbound socket.
* @since v8.7.0
* @return the `SO_SNDBUF` socket send buffer size in bytes.
*/
getSendBufferSize(): number;
/**
* @since v18.8.0, v16.19.0
* @return Number of bytes queued for sending.
*/
getSendQueueSize(): number;
/**
* @since v18.8.0, v16.19.0
* @return Number of send requests currently in the queue awaiting to be processed.
*/
getSendQueueCount(): number;
/**
* By default, binding a socket will cause it to block the Node.js process from
* exiting as long as the socket is open. The `socket.unref()` method can be used
* to exclude the socket from the reference counting that keeps the Node.js
* process active. The `socket.ref()` method adds the socket back to the reference
* counting and restores the default behavior.
*
* Calling `socket.ref()` multiples times will have no additional effect.
*
* The `socket.ref()` method returns a reference to the socket so calls can be
* chained.
* @since v0.9.1
*/
ref(): this;
/**
* Returns an object containing the `address`, `family`, and `port` of the remote
* endpoint. This method throws an `ERR_SOCKET_DGRAM_NOT_CONNECTED` exception
* if the socket is not connected.
* @since v12.0.0
*/
remoteAddress(): AddressInfo;
/**
* Broadcasts a datagram on the socket.
* For connectionless sockets, the destination `port` and `address` must be
* specified. Connected sockets, on the other hand, will use their associated
* remote endpoint, so the `port` and `address` arguments must not be set.
*
* The `msg` argument contains the message to be sent.
* Depending on its type, different behavior can apply. If `msg` is a `Buffer`,
* any `TypedArray` or a `DataView`,
* the `offset` and `length` specify the offset within the `Buffer` where the
* message begins and the number of bytes in the message, respectively.
* If `msg` is a `String`, then it is automatically converted to a `Buffer` with `'utf8'` encoding. With messages that
* contain multi-byte characters, `offset` and `length` will be calculated with
* respect to `byte length` and not the character position.
* If `msg` is an array, `offset` and `length` must not be specified.
*
* The `address` argument is a string. If the value of `address` is a host name,
* DNS will be used to resolve the address of the host. If `address` is not
* provided or otherwise nullish, `'127.0.0.1'` (for `udp4` sockets) or `'::1'` (for `udp6` sockets) will be used by default.
*
* If the socket has not been previously bound with a call to `bind`, the socket
* is assigned a random port number and is bound to the "all interfaces" address
* (`'0.0.0.0'` for `udp4` sockets, `'::0'` for `udp6` sockets.)
*
* An optional `callback` function may be specified to as a way of reporting
* DNS errors or for determining when it is safe to reuse the `buf` object.
* DNS lookups delay the time to send for at least one tick of the
* Node.js event loop.
*
* The only way to know for sure that the datagram has been sent is by using a `callback`. If an error occurs and a `callback` is given, the error will be
* passed as the first argument to the `callback`. If a `callback` is not given,
* the error is emitted as an `'error'` event on the `socket` object.
*
* Offset and length are optional but both _must_ be set if either are used.
* They are supported only when the first argument is a `Buffer`, a `TypedArray`,
* or a `DataView`.
*
* This method throws `ERR_SOCKET_BAD_PORT` if called on an unbound socket.
*
* Example of sending a UDP packet to a port on `localhost`;
*
* ```js
* import dgram from 'node:dgram';
* import { Buffer } from 'node:buffer';
*
* const message = Buffer.from('Some bytes');
* const client = dgram.createSocket('udp4');
* client.send(message, 41234, 'localhost', (err) => {
* client.close();
* });
* ```
*
* Example of sending a UDP packet composed of multiple buffers to a port on`127.0.0.1`;
*
* ```js
* import dgram from 'node:dgram';
* import { Buffer } from 'node:buffer';
*
* const buf1 = Buffer.from('Some ');
* const buf2 = Buffer.from('bytes');
* const client = dgram.createSocket('udp4');
* client.send([buf1, buf2], 41234, (err) => {
* client.close();
* });
* ```
*
* Sending multiple buffers might be faster or slower depending on the
* application and operating system. Run benchmarks to
* determine the optimal strategy on a case-by-case basis. Generally speaking,
* however, sending multiple buffers is faster.
*
* Example of sending a UDP packet using a socket connected to a port on `localhost`:
*
* ```js
* import dgram from 'node:dgram';
* import { Buffer } from 'node:buffer';
*
* const message = Buffer.from('Some bytes');
* const client = dgram.createSocket('udp4');
* client.connect(41234, 'localhost', (err) => {
* client.send(message, (err) => {
* client.close();
* });
* });
* ```
* @since v0.1.99
* @param msg Message to be sent.
* @param offset Offset in the buffer where the message starts.
* @param length Number of bytes in the message.
* @param port Destination port.
* @param address Destination host name or IP address.
* @param callback Called when the message has been sent.
*/
send(
msg: string | NodeJS.ArrayBufferView | readonly any[],
port?: number,
address?: string,
callback?: (error: Error | null, bytes: number) => void,
): void;
send(
msg: string | NodeJS.ArrayBufferView | readonly any[],
port?: number,
callback?: (error: Error | null, bytes: number) => void,
): void;
send(
msg: string | NodeJS.ArrayBufferView | readonly any[],
callback?: (error: Error | null, bytes: number) => void,
): void;
send(
msg: string | NodeJS.ArrayBufferView,
offset: number,
length: number,
port?: number,
address?: string,
callback?: (error: Error | null, bytes: number) => void,
): void;
send(
msg: string | NodeJS.ArrayBufferView,
offset: number,
length: number,
port?: number,
callback?: (error: Error | null, bytes: number) => void,
): void;
send(
msg: string | NodeJS.ArrayBufferView,
offset: number,
length: number,
callback?: (error: Error | null, bytes: number) => void,
): void;
/**
* Sets or clears the `SO_BROADCAST` socket option. When set to `true`, UDP
* packets may be sent to a local interface's broadcast address.
*
* This method throws `EBADF` if called on an unbound socket.
* @since v0.6.9
*/
setBroadcast(flag: boolean): void;
/**
* _All references to scope in this section are referring to [IPv6 Zone Indices](https://en.wikipedia.org/wiki/IPv6_address#Scoped_literal_IPv6_addresses), which are defined by [RFC
* 4007](https://tools.ietf.org/html/rfc4007). In string form, an IP_
* _with a scope index is written as `'IP%scope'` where scope is an interface name_
* _or interface number._
*
* Sets the default outgoing multicast interface of the socket to a chosen
* interface or back to system interface selection. The `multicastInterface` must
* be a valid string representation of an IP from the socket's family.
*
* For IPv4 sockets, this should be the IP configured for the desired physical
* interface. All packets sent to multicast on the socket will be sent on the
* interface determined by the most recent successful use of this call.
*
* For IPv6 sockets, `multicastInterface` should include a scope to indicate the
* interface as in the examples that follow. In IPv6, individual `send` calls can
* also use explicit scope in addresses, so only packets sent to a multicast
* address without specifying an explicit scope are affected by the most recent
* successful use of this call.
*
* This method throws `EBADF` if called on an unbound socket.
*
* #### Example: IPv6 outgoing multicast interface
*
* On most systems, where scope format uses the interface name:
*
* ```js
* const socket = dgram.createSocket('udp6');
*
* socket.bind(1234, () => {
* socket.setMulticastInterface('::%eth1');
* });
* ```
*
* On Windows, where scope format uses an interface number:
*
* ```js
* const socket = dgram.createSocket('udp6');
*
* socket.bind(1234, () => {
* socket.setMulticastInterface('::%2');
* });
* ```
*
* #### Example: IPv4 outgoing multicast interface
*
* All systems use an IP of the host on the desired physical interface:
*
* ```js
* const socket = dgram.createSocket('udp4');
*
* socket.bind(1234, () => {
* socket.setMulticastInterface('10.0.0.2');
* });
* ```
* @since v8.6.0
*/
setMulticastInterface(multicastInterface: string): void;
/**
* Sets or clears the `IP_MULTICAST_LOOP` socket option. When set to `true`,
* multicast packets will also be received on the local interface.
*
* This method throws `EBADF` if called on an unbound socket.
* @since v0.3.8
*/
setMulticastLoopback(flag: boolean): boolean;
/**
* Sets the `IP_MULTICAST_TTL` socket option. While TTL generally stands for
* "Time to Live", in this context it specifies the number of IP hops that a
* packet is allowed to travel through, specifically for multicast traffic. Each
* router or gateway that forwards a packet decrements the TTL. If the TTL is
* decremented to 0 by a router, it will not be forwarded.
*
* The `ttl` argument may be between 0 and 255\. The default on most systems is `1`.
*
* This method throws `EBADF` if called on an unbound socket.
* @since v0.3.8
*/
setMulticastTTL(ttl: number): number;
/**
* Sets the `SO_RCVBUF` socket option. Sets the maximum socket receive buffer
* in bytes.
*
* This method throws `ERR_SOCKET_BUFFER_SIZE` if called on an unbound socket.
* @since v8.7.0
*/
setRecvBufferSize(size: number): void;
/**
* Sets the `SO_SNDBUF` socket option. Sets the maximum socket send buffer
* in bytes.
*
* This method throws `ERR_SOCKET_BUFFER_SIZE` if called on an unbound socket.
* @since v8.7.0
*/
setSendBufferSize(size: number): void;
/**
* Sets the `IP_TTL` socket option. While TTL generally stands for "Time to Live",
* in this context it specifies the number of IP hops that a packet is allowed to
* travel through. Each router or gateway that forwards a packet decrements the
* TTL. If the TTL is decremented to 0 by a router, it will not be forwarded.
* Changing TTL values is typically done for network probes or when multicasting.
*
* The `ttl` argument may be between 1 and 255\. The default on most systems
* is 64.
*
* This method throws `EBADF` if called on an unbound socket.
* @since v0.1.101
*/
setTTL(ttl: number): number;
/**
* By default, binding a socket will cause it to block the Node.js process from
* exiting as long as the socket is open. The `socket.unref()` method can be used
* to exclude the socket from the reference counting that keeps the Node.js
* process active, allowing the process to exit even if the socket is still
* listening.
*
* Calling `socket.unref()` multiple times will have no additional effect.
*
* The `socket.unref()` method returns a reference to the socket so calls can be
* chained.
* @since v0.9.1
*/
unref(): this;
/**
* Tells the kernel to join a source-specific multicast channel at the given `sourceAddress` and `groupAddress`, using the `multicastInterface` with the `IP_ADD_SOURCE_MEMBERSHIP` socket
* option. If the `multicastInterface` argument
* is not specified, the operating system will choose one interface and will add
* membership to it. To add membership to every available interface, call `socket.addSourceSpecificMembership()` multiple times, once per interface.
*
* When called on an unbound socket, this method will implicitly bind to a random
* port, listening on all interfaces.
* @since v13.1.0, v12.16.0
*/
addSourceSpecificMembership(sourceAddress: string, groupAddress: string, multicastInterface?: string): void;
/**
* Instructs the kernel to leave a source-specific multicast channel at the given `sourceAddress` and `groupAddress` using the `IP_DROP_SOURCE_MEMBERSHIP` socket option. This method is
* automatically called by the kernel when the
* socket is closed or the process terminates, so most apps will never have
* reason to call this.
*
* If `multicastInterface` is not specified, the operating system will attempt to
* drop membership on all valid interfaces.
* @since v13.1.0, v12.16.0
*/
dropSourceSpecificMembership(sourceAddress: string, groupAddress: string, multicastInterface?: string): void;
/**
* Calls `socket.close()` and returns a promise that fulfills when the socket has closed.
* @since v20.5.0
*/
[Symbol.asyncDispose](): Promise<void>;
}
interface Socket extends InternalEventEmitter<SocketEventMap> {}
}
declare module "dgram" {
export * from "node:dgram";
}

Some files were not shown because too many files have changed in this diff Show More