ai-project-manage-cli 4.0.9 → 4.0.10

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
@@ -100,6 +100,10 @@ var requestConfig = {
100
100
  method: "POST",
101
101
  path: "/cli/requirements/comment"
102
102
  }),
103
+ commentStructured: defineEndpoint({
104
+ method: "POST",
105
+ path: "/cli/requirements/comment-structured"
106
+ }),
103
107
  refine: defineEndpoint({
104
108
  method: "POST",
105
109
  path: "/cli/requirements/refine"
@@ -237,15 +241,120 @@ function resolveCwdPath(file) {
237
241
  return resolve(process.cwd(), file);
238
242
  }
239
243
 
244
+ // src/structured-review-yaml.ts
245
+ import { parse } from "yaml";
246
+ var KIND_MAP = {
247
+ clarify: "CLARIFY",
248
+ difficulty: "DIFFICULTY",
249
+ business: "BUSINESS",
250
+ coordination: "COORDINATION",
251
+ CLARIFY: "CLARIFY",
252
+ DIFFICULTY: "DIFFICULTY",
253
+ BUSINESS: "BUSINESS",
254
+ COORDINATION: "COORDINATION"
255
+ };
256
+ var STANCE_VALUES = /* @__PURE__ */ new Set(["frontend", "backend", "fullstack"]);
257
+ function asRecord(value) {
258
+ if (value && typeof value === "object" && !Array.isArray(value)) {
259
+ return value;
260
+ }
261
+ throw new Error("YAML \u7ED3\u6784\u65E0\u6548\uFF1A\u9700\u8981\u5BF9\u8C61\u6839\u8282\u70B9");
262
+ }
263
+ function parseAnchor(anchor, index) {
264
+ const rec = asRecord(anchor);
265
+ const start = Number(rec.start ?? rec.startLine);
266
+ const end = Number(rec.end ?? rec.endLine);
267
+ if (!Number.isInteger(start) || !Number.isInteger(end) || start < 1 || end < start) {
268
+ throw new Error(`items[${index}].anchor \u884C\u53F7\u65E0\u6548\uFF08\u9700 1 \u2264 start \u2264 end\uFF09`);
269
+ }
270
+ return { start, end };
271
+ }
272
+ function parseKind(raw, index) {
273
+ const key = String(raw ?? "").trim();
274
+ const kind = KIND_MAP[key] ?? KIND_MAP[key.toLowerCase()];
275
+ if (!kind) {
276
+ throw new Error(
277
+ `items[${index}].kind \u65E0\u6548\uFF0C\u5E94\u4E3A clarify | difficulty | business | coordination`
278
+ );
279
+ }
280
+ return kind;
281
+ }
282
+ function parseStructuredReviewYaml(raw, cliModel) {
283
+ let doc;
284
+ try {
285
+ doc = parse(raw);
286
+ } catch (e) {
287
+ const msg = e instanceof Error ? e.message : String(e);
288
+ throw new Error(`YAML \u89E3\u6790\u5931\u8D25\uFF1A${msg}`);
289
+ }
290
+ const root = asRecord(doc);
291
+ const reviewer = asRecord(root.reviewer ?? {});
292
+ const stance = String(reviewer.stance ?? root.stance ?? "").trim();
293
+ if (!STANCE_VALUES.has(stance)) {
294
+ throw new Error("reviewer.stance \u5FC5\u987B\u4E3A frontend\u3001backend \u6216 fullstack");
295
+ }
296
+ const modelFromYaml = reviewer.model != null ? String(reviewer.model).trim() : "";
297
+ const model = cliModel?.trim() || modelFromYaml || null;
298
+ const itemsRaw = root.items;
299
+ if (!Array.isArray(itemsRaw) || itemsRaw.length === 0) {
300
+ throw new Error("items \u4E0D\u80FD\u4E3A\u7A7A");
301
+ }
302
+ const items = itemsRaw.map((entry, index) => {
303
+ const item = asRecord(entry);
304
+ const anchor = parseAnchor(item.anchor ?? item, index);
305
+ const kind = parseKind(item.kind, index);
306
+ const body = typeof item.body === "string" ? item.body.trim() : String(item.body ?? "").trim();
307
+ if (!body) {
308
+ throw new Error(`items[${index}].body \u4E0D\u80FD\u4E3A\u7A7A`);
309
+ }
310
+ if (kind === "COORDINATION" && item.reply != null && String(item.reply).trim()) {
311
+ throw new Error(`items[${index}] \u8054\u8C03\u4F9D\u8D56\uFF08coordination\uFF09\u4E0D\u5141\u8BB8 reply`);
312
+ }
313
+ return {
314
+ startLine: anchor.start,
315
+ endLine: anchor.end,
316
+ kind,
317
+ body
318
+ };
319
+ });
320
+ return { stance, model, items };
321
+ }
322
+ function inferCommentFormat(filePath, explicit) {
323
+ if (explicit === "structured" || explicit === "legacy") {
324
+ return explicit;
325
+ }
326
+ const lower = filePath.toLowerCase();
327
+ if (lower.endsWith(".yaml") || lower.endsWith(".yml")) {
328
+ return "structured";
329
+ }
330
+ return "legacy";
331
+ }
332
+
240
333
  // src/commands/comment.ts
241
- async function runComment(requirementId, file, model) {
334
+ async function runComment(requirementId, file, options) {
242
335
  const cfg = await ensureLoggedConfig();
243
- const content = readFileSync2(resolveCwdPath(file), "utf8");
336
+ const resolvedPath = resolveCwdPath(file);
337
+ const raw = readFileSync2(resolvedPath, "utf8");
338
+ const format = inferCommentFormat(resolvedPath, options?.format);
244
339
  const api = createApmApiClient(cfg);
340
+ if (format === "structured") {
341
+ const parsed = parseStructuredReviewYaml(raw, options?.model);
342
+ const data2 = await api.cliRequirements.commentStructured({
343
+ requirementId,
344
+ stance: parsed.stance,
345
+ model: parsed.model,
346
+ items: parsed.items
347
+ });
348
+ console.log(JSON.stringify(data2, null, 2));
349
+ return;
350
+ }
351
+ if (!raw.trim()) {
352
+ throw new Error("\u8BC4\u8BBA\u6B63\u6587\u4E0D\u80FD\u4E3A\u7A7A");
353
+ }
245
354
  const data = await api.cliRequirements.comment({
246
355
  requirementId,
247
- content,
248
- model
356
+ content: raw,
357
+ model: options?.model
249
358
  });
250
359
  console.log(JSON.stringify(data, null, 2));
251
360
  }
@@ -2173,9 +2282,20 @@ function buildProgram() {
2173
2282
  ).action(async (requirementId, opts) => {
2174
2283
  await runBranch(requirementId, { message: opts.message });
2175
2284
  });
2176
- program.command("comment").description("POST /api/cli/requirements/comment\uFF08\u6B63\u6587\u6765\u81EA\u6587\u4EF6\uFF09").argument("<requirementId>", "\u9700\u6C42 ID").requiredOption("--file <path>", "\u8BC4\u8BBA\u6B63\u6587\u6587\u4EF6\u8DEF\u5F84").option("--model <model>", "\u8BC4\u8BBA\u6A21\u578B").action(
2285
+ program.command("comment").description(
2286
+ "\u63D0\u4EA4\u9700\u6C42\u8BC4\u5BA1\uFF1Alegacy \u4E3A\u6574\u7BC7 Markdown\uFF08POST \u2026/comment\uFF09\uFF1Bstructured \u4E3A\u884C\u7EA7 YAML\uFF08POST \u2026/comment-structured\uFF09"
2287
+ ).argument("<requirementId>", "\u9700\u6C42 ID").requiredOption(
2288
+ "--file <path>",
2289
+ "\u8BC4\u8BBA\u6587\u4EF6\u8DEF\u5F84\uFF08.yaml/.yml \u9ED8\u8BA4 structured\uFF09"
2290
+ ).option(
2291
+ "--format <format>",
2292
+ "structured | legacy\uFF08\u9ED8\u8BA4 legacy\uFF1B.yaml/.yml \u672A\u6307\u5B9A\u65F6\u89C6\u4E3A structured\uFF09"
2293
+ ).option("--model <model>", "\u8BC4\u8BBA\u6A21\u578B\uFF08\u8986\u76D6 YAML reviewer.model\uFF09").action(
2177
2294
  async (requirementId, options) => {
2178
- await runComment(requirementId, options.file, options.model);
2295
+ await runComment(requirementId, options.file, {
2296
+ model: options.model,
2297
+ format: options.format
2298
+ });
2179
2299
  }
2180
2300
  );
2181
2301
  program.command("refine").description("POST /api/cli/requirements/refine\uFF08\u6B63\u6587\u6765\u81EA\u6587\u4EF6\uFF09").argument("<requirementId>", "\u9700\u6C42 ID").action(async (requirementId) => {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-project-manage-cli",
3
- "version": "4.0.9",
3
+ "version": "4.0.10",
4
4
  "description": "命令行工具:后续用于调用平台后端 API 完成运维与自动化操作",
5
5
  "type": "module",
6
6
  "private": false,
@@ -9,17 +9,17 @@ description: 结合本仓库上下文对需求做结构化评审,只有当用
9
9
 
10
10
  ## 评审目标
11
11
 
12
- 帮助产品经理**梳理需求边界、发现文档中未写清的口径**,并在实现难度较高时给出提示。**不是**为了凑问题而提问——PRD 已足够清晰且无高难度改造时,仅写锚定即可,不必强行列出待澄清项。
12
+ 帮助产品经理**梳理需求边界、发现文档中未写清的口径**,并在实现难度较高时给出提示。**不是**为了凑问题而提问——PRD 已足够清晰且无高难度改造时,可只输出少量锚定条目,不必强行列出待澄清项。
13
13
 
14
14
  ## 评审立场
15
15
 
16
- 每轮评审从 **前端 / 后端 / 全栈** 中择定一种立场,按该视角撰写一篇评审正文,并执行一次 `apm comment`。
16
+ 每轮评审从 **前端 / 后端 / 全栈** 中择定一种立场,按该视角输出 **YAML 行级评审**,并执行一次 `apm comment --format=structured`。
17
17
 
18
18
  ### 如何确定立场
19
19
 
20
20
  1. **用户明示**:用户说明「前端 / 后端 / 全栈 / 仅 UI / 仅接口」等,以用户为准。
21
- 2. **用户未说明**:阅读仓库中与该需求相关的现有代码,推断评审者更贴近哪一端;仍无法判断时,**默认前端**,并在 `### 评审人` 中注明「未指定立场,按代码推断为 xxx」。
22
- 3. **全栈**:在同一篇评审中同时覆盖 UI 与流程/数据关注点;同一业务点合并为一条待澄清项(见下文去重规则)。
21
+ 2. **用户未说明**:阅读仓库中与该需求相关的现有代码,推断评审者更贴近哪一端;仍无法判断时,**默认前端**,并在 `reviewer.stance` 使用 `frontend`。
22
+ 3. **全栈**:在同一轮评审中同时覆盖 UI 与流程/数据关注点;同一业务点若需澄清,合并为**一条** `clarify`(不要拆成两句重复问题)。
23
23
 
24
24
  ### 各端关注点(互斥优先,减少并行评审重复)
25
25
 
@@ -31,37 +31,34 @@ description: 结合本仓库上下文对需求做结构化评审,只有当用
31
31
 
32
32
  **去重规则**(按立场选用):
33
33
 
34
- - **前端立场**:只写界面与交互侧待澄清,不写落库/API 契约类问题。
34
+ - **前端立场**:只写界面与交互侧待澄清,不写落库/API 契约类问题(联调诉求用 `kind: coordination`)。
35
35
  - **后端立场**:只写流程、规则与数据侧待澄清,不写控件选型、Tab 布局等纯 UI 问题。
36
- - **全栈立场**:同一业务点若 UI 与规则都涉及,合并为**一条**待澄清(优先产品能直接回答的业务表述),**不要**拆成两句意思重复的问题。
36
+ - **全栈立场**:同一业务点合并为一条 `clarify`,**不要**拆成两句意思重复的问题。
37
37
 
38
38
  ## 表述规范(产品经理可读)
39
39
 
40
- 1. **产品语言优先**:用「医德考评弹窗」「行风办审批页」「列表页分类筛选」等说法;**禁止**用文件路径、组件名、函数名作为论据或问题主体(`prd.md` 行号锚定除外)。
40
+ 1. **产品语言优先**:用「医德考评弹窗」「行风办审批页」等说法;**禁止**用文件路径、组件名、函数名作为论据主体(行号锚定除外)。
41
41
  2. **后端可略宽**:必要时可写**表名 / 主表字段名**帮助产品理解数据口径,但仍避免贴大段代码或接口路径。
42
- 3. **锚定 PRD**:每条评审用 **`prd.md` 行号**指回原文(如 `prd.md` L44–L55),**不**另写半句概括或复述需求内容。
43
- 4. **问题 = 文档缺口**:只写 PRD **未写清**且**影响本端理解边界**的点(例如:只说了改字段名,没说哪些页面/弹窗要改;只说了新增 Tab,没说各审批节点能否编辑)。已写清楚的规则不要重复质疑。
42
+ 3. **锚定 PRD**:每条 `items[]` 必须写 **`anchor: { start, end }`**,与 Read 读到的 `prd.md` 行号一致;**不**在 `body` 里复述该段需求原文。
43
+ 4. **问题 = 文档缺口**:只写 PRD **未写清**且**影响本端理解边界**的点。已写清楚的规则不要重复质疑。
44
44
 
45
- ## 不在本轮的问题项
45
+ ## 不在「待澄清」里写的内容
46
46
 
47
- 以下属于**技术评审 / 联调**阶段,**不得**写入「待澄清」:
47
+ 以下不得使用 `kind: clarify`,若需记录则用 **`kind: coordination`**(且必须有行号锚定):
48
48
 
49
49
  - 接口字段名、请求体结构、子表编码
50
50
  - 前后端谁传哪个参数、现有 edit 接口是否够用
51
51
  - 与现网代码实现对齐的改造方案
52
52
 
53
- 若**前端**评审时确知实现依赖某些后端信息,可在全文末尾单独增加 **`## 联调依赖项`**(见 `output-template.md`),**简短**记录接口/字段等诉求;**不用产品回复,仅作记录,待技术评审阶段处理**。
53
+ `coordination` **不用产品回复**,仅作记录,待技术评审阶段处理。若同一行区间既要澄清又要联调,**拆成两条** `items`。
54
54
 
55
55
  ## 评审原则
56
56
 
57
- 1. **产品视角优先**:用户侧影响、业务闭环、边界与风险;避免术语堆砌。
58
- 2. **与仓库对齐(能力边界)**:阅读 `AGENTS.md`、`.apm/product-capability-inventory` 及与需求相关的代码,判断需求是否超出现有能力。**禁止**用代码证明 PRD 对错;**禁止**根据代码**猜测** PRD 未写清的口径——口径不清归入「待澄清」。
59
- 3. **业务合理性(按需)**:仅当方案与业务目标明显冲突或明显非较优解时,写 `- **业务合理性:**`;合理则不写。
60
- 4. **待澄清与实现难度(按需)**:
61
- - 存在**阻碍本端理解或验收**的文档缺口 → 写 `- **待澄清:**`。
62
- - 实现难度**高**时额外写 `- **实现难度高:**`(用产品语言简述原因);一般难度**不写**该字段。
63
- - **高难度**指:对现有代码改造面大(涉及文件/模块较多),或业务逻辑变化影响较大。
64
- 5. **澄清优先但不泛问**:不阻塞理解则不提问;不问可从 PRD 或常识推断的琐碎点。
57
+ 1. **产品视角优先**:用户侧影响、业务闭环、边界与风险。
58
+ 2. **与仓库对齐(能力边界)**:阅读 `AGENTS.md`、`.apm/product-capability-inventory` 及与需求相关的代码,判断改造面。**禁止**用代码证明 PRD 对错;口径不清归入 `clarify`。
59
+ 3. **`kind: business`**:仅当方案与业务目标明显冲突或明显非较优解时使用。
60
+ 4. **`kind: difficulty`**:仅当改造面大或业务逻辑影响大时使用。
61
+ 5. **澄清优先但不泛问**:不阻塞理解则不提问。
65
62
 
66
63
  ## 执行步骤
67
64
 
@@ -69,35 +66,36 @@ description: 结合本仓库上下文对需求做结构化评审,只有当用
69
66
 
70
67
  1. 使用 **Read** 读取 `.apm/workitems/<需求ID>/prd.md`。
71
68
  2. 当 PRD 涉及到图片链接时,自动下载对应的图片,存放在 `.apm/workitems/<需求ID>/images/<对应图片名称>.png`,并理解图片内容。
72
- 3. 若 Read **失败**(文件不存在或无法读取):本步骤记为失败,**终止**,不执行后续步骤。
69
+ 3. 若 Read **失败**:本步骤记为失败,**终止**,不执行后续步骤。
73
70
 
74
71
  ### 步骤 2:读代码
75
72
 
76
- 阅读仓库源码以及 `.apm/product-capability-inventory` 中与需求相关的条目,用于**理解现网能力与边界、判断改造面**,不展开无关模块。**读代码是为了校准评审立场和实现难度,不是为了把代码细节写进评审正文。**
73
+ 阅读仓库源码以及 `.apm/product-capability-inventory` 中与需求相关的条目,用于**理解现网能力与边界、判断改造面**。
77
74
 
78
75
  ### 步骤 3:评审与提交
79
76
 
80
- 1. **锁定立场**(见上文「评审立场」),在正文顶格写 `### 评审人` + 模型名 + 立场(如「Auto(前端)」)。
81
- 2. **拆条**:按 PRD 章节拆条(标题序号与 PRD 一致即可);每条写 **`prd.md` 行号锚定**,按需追加待澄清、实现难度高、业务合理性等字段,**不**复述该段需求原文。
82
- 3. **成文**:按 **[output-template.md](./output-template.md)** Markdown;**不含**「隐性知识沉淀」段落。
83
- 4. 使用 **Write** 写入临时文件(建议绝对路径 `/tmp/apm-review-<需求ID>.md`)。
77
+ 1. **锁定立场**,填入 YAML `reviewer.stance`(`frontend` / `backend` / `fullstack`)。
78
+ 2. **拆条**:每条对应 `prd.md` 连续行号;按需设置 `kind`(`clarify` / `difficulty` / `business` / `coordination`)与 `body`。
79
+ 3. **成文**:按 **[output-template.md](./output-template.md)** YAML。
80
+ 4. 使用 **Write** 写入临时文件(建议 `/tmp/apm-review-<需求ID>.yaml`)。
84
81
  5. **自检**:
85
- - 全文是否始终符合已锁定的评审立场;
86
- - 待澄清项是否均为产品能回答的业务/交互/规则问题,而非接口契约;
87
- - 是否误用代码路径、组件名作主要表述;
88
- - 一般难度需求是否误写了「实现难度高」;
89
- - 是否存在语义重复的待澄清项(应合并或删去一条)。
90
- 6. 在项目根目录执行一次:`apm comment <需求ID> --file=<临时文件绝对路径> --model=<评论使用的模型名称>`。
82
+ - 每条均有 `anchor`,且无全篇评审;
83
+ - `stance` 与立场一致;
84
+ - `clarify` 均为产品可回答的业务问题;
85
+ - `coordination` 无 `reply` 字段;
86
+ - 无重复语义的条目。
87
+ 6. 在项目根目录执行:
88
+
89
+ `apm comment <需求ID> --file=<临时文件绝对路径> --format=structured --model=<模型名>`
90
+
91
91
  7. 命令结束后 **删除临时文件**(失败时若需排错可保留,默认仍删除)。
92
92
 
93
93
  ### 步骤 4:回复用户
94
94
 
95
- 对用户可见回复 **仅限一张 Markdown 表格**,汇总各步**结果**(成功/失败、摘要)。**不得**在表格外输出任何其他内容(完整评审正文已通过 `apm comment` 提交)。
96
-
97
- 建议表头:
95
+ 对用户可见回复 **仅限一张 Markdown 表格**,汇总各步**结果**。**不得**在表格外输出其他内容。
98
96
 
99
- | 步骤 | 结果 | 说明 |
100
- | ----------------- | ----------- | ---------------------------------------------------------------------------------------------- |
101
- | 1. 读取 prd.md | 成功 / 失败 | 实际路径;失败时写原因 |
102
- | 2. 评审与 comment | 成功 / 失败 | 临时文件(已删可写「已清理」);`apm comment` 情况;**注明本轮评审立场**(前端 / 后端 / 全栈) |
103
- | 3. 清理临时文件 | 成功 / 失败 | — |
97
+ | 步骤 | 结果 | 说明 |
98
+ | ----------------- | ----------- | ------------------------------------------------------------------------------- |
99
+ | 1. 读取 prd.md | 成功 / 失败 | 实际路径;失败时写原因 |
100
+ | 2. 评审与 comment | 成功 / 失败 | 临时文件;`apm comment` 情况;**注明 stance**(frontend / backend / fullstack) |
101
+ | 3. 清理临时文件 | 成功 / 失败 | — |
@@ -1,75 +1,64 @@
1
- # 评审输出模板
2
-
3
- 顶格写评审人,随后按 PRD 章节逐条输出。全文使用**产品经理可读**的表述;锚定 PRD 时**只写行号**,不复述需求。
4
-
5
- ```markdown
6
- ### 评审人
7
-
8
- {模型名}({前端 / 后端 / 全栈})
9
-
10
- ### 需求点 1
11
-
12
- - **锚定:** `prd.md` L44–L55
13
-
14
- - **待澄清:**
15
- - {文档未写清、影响本端理解边界的点;无则整段省略}
16
- - {示例(前端):「类型」隐藏后,统计分析页的「分类」筛选是否一并去掉,导出是否统一走临床模板}
17
- - {示例(后端):新建单据「默认临床」对应字典编码/文案未写明,历史非临床记录在列表是否仍展示原类型}
18
-
19
- ### 需求点 2
20
-
21
- - **锚定:** `prd.md` L59–L67
22
-
23
- {PRD 已写清且改造面不大 仅锚定,不写其他字段}
24
-
25
- ### 需求点 3
26
-
27
- - **锚定:** `prd.md` L69–L81
28
-
29
- - **待澄清:**
30
- - {仅列本端视角的缺口}
31
-
32
- ### 需求点 4(业务合理性:仅不合理时出现)
33
-
34
- - **锚定:** `prd.md` L83–L106
35
-
36
- - **业务合理性:**
37
-
38
- - {为何与业务目标冲突或方案非较优;可给替代思路}
39
-
40
- - **待澄清:**
41
- - {若仍有文档缺口}
42
-
43
- ### 需求点 5
44
-
45
- - **锚定:** `prd.md` L108–L132
46
-
47
- - **实现难度高:**
48
- - {用产品语言说明:改造涉及哪些页面/流程/规则,为何面大;示例:加/减分明细需按审批层级分块改造,涉及弹窗表单、子表编辑与汇总多处联动,业务规则变化面大}
49
-
50
- ---
51
-
52
- ## 联调依赖项
53
-
54
- {**仅当前端评审**且存在联调前置信息时出现;**可选**,无则省略整节}
55
-
56
- > 不用产品回复,仅记录,待技术评审阶段处理。
1
+ # 评审输出模板(YAML · structured)
2
+
3
+ 使用 **YAML** 文件提交行级评审;每条必须锚定 `prd.md` 行号。全文使用**产品经理可读**的表述;**不**在 `body` 里复述需求原文。
4
+
5
+ ## 文件示例
6
+
7
+ ```yaml
8
+ reviewer:
9
+ model: Auto
10
+ stance: frontend # frontend | backend | fullstack
11
+
12
+ items:
13
+ - anchor: { start: 44, end: 55 }
14
+ kind: clarify
15
+ body: |
16
+ - 「类型」隐藏后,统计分析页的「分类」筛选是否一并去掉,导出是否统一走临床模板
17
+
18
+ - anchor: { start: 59, end: 67 }
19
+ kind: clarify
20
+ body: |
21
+ - (PRD 已写清且无缺口时可省略该条,不要写「仅锚定」空条目)
22
+
23
+ - anchor: { start: 83, end: 106 }
24
+ kind: business
25
+ body: |
26
+ - 方案与业务目标冲突的原因;可给替代思路
27
+
28
+ - anchor: { start: 108, end: 132 }
29
+ kind: difficulty
30
+ body: |
31
+ - 用产品语言说明改造面:涉及哪些页面/流程/规则,为何面大
32
+
33
+ - anchor: { start: 44, end: 55 }
34
+ kind: coordination
35
+ body: |
36
+ - 需与后端对齐的接口/字段诉求(不用产品回复)
37
+ ```
57
38
 
58
- {简短条目,记录后续需与后端对齐的接口/字段/能力诉求}
39
+ ## kind 与技能表述
59
40
 
60
- - 一票否决:需明确存储位置(主表字段或独立子表)及回显字段,以便行风办页勾选与查看页展示。
61
- - 加/减分明细分级:需明确各级分值对应落库字段,以便分块编辑与汇总。
62
- ```
41
+ | `kind`(YAML 小写) | 含义 | 说明 |
42
+ | ------------------- | ---------- | ---------------------------- |
43
+ | `clarify` | 待澄清 | 文档未写清、影响本端理解边界 |
44
+ | `difficulty` | 实现难度高 | 仅改造面大或业务逻辑影响大时 |
45
+ | `business` | 业务合理性 | 仅方案明显不合理时 |
46
+ | `coordination` | 联调依赖 | **禁止** `reply`;待技术评审 |
63
47
 
64
48
  ## 字段规则
65
49
 
50
+ | 字段 | 规则 |
51
+ | ----------------- | ---------------------------------------------------------------- |
52
+ | `reviewer.stance` | 必填:`frontend` / `backend` / `fullstack` |
53
+ | `reviewer.model` | 建议填写;可被 CLI `--model` 覆盖 |
54
+ | `anchor` | 必填 `{ start, end }`,1-based 闭区间,与 Read `prd.md` 行号一致 |
55
+ | `body` | 必填;产品语言;**禁止**全篇评审(必须有行号) |
56
+ | 同区间多类型 | 拆成多条 `items`(例如同时 `clarify` 与 `coordination` 各一条) |
66
57
 
67
- | 字段 | 规则 |
68
- | --------- | -------------------------------------------------------------- |
69
- | **锚定** | 每条必有;写 `prd.md` L{起}–L{止},与 Read 工具读到的行号一致;**不**概括、**不**复述该段需求 |
70
- | **待澄清** | 按需;无缺口则**不写**(不要写「无」) |
71
- | **实现难度高** | 按需;**仅**改造面大或业务逻辑影响大时写;一般难度**不写** |
72
- | **业务合理性** | 按需;仅方案明显不合理时出现 |
73
- | **联调依赖项** | 全文最多一节;**不算待澄清**;不用产品回复,仅记录,待技术评审阶段处理 |
58
+ ## 提交命令
74
59
 
60
+ ```bash
61
+ apm comment <需求ID> --file=/tmp/apm-review-<需求ID>.yaml --format=structured --model=<模型名>
62
+ ```
75
63
 
64
+ `.yaml` / `.yml` 扩展名在未指定 `--format` 时默认按 structured 解析。