ai-project-manage-cli 6.0.29 → 6.0.30

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.js CHANGED
@@ -1102,9 +1102,72 @@ function validateAgentWsMessage(value, kind) {
1102
1102
  };
1103
1103
  }
1104
1104
 
1105
+ // src/commands/connect/abort-signal-debug.ts
1106
+ import {
1107
+ getEventListeners,
1108
+ getMaxListeners,
1109
+ setMaxListeners
1110
+ } from "node:events";
1111
+ function isAbortSignalDebugEnabled() {
1112
+ const v = process.env.APM_DEBUG_ABORT_SIGNAL?.trim().toLowerCase();
1113
+ return v === "1" || v === "true" || v === "yes";
1114
+ }
1115
+ function formatAbortSignalStats(signal, label) {
1116
+ if (!signal) {
1117
+ return `[apm:abort-debug] ${label}: (no signal)`;
1118
+ }
1119
+ const listeners = getEventListeners(signal, "abort");
1120
+ const max = getMaxListeners(signal);
1121
+ return `[apm:abort-debug] ${label}: abortListeners=${listeners.length} maxListeners=${max} aborted=${signal.aborted}`;
1122
+ }
1123
+ function logAbortSignalStats(signal, label) {
1124
+ if (!isAbortSignalDebugEnabled()) return;
1125
+ console.log(formatAbortSignalStats(signal, label));
1126
+ }
1127
+ var installed = false;
1128
+ function installAbortSignalDebug() {
1129
+ if (!isAbortSignalDebugEnabled() || installed) return;
1130
+ installed = true;
1131
+ const maxFromEnv = Number.parseInt(
1132
+ process.env.APM_ABORT_SIGNAL_MAX_LISTENERS ?? "",
1133
+ 10
1134
+ );
1135
+ if (Number.isFinite(maxFromEnv) && maxFromEnv > 0) {
1136
+ setMaxListeners(maxFromEnv);
1137
+ console.log(
1138
+ `[apm:abort-debug] setMaxListeners(${maxFromEnv}) via APM_ABORT_SIGNAL_MAX_LISTENERS`
1139
+ );
1140
+ }
1141
+ process.on("warning", (warning) => {
1142
+ if (warning.name !== "MaxListenersExceededWarning") return;
1143
+ console.warn(`[apm:abort-debug] ${warning.name}: ${warning.message}`);
1144
+ if (warning.stack) {
1145
+ console.warn(warning.stack);
1146
+ }
1147
+ });
1148
+ const proto = AbortSignal.prototype;
1149
+ const original = proto.addEventListener;
1150
+ proto.addEventListener = function(type, listener, options) {
1151
+ if (type === "abort") {
1152
+ const sig = this;
1153
+ const before = getEventListeners(sig, "abort").length;
1154
+ const max = getMaxListeners(sig);
1155
+ const stack = new Error("[apm:abort-debug] addEventListener stack").stack?.split("\n").slice(2, 8).join("\n") ?? "";
1156
+ console.log(
1157
+ `[apm:abort-debug] addEventListener("abort") before=${before} max=${max}
1158
+ ${stack}`
1159
+ );
1160
+ }
1161
+ return original.call(this, type, listener, options);
1162
+ };
1163
+ console.log(
1164
+ "[apm:abort-debug] \u5DF2\u542F\u7528 AbortSignal \u8C03\u8BD5\uFF08APM_DEBUG_ABORT_SIGNAL\uFF09"
1165
+ );
1166
+ }
1167
+
1105
1168
  // src/commands/connect/cursor-agent.ts
1106
1169
  import { Agent, CursorAgentError } from "@cursor/sdk";
1107
- import { setMaxListeners } from "node:events";
1170
+ import { setMaxListeners as setMaxListeners2 } from "node:events";
1108
1171
  import { resolve as resolve3 } from "path";
1109
1172
 
1110
1173
  // src/session-utils.ts
@@ -1340,7 +1403,8 @@ async function syncCursorMessageLog(cfg, ctx, events) {
1340
1403
  }
1341
1404
 
1342
1405
  // src/commands/connect/cursor-agent.ts
