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 +38 -22
- package/package.json +1 -1
- package/template/skills/apm-refine/SKILL.md +39 -14
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
|
|
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[${
|
|
705
|
+
` <current><![CDATA[${escapeForCdata2(
|
|
672
706
|
d.currentState ?? ""
|
|
673
707
|
)}]]></current>`
|
|
674
708
|
);
|
|
675
709
|
lines.push(
|
|
676
|
-
` <expected><![CDATA[${
|
|
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
|
|
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
|
@@ -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. **正文 = 需求原文 +
|
|
27
|
-
2.
|
|
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:``
|
|
39
|
-
- **优先读本地附件**:`.apm/workitems/<需求ID
|
|
40
|
-
-
|
|
41
|
-
-
|
|
61
|
+
- 图片为标准 Markdown:``
|
|
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.
|
|
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
|
**仅**输出一张状态表,表格外无其他文字:
|