ai-project-manage-cli 4.0.10 → 4.0.11

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
@@ -590,6 +590,40 @@ async function runUploadArtifact(requirementId, workspaceDir) {
590
590
  import { writeFileSync as writeFileSync3 } from "fs";
591
591
  import { join as join5 } from "path";
592
592
  import { stringify as yamlStringify } from "yaml";
593
+
594
+ // src/pull-reviews-xml.ts
595
+ function escapeForCdata(text) {
596
+ return text.replace(/\]\]>/g, "]]]]><![CDATA[>");
597
+ }
598
+ function buildReviewsXml(reviews) {
599
+ const lines = ["<reviews>"];
600
+ for (const review of reviews) {
601
+ lines.push(
602
+ ` <review id="${xmlEscape(review.id)}" model="${xmlEscape(
603
+ review.model ?? ""
604
+ )}" stance="${xmlEscape(review.stance ?? "")}" memberRole="${xmlEscape(
605
+ review.memberRole
606
+ )}">`
607
+ );
608
+ for (const item of review.items) {
609
+ const kind = item.kind.toLowerCase();
610
+ lines.push(
611
+ ` <item id="${xmlEscape(item.id)}" start="${item.startLine}" end="${item.endLine}" kind="${xmlEscape(kind)}" status="open">`
612
+ );
613
+ lines.push(
614
+ ` <body><![CDATA[${escapeForCdata(item.body ?? "")}]]></body>`
615
+ );
616
+ const reply = item.reply?.trim() ?? "";
617
+ lines.push(` <reply><![CDATA[${escapeForCdata(reply)}]]></reply>`);
618
+ lines.push(" </item>");
619
+ }
620
+ lines.push(" </review>");
621
+ }
622
+ lines.push("</reviews>", "");
623
+ return lines.join("\n");
624
+ }
625
+
626
+ // src/commands/pull.ts
593
627
  var PULL_ARTIFACT_FILE_NAMES = ["api.md", "backend.md"];
594
628
  function normalizeArtifactPath(fileName) {
595
629
  return fileName.trim().replace(/\\/g, "/").replace(/^\/+/, "");
@@ -656,7 +690,7 @@ function unknownArrayToXml(rootName, itemName, items) {
656
690
  lines.push(`</${rootName}>`, "");
657
691
  return lines.join("\n");
658
692
  }
659
- function escapeForCdata(text) {
693
+ function escapeForCdata2(text) {
660
694
  return text.replace(/\]\]>/g, "]]]]><![CDATA[>");
661
695
  }