1343
- setMaxListeners(50);
1406
+ setMaxListeners2(50);
1407
+ installAbortSignalDebug();
1344
1408
  var logCtx = (ctx, agentId) => ({
1345
1409
  sessionId: ctx.sessionId,
1346
1410
  messageId: ctx.messageId,
@@ -1348,6 +1412,7 @@ var logCtx = (ctx, agentId) => ({
1348
1412
  });
1349
1413
  async function runCursorAgent(cfg, ctx, options) {
1350
1414
  const signal = options?.signal;
1415
+ logAbortSignalStats(signal, "runCursorAgent:start");
1351
1416
  if (signal?.aborted) {
1352
1417
  throw new Error("\u8FDE\u63A5\u5DF2\u5173\u95ED\uFF0C\u4EFB\u52A1\u4E2D\u65AD");
1353
1418
  }
@@ -1386,9 +1451,11 @@ async function runCursorAgent(cfg, ctx, options) {
1386
1451
  void activeRun.cancel().catch(() => void 0);
1387
1452
  };
1388
1453
  signal?.addEventListener("abort", abortRun, { once: true });
1454
+ logAbortSignalStats(signal, "runCursorAgent:after-addListener");
1389
1455
  try {
1390
1456
  const run = await agent.send(prompt);
1391
1457
  activeRun = run;
1458
+ logAbortSignalStats(signal, "runCursorAgent:after-send");
1392
1459
  console.log(`[apm] Cursor run id=${run.id} agentId=${agent.agentId}`);
1393
1460
  for await (const event of run.stream()) {
1394
1461
  if (signal?.aborted) {
@@ -1415,7 +1482,9 @@ async function runCursorAgent(cfg, ctx, options) {
1415
1482
  }
1416
1483
  throw err;
1417
1484
  } finally {
1485
+ logAbortSignalStats(signal, "runCursorAgent:finally-before-cleanup");
1418
1486
  signal?.removeEventListener("abort", abortRun);
1487
+ logAbortSignalStats(signal, "runCursorAgent:finally-after-cleanup");
1419
1488
  await agent[Symbol.asyncDispose]();
1420
1489
  }
1421
1490
  }
@@ -1541,7 +1610,12 @@ async function runConnect(options) {
1541
1610
  const shutdown = async (code = 0) => {
1542
1611
  if (shuttingDown) return;
1543
1612
  shuttingDown = true;
1613
+ logAbortSignalStats(
1614
+ shutdownAbort.signal,
1615
+ "connect:shutdown-before-abort"
1616
+ );
1544
1617
  shutdownAbort.abort();
1618
+ logAbortSignalStats(shutdownAbort.signal, "connect:shutdown-after-abort");
1545
1619
  stopHeartbeat?.();
1546
1620
  if (ws.readyState === WebSocket.OPEN || ws.readyState === WebSocket.CONNECTING) {
1547
1621
  ws.terminate();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-project-manage-cli",
3
- "version": "6.0.29",
3
+ "version": "6.0.30",
4
4
  "description": "命令行工具:后续用于调用平台后端 API 完成运维与自动化操作",
5
5
  "type": "module",
6
6
  "private": false,
@@ -3,7 +3,7 @@
3
3
  ### 步骤 1: 获取当前版本 PRD 的内容以及协作内容
4
4
 
5
5
  1. 用 **Read** 工具阅读 `.apm/sessions/<会话ID>/docs/PRD.md`,如果不存在则开发失败,退出流程。
6
- 2. 按需阅读 `.apm/sessions/<会话ID>/docs`下的其他文档,比如前后端联调的文档
6
+ 2. 按需阅读 `.apm/sessions/<会话ID>/docs` 下其他文档:前端对照 `FRONTEND.md` + `API.md`,后端对照 `BACKEND.md` + `API.md`
7
7
 
8
8
  ### 步骤 2: 明确开发模式
9
9
 
@@ -0,0 +1,77 @@
1
+ ## 工作流程
2
+
3
+ ### 步骤 1:确认身份
4
+
5
+ 1. 用 **Read** 工具阅读 `.apm/sessions/<会话ID>/session.yaml`
6
+ 2. 从 `members` 中找到与**当前角色**对应的 `name` 和 `expert`(以 session.yaml 为准,不要猜)
7
+ 3. 若无法确定自己的 `name`,停止流程并在回复中说明
8
+
9
+ ### 步骤 2:提取本人发言
10
+
11
+ 1. 用 **Read** 工具阅读 `.apm/sessions/<会话ID>/messages.xml`
12
+ 2. 只保留 `name="<本人 name>"` 的 `<message>` 条目
13
+ 3. 跳过 `content` 为空或仅有空白的内容
14
+ 4. 按 `round` 属性升序排列(无 `round` 的放在最后)
15
+ 5. 若有效消息少于 1 条,停止流程并在回复中说明「消息不足,无法复盘」及缺什么
16
+
17
+ **messages.xml 过大时**:优先保留带 `round` 的消息;单条 `content` 超过 3000 字时,保留开头结论段与结尾总结段,中间用「…(已省略)…」代替,不要丢弃关键口径。
18
+
19
+ ### 步骤 3:补充任务背景(按需)
20
+
21
+ 1. 用 **Read** 工具阅读 `.apm/sessions/<会话ID>/TASK.md`
22
+ 2. 若存在 `docs/PRD.md`,Read 它,用于锚定「这次在解决什么业务问题」
23
+ 3. 按需 Read `docs/` 下与本人产出相关的文档(如技术方案、评审意见),**不要**把全文复制进复盘
24
+
25
+ ### 步骤 4:生成经验 Skill 草稿
26
+
27
+ 1. 用 **Read** 工具阅读 `.apm/skills/apm-recap/recap-template.md`
28
+ 2. 严格按模板结构撰写,Write 到 `.apm/sessions/<会话ID>/docs/<name>-复盘技能.md`
29
+ 3. 文件名中的 `<name>` 与 session.yaml 中的 `name` 完全一致(含中文)
30
+
31
+ **内容原则**:
32
+
33
+ - 只基于步骤 2 ~ 3 的实际材料归纳,**禁止编造**未出现过的决策或产出
34
+ - 写**可复用的经验**(口径、做法、踩坑),**禁止**按轮次记流水账(不要写「第 N 轮我…」)
35
+ - **产品语言优先**;必要时后端可写表名 / 主表字段名,但避免大段代码、文件路径、组件名
36
+ - 只复盘**本人**贡献;他人工作仅可在「协作依赖」中简要提及,不得代写
37
+
38
+ ### 步骤 5:拟定平台 Skill 元数据
39
+
40
+ 在草稿文件**最顶部**写入元数据块(格式见 recap-template.md),包含:
41
+
42
+ - `skill_name`:建议 `recap-<expert-key>-<topic-slug>`(英文小写、连字符;topic 取自任务主题,非 sessionId)
43
+ - `skill_description`:第三人称,说明适用场景与触发词,便于下次任务时被发现
44
+ - `merge_strategy`:同业务域已有 Skill 时填 `upsert`,全新领域填 `create`
45
+
46
+ **expert-key 参考**(按 session.yaml 的 expert 映射):
47
+
48
+ | expert | expert-key |
49
+ | ---------- | ---------- |
50
+ | 产品经理 | pm |
51
+ | 前端工程师 | frontend |
52
+ | 后端工程师 | backend |
53
+ | 全栈工程师 | fullstack |
54
+ | 项目经理 | pm-lead |
55
+
56
+ ### 步骤 6:回复消息
57
+
58
+ 回复中须包含:
59
+
60
+ 1. 草稿路径:`.apm/sessions/<会话ID>/docs/<name>-复盘技能.md`
61
+ 2. 建议的 `skill_name` 与 `skill_description`(与元数据块一致)
62
+ 3. 本次复盘摘要(3 ~ 5 条 bullet,概括解决了哪些口径/问题)
63
+ 4. 发布提醒:草稿需由管理员发布到平台 **Skills** 后,执行 `apm update-skills`,其他会话中的同角色成员方可 Read 该经验技能
64
+
65
+ ---
66
+
67
+ ## 约束
68
+
69
+ - 本技能只生成**草稿**;Agent 无法直接写入平台 Skill 库或 `.apm/skills/`
70
+ - 不得修改他人已写的 `docs/*-复盘技能.md`
71
+ - 若 TASK/PRD 与 messages 矛盾,以**本人实际发言**为准,并在「已知踩坑」中注明口径尚未统一
72
+
73
+ ## 何时使用本技能
74
+
75
+ - 用户或项目经理要求复盘、总结、沉淀经验
76
+ - 会话即将归档,需要将本次贡献转化为可复用技能
77
+ - 任务阶段性完成,角色主动沉淀领域知识
@@ -0,0 +1,121 @@
1
+ ## 复盘技能模板
2
+
3
+ 生成 `.apm/sessions/<会话ID>/docs/<name>-复盘技能.md` 时,**完整文件**须包含下方「元数据块 + 正文」两部分。
4
+
5
+ ---
6
+
7
+ ### 元数据块(写在文件最顶部)
8
+
9
+ ```markdown
10
+ ---
11
+ skill_name: recap-<expert-key>-<topic-slug>
12
+ skill_description: <第三人称描述:谁在什么业务域的经验;什么需求场景下应阅读本技能。含 2~4 个触发关键词。>
13
+ expert: <session.yaml 中的 expert,如「后端工程师」>
14
+ member: <session.yaml 中的 name,如「医务小朱」>
15
+ source_session: <会话 ID>
16
+ merge_strategy: upsert
17
+ generated_at: <YYYY-MM-DD>
18
+ ---
19
+ ```
20
+
21
+ **skill_name 示例**:
22
+
23
+ - `recap-backend-death-patient-list`
24
+ - `recap-frontend-death-patient-list`
25
+ - `recap-pm-medical-record-check`
26
+
27
+ **skill_description 示例**:
28
+
29
+ > 医务小朱在死亡患者列表选择界面项目中的后端经验。处理死亡患者筛选、病历检查、HIS 列表接口相关需求时使用。
30
+
31
+ ---
32
+
33
+ ### 正文骨架(元数据块之后)
34
+
35
+ ```markdown
36
+ ## 适用场景
37
+
38
+ - <什么业务、什么页面、什么类型的需求应读本技能>
39
+ - <2 ~ 3 条,每条一句,可验收>
40
+
41
+ ## 任务背景
42
+
43
+ <1 ~ 3 句:本次任务要解决什么;不写轮次过程>
44
+
45
+ ## 已解决的口径与决策
46
+
47
+ <!-- 产品/技术结论,可直接当下次任务的规范 -->
48
+
49
+ ### <决策点 1 简短名称>
50
+
51
+ - **结论**:…
52
+ - **依据**:…(可引用 PRD 行号或本人发言中的要点,不要贴大段原文)
53
+
54
+ ### <决策点 2>
55
+
56
+ - **结论**:…
57
+ - **依据**:…
58
+
59
+ ## 可复用做法
60
+
61
+ <!-- 步骤、检查清单、评审要点——下次同类任务可直接照做 -->
62
+
63
+ 1. …
64
+ 2. …
65
+
66
+ ## 已知踩坑与协作依赖
67
+
68
+ ### 踩坑
69
+
70
+ - …
71
+
72
+ ### 协作依赖
73
+
74
+ - **<其他角色名>**:…(仅写与本人工作相关的交接点)
75
+
76
+ ## 关联产出
77
+
78
+ <!-- 只列文件名,不贴全文 -->
79
+
80
+ - `PRD.md` / `TASK.md` / `<角色>-技术方案.md` / …
81
+
82
+ ## 来源
83
+
84
+ - 会话:<source_session>
85
+ - 角色:<member>(<expert>)
86
+ - 生成:<generated_at>
87
+ ```
88
+
89
+ ---
90
+
91
+ ### 编写规范
92
+
93
+ | 类型 | 写法要点 |
94
+ | ---------- | ----------------------------------------------------------- |
95
+ | 口径/决策 | 写清「是什么 + 为什么」,避免「讨论过」「沟通过」等空泛表述 |
96
+ | 可复用做法 | 用动词开头(确认、校验、先…再…),可当作 checklist |
97
+ | 踩坑 | 写「不要…」「注意…」,说明后果或触发条件 |
98
+ | 协作依赖 | 只写阻塞或交接点,不写他人完整方案 |
99
+ | 禁止 | 轮次叙事、聊天记录摘要、大段代码、组件/文件路径堆砌 |
100
+
101
+ ### 质量自检(写入前逐项确认)
102
+
103
+ - [ ] 元数据块完整,`skill_name` 为英文 slug
104
+ - [ ] 「适用场景」陌生人能判断要不要读
105
+ - [ ] 「已解决的口径与决策」至少 1 条,且来自本人 messages 或 docs
106
+ - [ ] 全文无「第 N 轮」字样
107
+ - [ ] 无编造:材料中没有的内容未写入
108
+
109
+ ### 合并已有 Skill 时(merge_strategy: upsert)
110
+
111
+ 若平台已存在同名 `skill_name` 的 Skill,生成草稿时在正文最末追加:
112
+
113
+ ```markdown
114
+ ## 合并说明
115
+
116
+ - 本次新增:<bullet 列表>
117
+ - 本次修正:<bullet 列表,旧口径 → 新口径>
118
+ - 建议保留不变:<bullet 列表>
119
+ ```
120
+
121
+ 管理员发布时对照合并说明更新平台 Skill 正文。
@@ -0,0 +1,40 @@
1
+ ## 适用范围
2
+
3
+ 后端工程师在需求评审通过后,编写两份文档:
4
+
5
+ | 文档 | 路径 | 定位 |
6
+ |------|------|------|
7
+ | `BACKEND.md` | `docs/BACKEND.md` | **Plan**:后端怎么改、分几步、动哪些文件 |
8
+ | `API.md` | `docs/API.md` | **联调契约**:给前端看的 URL、参数、示例 |
9
+
10
+ 两份文档禁止合并;`API.md` 不写 Service/SQL 等实现细节。
11
+
12
+ ---
13
+
14
+ ## 工作流程
15
+
16
+ 1. **Read** `docs/PRD.md`;不存在则退出。
17
+ 2. 按需调研代码库,确认现网接口与表结构。
18
+ 3. **Read** `backend-template.md`,按模板 **Write** `docs/BACKEND.md`。
19
+ 4. **Read** `api-template.md`,按模板 **Write** `docs/API.md`。
20
+ 5. 执行 `apm sync-document` 同步两份文档,@ 前端阅读 `API.md` 并编写 `FRONTEND.md`。
21
+
22
+ (模板路径:`.apm/skills/apm-write-backend-api/`)
23
+
24
+ ---
25
+
26
+ ## 写作要求
27
+
28
+ **BACKEND.md**(对齐 Cursor Plan,通常 **30~80 行**)
29
+
30
+ - 背景 → 实现步骤 → 涉及文件 → 数据与规则 → 验收
31
+ - 可写表名、关键字段;不要大段 SQL、不要完整参数表(那些放 `API.md`)
32
+
33
+ **API.md**
34
+
35
+ - 写清前端联调所需:Path、参数、示例、联调注意点
36
+ - 不复制 `BACKEND.md` 的实现步骤
37
+
38
+ ## 何时使用
39
+
40
+ 协作流程阶段 3;或用户要求写 `BACKEND.md` / `API.md`。
@@ -0,0 +1,35 @@
1
+ # API.md 模板
2
+
3
+ 给前端联调用。实现步骤见 `BACKEND.md`;本文只写契约,篇幅尽量克制。
4
+
5
+ ```markdown
6
+ # <功能名称> — 接口
7
+
8
+ ## 背景
9
+
10
+ <1~2 句:本次新增/变更/复用哪些接口>
11
+
12
+ ## <接口名称>(新增)
13
+
14
+ - **GET** `/实际/path`
15
+ - **用途**:…
16
+ - **参数**:`pageNo`、`pageSize`(可选,默认 …);`xxx`(何时传、何时不传)
17
+ - **成功**:`result` 结构简述 + 简短 JSON 示例
18
+ - **失败**:格式非法 → `message` 示例;无数据 → 空列表算成功
19
+
20
+ ## <接口名称>(现网,不变)
21
+
22
+ - **GET** `/实际/path/{id}`
23
+ - **用途**:…
24
+ - **前端关注字段**:`fieldA` → 模板 xxx
25
+
26
+ ## 联调说明
27
+
28
+ 1. 首屏:只传 …
29
+ 2. 筛选:…
30
+ 3. 选中后:必须先调详情,禁止 …
31
+ ```
32
+
33
+ ## 不要写
34
+
35
+ SQL、Java 类路径、Service 实现、后端验收清单。
@@ -0,0 +1,41 @@
1
+ # Plan 模板(BACKEND.md)
2
+
3
+ 对齐 Cursor Plan:背景 → 实现步骤 → 涉及文件 → 数据与规则 → 验收。全文尽量一页以内。
4
+
5
+ ```markdown
6
+ # <功能名称>
7
+
8
+ ## 背景
9
+
10
+ <2 ~ 3 句:交付什么能力、改哪些模块、现网什么保持不变>
11
+
12
+ ## 实现步骤
13
+
14
+ 1. <例如:新增 VO,字段来自哪张表>
15
+ 2. <例如:Service 组装查询条件、分页>
16
+ 3. <例如:Mapper/SQL,关联哪张表>
17
+ 4. <例如:Controller 新增端点;路由顺序等约束>
18
+ 5. <例如:复用现网某方法,保持详情接口不变>
19
+
20
+ ## 涉及文件
21
+
22
+ - `<路径>` — <新增/修改,一句话>
23
+ - `<路径>` — <…>
24
+
25
+ ## 数据与规则
26
+
27
+ (几条 bullet,不写长表)
28
+
29
+ - 数据源:`<表名>`,关键字段 …
30
+ - 筛选:默认范围 …;与 `deathMonth`、住院号 AND …
31
+ - 注意:<易错点,如固定路径须写在 `{id}` 路由之前>
32
+
33
+ ## 验收
34
+
35
+ - [ ] <接口行为可对照 PRD 检查>
36
+ - [ ] <…>
37
+ ```
38
+
39
+ ## 不要写
40
+
41
+ 完整接口参数表、JSON 示例、大段 SQL、mermaid、风险大表——参数示例放 `API.md`,实现细节开发时再展开。
@@ -0,0 +1,27 @@
1
+ ## 适用范围
2
+
3
+ 前端工程师在 `API.md` 就绪后,编写 `.apm/sessions/<会话ID>/docs/FRONTEND.md`。
4
+
5
+ 这是一份 **Plan**(实现计划),不是技术方案:说清楚改什么、分几步做、动哪些文件;接口细节读 `API.md`,代码细节留给 `apm-dev`。
6
+
7
+ ---
8
+
9
+ ## 工作流程
10
+
11
+ 1. **Read** `docs/PRD.md`、`docs/API.md`;`API.md` 不存在则退出并 @ 后端。
12
+ 2. 按需调研代码库,确认改动入口。
13
+ 3. **Read** `.apm/skills/apm-write-frontend-plan/plan-template.md`,按模板 **Write** `docs/FRONTEND.md`。
14
+ 4. 执行 `apm sync-document <会话ID> --file=FRONTEND.md`,回复消息通知可进入开发。
15
+
16
+ ---
17
+
18
+ ## 写作要求
19
+
20
+ - 篇幅宜短(通常 **30 ~ 80 行**),宁可少写,不要铺表、不要伪代码。
21
+ - 接口只写「何时调、关键点」,不复制 `API.md`。
22
+ - 产品语言描述交互;文件路径只出现在「涉及文件」。
23
+ - 禁止流水账。
24
+
25
+ ## 何时使用
26
+
27
+ 协作流程阶段 3;或用户要求写 `FRONTEND.md`。
@@ -0,0 +1,40 @@
1
+ # Plan 模板
2
+
3
+ 对齐 Cursor Plan:背景 → 实现步骤 → 涉及文件 → 接口要点 → 验收。全文尽量一页以内。
4
+
5
+ ```markdown
6
+ # <功能名称>
7
+
8
+ ## 背景
9
+
10
+ <2 ~ 3 句:要做什么、限定在哪个页面/场景、现网什么保持不变>
11
+
12
+ ## 实现步骤
13
+
14
+ 1. <第一步:例如新增某弹窗组件,负责什么交互>
15
+ 2. <第二步:例如改某页面条件分支,触发条件是什么>
16
+ 3. <第三步:对接 API.md 中的某某接口,调用时机>
17
+ 4. <第四步:回填/校验/联调>
18
+
19
+ ## 涉及文件
20
+
21
+ - `<路径>` — <改什么>
22
+ - `<路径>` — <改什么>
23
+
24
+ ## 接口
25
+
26
+ (不复制 API.md;几条 bullet 即可)
27
+
28
+ - 列表:<何时请求、首屏传什么>
29
+ - 详情:<选中后怎么走>
30
+ - 注意:<禁止用列表数据代替详情等,引用 API.md 章节>
31
+
32
+ ## 验收
33
+
34
+ - [ ] <对照 PRD 的可检查项>
35
+ - [ ] <…>
36
+ ```
37
+
38
+ ## 不要写
39
+
40
+ 伪代码、参数表、JSON 示例、mermaid(除非步骤说不清)、风险大表、模块拆分专章。