662
696
  function defectsToXml(defects) {
@@ -668,12 +702,12 @@ function defectsToXml(defects) {
668
702
  lines.push(` <defect id="${xmlEscape(d.id)}">`);
669
703
  lines.push(` <status>${xmlEscape(d.status)}</status>`);
670
704
  lines.push(
671
- ` <current><![CDATA[${escapeForCdata(
705
+ ` <current><![CDATA[${escapeForCdata2(
672
706
  d.currentState ?? ""
673
707
  )}]]></current>`
674
708
  );
675
709
  lines.push(
676
- ` <expected><![CDATA[${escapeForCdata(
710
+ ` <expected><![CDATA[${escapeForCdata2(
677
711
  d.expectedEffect ?? ""
678
712
  )}]]></expected>`
679
713
  );
@@ -705,25 +739,7 @@ async function runPull(requirementId, workspaceDir) {
705
739
  "utf8"
706
740
  );
707
741
  writeFileSync3(join5(WORKITEMS_DIR, "prd.md"), req2.content || "", "utf8");
708
- const reviews = data.reviews ?? [];
709
- const reviewsXml = [
710
- "<reviews>",
711
- ...reviews.map((r) => {
712
- return [
713
- ` <review id="${xmlEscape(r.id)}">`,
714
- ` <model>${xmlEscape(r.model ?? "")}</model>`,
715
- ` <content>`,
716
- `${xmlEscape(r.content ?? "")}`,
717
- ` </content>`,
718
- ` <reply>`,
719
- `${xmlEscape(r.reply ?? "")}`,
720
- ` </reply>`,
721
- " </review>"
722
- ].join("\n");
723
- }),
724
- "</reviews>",
725
- ""
726
- ].join("\n");
742
+ const reviewsXml = buildReviewsXml(data.reviews ?? []);
727
743
  writeFileSync3(join5(WORKITEMS_DIR, "reviews.xml"), reviewsXml, "utf8");
728
744
  const defectsXml = defectsToXml(data.defects ?? []);
729
745
  writeFileSync3(join5(WORKITEMS_DIR, "defect.xml"), defectsXml, "utf8");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ai-project-manage-cli",
3
- "version": "4.0.10",
3
+ "version": "4.0.11",
4
4
  "description": "命令行工具:后续用于调用平台后端 API 完成运维与自动化操作",
5
5
  "type": "module",
6
6
  "private": false,
@@ -21,12 +21,35 @@ description: 根据需求 ID 读取 prd.md 与 reviews.xml,润色为简短、
21
21
 
22
22
  正文骨架、需求点写法、示例与落盘自检见 **[apm-refine-template.md](./apm-refine-template.md)**。
23
23
 
24
+ ## `reviews.xml` 格式(v2)
25
+
26
+ `apm pull` 生成的 `reviews.xml` 为**行级条目**,仅含 **OPEN** 状态、且 **有 `reply` 的条目**才需要合并进正文(无 `reply` 或空 `reply` 的条目本次不处理)。
27
+
28
+ ```xml
29
+ <reviews>
30
+ <review id="..." model="Auto" stance="frontend" memberRole="FE">
31
+ <item id="..." start="44" end="55" kind="clarify" status="open">
32
+ <body><![CDATA[评审正文…]]></body>
33
+ <reply><![CDATA[产品已拍板口径…]]></reply>
34
+ </item>
35
+ </review>
36
+ </reviews>
37
+ ```
38
+
39
+ | 属性 / 节点 | 含义 |
40
+ | --------------- | --------------------------------------------------------------------------------------- |
41
+ | `start` / `end` | 锚定 `prd.md` 行号(1-based,闭区间),合并时优先据此定位段落 |
42
+ | `kind` | `clarify` / `difficulty` / `business` / `coordination`;**仅处理含非空 `reply` 的条目** |
43
+ | `body` | 评审意见(辅助理解,不作为正文来源) |
44
+ | `reply` | 产品回复,**并入正文**的权威补充 |
45
+
24
46
  ## 合并原则
25
47
 
26
- 1. **正文 = 需求原文 + 已拍板补充**:合并 `prd.md` `reviews.xml` `reply` 的明确口径;未说到的保持原文或不写,不自行扩需求。
27
- 2. **评审辅助理解 `reply`**;与补充冲突时以补充为准;未回应的评审本次不处理。
28
- 3. **同一议题只写一处**;合并后篇幅短于或接近原文。
29
- 4. **图片**:原文图片语法原样保留;理解图片后把规则写入对应需求点的文字描述。
48
+ 1. **正文 = 需求原文 + 已拍板补充**:将各 `<item>` 中非空 `reply` 并入 `prd.md` 对应行区间(或该行所在需求点);未回复的评审本次不处理。
49
+ 2. **定位**:优先按 `start`–`end` 行号找到段落;行号区间与章节标题不一致时,以**业务语义**归入最近的需求点,**不要**机械插入到错误章节。
50
+ 3. **冲突**:`reply` 与原文冲突时以 `reply` 为准;`coordination` 类条目通常无 `reply`,润色时忽略。
51
+ 4. **同一议题只写一处**;合并后篇幅短于或接近原文。
52
+ 5. **图片**:原文图片语法原样保留;理解图片后把规则写入对应需求点的文字描述。
30
53
 
31
54
  ## 执行步骤
32
55
 
@@ -35,22 +58,22 @@ description: 根据需求 ID 读取 prd.md 与 reviews.xml,润色为简短、
35
58
  1. **Read** `.apm/workitems/<需求ID>/prd.md`
36
59
  2. **Read** `.apm/workitems/<需求ID>/reviews.xml`(不存在则视为无评审)
37
60
  3. **理解 PRD 中的图片**(有则执行):
38
- - 图片为标准 Markdown:`![描述](展示URL "相对路径")` — 圆括号内为展示 URL,**引号内**为相对路径(如 `attachments/图1.png`)
39
- - **优先读本地附件**:`.apm/workitems/<需求ID>/<相对路径>`;存在则用该文件理解图片内容
40
- - **本地不存在时**:再用圆括号内的展示 URL 下载图片后理解
41
- - 将理解到的界面/字段/交互规则写入对应需求点;回写时保留原 `![…](… "…")` 语法不变
61
+ - 图片为标准 Markdown:`![描述](展示URL "相对路径")`
62
+ - **优先读本地附件**:`.apm/workitems/<需求ID>/<相对路径>`
63
+ - **本地不存在时**:再用展示 URL 下载后理解
64
+ - 回写时保留原 `![…](… "…")` 语法不变
42
65
 
43
66
  `prd.md` 不可读 → 终止,步骤 5 注明原因。
44
67
 
45
68
  ### 步骤 2:润色
46
69
 
47
- **不向用户追问。** `reply` 视为补充信息。
70
+ **不向用户追问。** 非空 `reply` 视为已拍板补充。
48
71
 
49
- 1. `reply` 中已拍板内容并入正文对应需求点。
50
- 2. **Read** **[apm-refine-template.md](./apm-refine-template.md)**,按骨架、写法表与示例组织全文。
51
- 3. 写完后按模板 **「落盘前自检」** 核对,不满足则再改一版。
52
- 4. **待确认**:仅当补充信息原文含「待定」「再议」等时追加该章,每条一行归纳用户原意。
53
- 5. **局部替换**:只改涉及段落时,保留其余章节,重写为完整段落。
72
+ 1. 遍历 `reviews.xml` 中每个 `<item>`:若 `<reply>` 非空,将口径并入 `prd.md` 对应段落(参考 `start`/`end`)。
73
+ 2. **Read** **[apm-refine-template.md](./apm-refine-template.md)**,按骨架组织全文。
74
+ 3. 写完后按模板 **「落盘前自检」** 核对。
75
+ 4. **待确认**:仅当补充信息原文含「待定」「再议」等时追加该章。
76
+ 5. **局部替换**:只改涉及段落时,保留其余章节。
54
77
 
55
78
  ### 步骤 3:回写
56
79
 
@@ -60,6 +83,8 @@ description: 根据需求 ID 读取 prd.md 与 reviews.xml,润色为简短、
60
83
 
61
84
  仓库根目录执行:`apm refine <需求ID>`
62
85
 
86
+ (平台会将旧正文追加到 `contentHistory`,并将全部 OPEN 评审标为已解决;侧栏不再展示这些条目。)
87
+
63
88
  ### 步骤 5:回复用户
64
89
 
65
90
  **仅**输出一张状态表,表格外无其他文字: