ai-project-manage-cli 4.0.7 → 4.0.9

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/README.md CHANGED
@@ -4,7 +4,11 @@
4
4
 
5
5
  ## 安装
6
6
 
7
- `npm install ai-project-manage-cli@latest`
7
+ `npm install -g ai-project-manage-cli@latest`
8
+
9
+ ## 更新到最新版
10
+
11
+ `apm update`(等价于 `npm install -g ai-project-manage-cli@latest`)
8
12
 
9
13
  ## 登录
10
14
 
@@ -16,4 +20,4 @@
16
20
 
17
21
  ## 连接服务器
18
22
 
19
- `apm connect`
23
+ `apm connect`
package/dist/index.js CHANGED
@@ -1,9 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
 
3
3
  // src/index.ts
4
- import { readFileSync as readFileSync10 } from "fs";
5
- import { dirname as dirname2, join as join10 } from "path";
6
- import { fileURLToPath as fileURLToPath2 } from "url";
7
4
  import { Command } from "commander";
8
5
 
9
6
  // src/config.ts
@@ -1006,6 +1003,84 @@ async function runRefine(requirementId) {
1006
1003
  console.log(JSON.stringify(data, null, 2));
1007
1004
  }
1008
1005
 
1006
+ // src/commands/update.ts
1007
+ import { spawnSync } from "child_process";
1008
+
1009
+ // src/version.ts
1010
+ import { readFileSync as readFileSync6 } from "fs";
1011
+ import { dirname as dirname2, join as join9 } from "path";
1012
+ import { fileURLToPath as fileURLToPath2 } from "url";
1013
+ var CLI_PACKAGE_NAME = "ai-project-manage-cli";
1014
+ function readCliVersion() {
1015
+ try {
1016
+ const dir = dirname2(fileURLToPath2(import.meta.url));
1017
+ const pkgPath = join9(dir, "..", "package.json");
1018
+ const pkg = JSON.parse(readFileSync6(pkgPath, "utf8"));
1019
+ return pkg.version ?? "0.0.0";
1020
+ } catch {
1021
+ return "0.0.0";
1022
+ }
1023
+ }
1024
+
1025
+ // src/commands/update.ts
1026
+ function registryBaseUrl() {
1027
+ const fromEnv = process.env.npm_config_registry?.trim() || process.env.NPM_CONFIG_REGISTRY?.trim();
1028
+ return (fromEnv || "https://registry.npmjs.org").replace(/\/+$/, "");
1029
+ }
1030
+ async function fetchLatestPublishedVersion() {
1031
+ const url = `${registryBaseUrl()}/${CLI_PACKAGE_NAME}/latest`;
1032
+ try {
1033
+ const res = await fetch(url);
1034
+ if (!res.ok) return null;
1035
+ const data = await res.json();
1036
+ return data.version?.trim() || null;
1037
+ } catch {
1038
+ return null;
1039
+ }
1040
+ }
1041
+ function npmAvailable() {
1042
+ const r = spawnSync("npm", ["--version"], { encoding: "utf8" });
1043
+ return !r.error && r.status === 0;
1044
+ }
1045
+ async function runUpdate() {
1046
+ const current = readCliVersion();
1047
+ const latest = await fetchLatestPublishedVersion();
1048
+ if (latest && current === latest) {
1049
+ console.log(`[apm] \u5DF2\u662F\u6700\u65B0\u7248\u672C ${current}`);
1050
+ return;
1051
+ }
1052
+ if (!npmAvailable()) {
1053
+ console.error(
1054
+ `[apm] \u672A\u627E\u5230 npm\u3002\u8BF7\u5B89\u88C5 Node.js \u540E\u6267\u884C\uFF1Anpm install -g ${CLI_PACKAGE_NAME}@latest`
1055
+ );
1056
+ process.exit(1);
1057
+ }
1058
+ const targetLabel = latest ?? "latest";
1059
+ console.error(
1060
+ `[apm] \u5F53\u524D\u7248\u672C ${current}\uFF0C\u6B63\u5728\u5B89\u88C5 ${CLI_PACKAGE_NAME}@${targetLabel} \u2026`
1061
+ );
1062
+ const install = spawnSync(
1063
+ "npm",
1064
+ ["install", "-g", `${CLI_PACKAGE_NAME}@latest`],
1065
+ { stdio: "inherit", shell: process.platform === "win32" }
1066
+ );
1067
+ if (install.error) {
1068
+ console.error("[apm] \u66F4\u65B0\u5931\u8D25:", install.error.message);
1069
+ process.exit(1);
1070
+ }
1071
+ if (install.status !== 0) {
1072
+ process.exit(install.status ?? 1);
1073
+ }
1074
+ const after = readCliVersion();
1075
+ if (latest && after === latest) {
1076
+ console.log(`[apm] \u5DF2\u66F4\u65B0\u5230 ${after}`);
1077
+ } else {
1078
+ console.log(
1079
+ `[apm] \u66F4\u65B0\u5B8C\u6210\u3002\u82E5\u7248\u672C\u53F7\u672A\u53D8\u5316\uFF0C\u8BF7\u5728\u65B0\u7EC8\u7AEF\u6267\u884C apm -V \u786E\u8BA4\uFF08\u5168\u5C40\u5B89\u88C5\u8DEF\u5F84\u53EF\u80FD\u672A\u5237\u65B0\uFF09`
1080
+ );
1081
+ }
1082
+ }
1083
+
1009
1084
  // src/commands/update-dev-status.ts
1010
1085
  async function runUpdateDevStatus(requirementId, status) {
1011
1086
  const cfg = await ensureLoggedConfig();
@@ -1032,7 +1107,7 @@ async function runUpdateStatus(requirementId, status) {
1032
1107
  import path5 from "node:path";
1033
1108
 
1034
1109
  // src/commands/deploy/internal/apm-config.ts
1035
- import { existsSync as existsSync4, readFileSync as readFileSync6 } from "node:fs";
1110
+ import { existsSync as existsSync4, readFileSync as readFileSync7 } from "node:fs";
1036
1111
  import { resolve as resolve4 } from "node:path";
1037
1112
  function loadApmConfig(options) {
1038
1113
  const p = resolve4(
@@ -1044,7 +1119,7 @@ function loadApmConfig(options) {
1044
1119
  process.exit(1);
1045
1120
  }
1046
1121
  try {
1047
- const raw = readFileSync6(p, "utf8");
1122
+ const raw = readFileSync7(p, "utf8");
1048
1123
  return JSON.parse(raw);
1049
1124
  } catch (e) {
1050
1125
  console.error(`\u65E0\u6CD5\u89E3\u6790 apm.config.json\uFF1A${p}`, e);
@@ -1143,7 +1218,7 @@ import path4 from "node:path";
1143
1218
  import Docker from "dockerode";
1144
1219
 
1145
1220
  // src/commands/deploy/internal/backend-deploy/dockerode-client/connection-options.ts
1146
- import { existsSync as existsSync5, readFileSync as readFileSync7 } from "node:fs";
1221
+ import { existsSync as existsSync5, readFileSync as readFileSync8 } from "node:fs";
1147
1222
  import path from "node:path";
1148
1223
  function asOptionalTlsBuffer(value) {
1149
1224
  if (typeof value !== "string") {
@@ -1156,7 +1231,7 @@ function asOptionalTlsBuffer(value) {
1156
1231
  return void 0;
1157
1232
  }
1158
1233
  if (existsSync5(normalized)) {
1159
- return readFileSync7(normalized);
1234
+ return readFileSync8(normalized);
1160
1235
  }
1161
1236
  const looksLikePath = /[\\/]/.test(normalized) || normalized.endsWith(".pem");
1162
1237
  if (looksLikePath) {
@@ -1366,7 +1441,7 @@ var DockerodeClient = class {
1366
1441
  var createDockerodeClient = (config) => new DockerodeClient(config);
1367
1442
 
1368
1443
  // src/commands/deploy/internal/backend-deploy/dockerode-client/env.ts
1369
- import { existsSync as existsSync6, readFileSync as readFileSync8, statSync as statSync3 } from "node:fs";
1444
+ import { existsSync as existsSync6, readFileSync as readFileSync9, statSync as statSync3 } from "node:fs";
1370
1445
  import path2 from "node:path";
1371
1446
  function stripSurroundingQuotes(value) {
1372
1447
  const t = value.trim();
@@ -1386,7 +1461,7 @@ function loadEnvFromFile(envFilePath) {
1386
1461
  if (!existsSync6(targetPath) || !statSync3(targetPath).isFile()) {
1387
1462
  return {};
1388
1463
  }
1389
- const raw = readFileSync8(targetPath, "utf-8");
1464
+ const raw = readFileSync9(targetPath, "utf-8");
1390
1465
  const result = {};
1391
1466
  for (const line of raw.split(/\r?\n/)) {
1392
1467
  const normalized = line.trim();
@@ -1691,16 +1766,16 @@ import { copyFile, readdir as readdir2, stat } from "node:fs/promises";
1691
1766
  import path7 from "node:path";
1692
1767
 
1693
1768
  // src/commands/deploy/internal/load-apm-dotenv.ts
1694
- import { existsSync as existsSync8, readFileSync as readFileSync9 } from "node:fs";
1695
- import { join as join9 } from "node:path";
1769
+ import { existsSync as existsSync8, readFileSync as readFileSync10 } from "node:fs";
1770
+ import { join as join10 } from "node:path";
1696
1771
  function loadApmDotEnvIfPresent() {
1697
- const p = join9(WORKSPACE_APM_DIR, ".env");
1772
+ const p = join10(WORKSPACE_APM_DIR, ".env");
1698
1773
  if (!existsSync8(p)) {
1699
1774
  return;
1700
1775
  }
1701
1776
  let text;
1702
1777
  try {
1703
- text = readFileSync9(p, "utf8");
1778
+ text = readFileSync10(p, "utf8");
1704
1779
  } catch {
1705
1780
  return;
1706
1781
  }
@@ -2056,16 +2131,6 @@ function registerDeployCommands(program) {
2056
2131
  }
2057
2132
 
2058
2133
  // src/index.ts
2059
- function readCliVersion() {
2060
- try {
2061
- const dir = dirname2(fileURLToPath2(import.meta.url));
2062
- const pkgPath = join10(dir, "..", "package.json");
2063
- const pkg = JSON.parse(readFileSync10(pkgPath, "utf8"));
2064
- return pkg.version ?? "0.0.0";
2065
- } catch {
2066
- return "0.0.0";
2067
- }
2068
- }
2069
2134
  function buildProgram() {
2070
2135
  const program = new Command();
2071
2136
  program.name("apm").description(
@@ -2085,6 +2150,11 @@ function buildProgram() {
2085
2150
  program.command("init").description("\u5728\u5F53\u524D\u5DE5\u4F5C\u76EE\u5F55\u521D\u59CB\u5316 .apm \u6A21\u677F\uFF08\u82E5 .apm \u5DF2\u5B58\u5728\u4E14\u975E\u7A7A\u5219\u62A5\u9519\uFF09").option("--name <name>", "\u5DE5\u4F5C\u76EE\u5F55\u540D\u79F0").action(async (opts) => {
2086
2151
  await runInit(opts.name);
2087
2152
  });
2153
+ program.command("update").description(
2154
+ `\u901A\u8FC7 npm \u5168\u5C40\u5B89\u88C5 ${CLI_PACKAGE_NAME}@latest\uFF0C\u5C06 apm \u66F4\u65B0\u5230 registry \u6700\u65B0\u7248`
2155
+ ).action(async () => {
2156
+ await runUpdate();
2157
+ });
2088
2158
  program.command("pull").description(
2089
2159
  "GET /api/cli/requirements/pull\uFF0C\u540C\u6B65\u6570\u636E\u4E0E\u9644\u4EF6\u5230 .apm/workitems/<\u9700\u6C42ID>"
2090
2160
  ).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.7",
3
+ "version": "4.0.9",
4
4
  "description": "命令行工具:后续用于调用平台后端 API 完成运维与自动化操作",
5
5
  "type": "module",
6
6
  "private": false,
@@ -1,75 +1,68 @@
1
1
  ---
2
2
  name: apm-refine
3
- description: 根据需求 ID 读取工作项 prd.md 与 reviews.xml,按「修订稿」标准结构(修订说明、背景与目标、范围、需求点、非功能、待确认/问题与局限等)润色并回写,再执行 apm refine 同步平台;当用户在对话中 @ 本技能或提出需求润色时使用。
3
+ description: 根据需求 ID 读取 prd.md 与 reviews.xml,润色为简短、可验收的业务需求文档并回写,再执行 apm refine 同步平台;当用户 @ 本技能或提出需求润色时使用。
4
4
  ---
5
5
 
6
6
  # APM 需求润色
7
7
 
8
8
  用户仅提供 **需求 ID**(workitem id)。**缺 ID 时索要,不猜测。**
9
9
 
10
- ## 合并原则
11
-
12
- 1. **正文依据 = 需求原文 + 补充信息**:只把用户在补充信息中**明确**要体现的内容(补充、修改、删除、拍板口径)合并进修订稿;用户没说到的,**保持原文或不写**,**不自作主张**补需求、不替用户「落实」评审建议。
13
- 2. **未回应的评审**:不列入「待确认」、不改成待办、不推断「仍有问题」;视为用户未要求在本次修订中处理(可能无此问题、暂不改、或评审误解)。
14
- 3. **评审的定位**:仅辅助理解补充信息在回应什么;补充信息与评审不一致时,**以补充信息为准**。
15
- 4. **消除重复**:同一议题在原文与补充信息中多处出现时,合并为**一处**表述。
16
- 5. **可追溯(轻量)**:可选「修订说明」概括相对原文的变化,且**只写补充信息实际带来的变化**,不罗列未采纳的评审。
17
- 6. **与仓库一致**:对照 `AGENTS.md`、`.apm/product-capability-inventory` 等,避免修订稿与用户已确认表述冲突;**禁止**用代码路径当需求论据。
18
- 7. **图片引用保持原样**:若原文含图片(如 `<img ...>`),在修订稿中直接保留对应 `img` 标签原文;不要改写成“图片见原始文档”等占位说明。
19
-
20
- ## 强制执行顺序(四步)
21
-
22
- ### 步骤 1:读取 `prd.md` 与 `reviews.xml`
10
+ ## 产出品质
23
11
 
24
- 1. 使用 **Read** 读取 **`.apm/workitems/<需求ID>/prd.md`**。
25
- 2. 当PRD涉及到图片链接时,自动下载对应的图片,存放在 `.apm/workitems/<需求ID>/images/<对应图片名称>.png`,并理解图片内容
26
- 3. 使用 **Read** 读取 **`.apm/workitems/<需求ID>/reviews.xml`**。
12
+ 润色后的 `prd.md` 应做到:
27
13
 
28
- **失败与缺省:**
14
+ - **可验收**:产品、测试、开发不读代码也能写用例;每条规则能对应一个测试点
15
+ - **简短**:合并后删繁就简,两三分钟可通读
16
+ - **业务化**:写界面、按钮、选项、场景与系统表现;材料中的技术名译为用户能懂的说法(原文要求保留的名称除外)
17
+ - **分场景**:新增 / 编辑 / 审核等分开写;**暂存**与**提交**的行为、校验时机分别写清
18
+ - **边界清楚**:范围写明包含与不包含;需求点内用 **「不考虑」** 标明明确排除项
19
+ - **术语统一**:同一字段、按钮全文同一叫法;改名时写明界面、提示、校验文案一并调整
20
+ - **结构平**:每个需求点一层 bullet(2 ~ 5 条),关键用语 **加粗**
29
21
 
30
- - 若 **`prd.md` 无法读取**(不存在、无权限等):**终止**后续步骤,在最终对用户的说明中记录失败原因。
31
- - 若 **`reviews.xml` 无法读取**(不存在等):**不终止**;视为**无评审意见**,仅基于当前 `prd.md` 做标准化与润色,不杜撰评审内容。
22
+ 正文骨架、需求点写法、示例与落盘自检见 **[apm-refine-template.md](./apm-refine-template.md)**。
32
23
 
33
- ### 步骤 2:润色为「标准需求文档」
34
-
35
- 在**不向用户追问补充信息**的前提下完成(**禁止**为润色向用户发起对话式提问;材料中已写的 `reply` 与评审意见视为**补充信息**可吸收。有歧义、缺信息、与 reviews 冲突等,**对材料未载明之事项**一律记入 **「问题与局限」** 或按条件写入 **「待确认」**,见 **[apm-refine-template.md](./apm-refine-template.md)** 与小节 3)。
24
+ ## 合并原则
36
25
 
37
- 1. **综合 `reviews.xml`(若可读)**
38
- 解析其中各条 `review` `content`(对 prd 的改进点/问题)与 `reply`(已给出的处理意见)。将**可落实的**回复与结论吸收进正文;**未覆盖或仍模糊**的,写入 **「问题与局限」**;若原文或 `reply` 中**已标明**未决、「待定」等,则同时或单独写入 **「待确认」**。**不**停步询问用户。
26
+ 1. **正文 = 需求原文 + 已拍板补充**:合并 `prd.md` 与 `reviews.xml` 里 `reply` 的明确口径;未说到的保持原文或不写,不自行扩需求。
27
+ 2. **评审辅助理解 `reply`**;与补充冲突时以补充为准;未回应的评审本次不处理。
28
+ 3. **同一议题只写一处**;合并后篇幅短于或接近原文。
29
+ 4. **图片**:原文图片语法原样保留;理解图片后把规则写入对应需求点的文字描述。
39
30
 
40
- 2. **落盘结构:标准「修订稿」模板(写入 `prd.md` 须遵循)**
41
- 使用 **Read** 读取与 `SKILL.md` 同目录的 **[apm-refine-template.md](./apm-refine-template.md)**,按其中**修订稿骨架**与章节说明组织正文(默认全文润色形态)。执行本技能时**须**读取该文件,不以记忆代替。
31
+ ## 执行步骤
42
32
 
43
- **改动与边界(仍须满足)**
33
+ ### 步骤 1:读取材料
44
34
 
45
- - **修订说明**中的「本次合并的要点」须**分点**写清本次相对原文的变更与**范围边界**(影响面、不做的事)。若改动项多于 3 条,可在「修订说明」后续追加小节 **「相对原文的变更」** 继续分点罗列。
46
- - 全文润色时**不要机械留空标题**:无内容的章节可删除(如无非功能则不写该章),但**背景与目标 / 范围 / 需求说明**一般应完整可读。
35
+ 1. **Read** `.apm/workitems/<需求ID>/prd.md`
36
+ 2. **Read** `.apm/workitems/<需求ID>/reviews.xml`(不存在则视为无评审)
37
+ 3. **理解 PRD 中的图片**(有则执行):
38
+ - 图片为标准 Markdown:`![描述](展示URL "相对路径")` — 圆括号内为展示 URL,**引号内**为相对路径(如 `attachments/图1.png`)
39
+ - **优先读本地附件**:`.apm/workitems/<需求ID>/<相对路径>`;存在则用该文件理解图片内容
40
+ - **本地不存在时**:再用圆括号内的展示 URL 下载图片后理解
41
+ - 将理解到的界面/字段/交互规则写入对应需求点;回写时保留原 `![…](… "…")` 语法不变
47
42
 
48
- **局部替换(不必强行铺满模板)**
49
- 若本轮实质上只需替换若干段落:可在 `prd.md` 中保留未改章节,仅重写涉及段落为**修改后的完整段落**,并在 **修订说明**(或 **相对原文的变更摘要**)中用简短 bullet 说明相对原文改了什么。**对用户对话仍以步骤 5 表格为唯一输出**,不把局部稿粘贴到对话里。
43
+ `prd.md` 不可读 → 终止,步骤 5 注明原因。
50
44
 
51
- 3. **「问题与局限」与「待确认」**
52
- 触发条件见 **[apm-refine-template.md](./apm-refine-template.md)** 文末两节;凡触发「问题与局限」的情形须按该节撰写,**禁止**因上述情况向用户发起追问。
45
+ ### 步骤 2:润色
53
46
 
54
- ### 步骤 3:回写 `prd.md`
47
+ **不向用户追问。** `reply` 视为补充信息。
55
48
 
56
- 使用 **Write** 将**完整**润色后的正文写回 **第一步同一路径**:**`.apm/workitems/<需求ID>/prd.md`**(覆盖原文件)。
49
+ 1. `reply` 中已拍板内容并入正文对应需求点。
50
+ 2. **Read** **[apm-refine-template.md](./apm-refine-template.md)**,按骨架、写法表与示例组织全文。
51
+ 3. 写完后按模板 **「落盘前自检」** 核对,不满足则再改一版。
52
+ 4. **待确认**:仅当补充信息原文含「待定」「再议」等时追加该章,每条一行归纳用户原意。
53
+ 5. **局部替换**:只改涉及段落时,保留其余章节,重写为完整段落。
57
54
 
58
- ### 步骤 4:执行 `apm refine <需求ID>`
55
+ ### 步骤 3:回写
59
56
 
60
- 在**仓库/工作区根目录**执行:
57
+ **Write** 覆盖 `.apm/workitems/<需求ID>/prd.md`。
61
58
 
62
- ```bash
63
- apm refine <需求ID>
64
- ```
59
+ ### 步骤 4:同步
65
60
 
66
- 命令结束后将本步执行结果记入「步骤 5」表格对应行的**状态**(见下)。
61
+ 仓库根目录执行:`apm refine <需求ID>`
67
62
 
68
63
  ### 步骤 5:回复用户
69
64
 
70
- 对用户可见回复 **仅限一张 Markdown 表格**,**仅填写步骤 1 ~ 4 每步的执行状态**。**不得**在表格外输出任何其他内容(不粘贴 prd 正文、不另写摘要或提示)。
71
-
72
- 建议表头(每行「状态」仅填 **成功** / **失败** / **跳过**;失败时可加简短原因,如 `失败:文件不存在`):
65
+ **仅**输出一张状态表,表格外无其他文字:
73
66
 
74
67
  | 步骤 | 状态 |
75
68
  | --------------------------------- | ---- |
@@ -78,4 +71,4 @@ apm refine <需求ID>
78
71
  | 3. 回写 `prd.md` | |
79
72
  | 4. 执行 `apm refine <需求ID>` | |
80
73
 
81
- ---
74
+ 状态:**成功** / **失败** / **跳过**(失败可加简短原因)。
@@ -1,39 +1,73 @@
1
- # [需求标题](修订稿)
1
+ # 需求文档模板
2
2
 
3
- **修订说明**(可选)
3
+ 执行润色时 **Read 本文件**,按下列骨架与写法组织 `prd.md`。
4
4
 
5
- - 基于评审日期 / 评审记录:简要一句
6
- - 本次合并的要点:1~3 条 bullet
5
+ ## 正文骨架
7
6
 
8
- ---
7
+ ```markdown
8
+ # [需求标题]
9
9
 
10
10
  ## 背景与目标
11
11
 
12
- (合并原文与用户补充信息中明确补充/修正的表述)
12
+ (1 ~ 3 句:业务背景、要解决的问题、预期效果)
13
13
 
14
14
  ## 范围
15
15
 
16
- - **包含**:
17
- - **不包含**:(若**补充信息**或原文中明确收窄/排除)
16
+ - **包含**:…
17
+ - **不包含**:…
18
18
 
19
19
  ## 需求说明
20
20
 
21
- ### 需求点 1:[名称]
21
+ ### 需求点 1:[简短名称]
22
22
 
23
- (将原文与补充信息合并后的**可执行描述**写清:角色、场景、规则、口径、边界)
23
+ - …
24
+ - …
24
25
 
25
26
  ### 需求点 2:…
27
+ ```
26
28
 
27
- ## 非功能与约束(若有)
29
+ 按需追加(材料中确有相关内容时):
28
30
 
29
- (性能、权限、兼容、埋点等——仅当原文或补充信息涉及)
31
+ - **非功能与约束** — 性能、权限、兼容、埋点等
32
+ - **待确认** — 补充信息里用户原文写明的待定项,每条 `[ ]` 一行
30
33
 
31
- ## 待确认
34
+ ## 需求点怎么写
32
35
 
33
- 仅当**用户自己在补充信息里**留下未拍板事项、或写明「待定」「再议」等时列出(可选章节,可整节省略):
36
+ 每条 bullet 写清一个可验收点,优先覆盖:
34
37
 
35
- - [ ] (用户未决口径摘要)— 用户原话或简要归纳
38
+ | 类型 | 写法要点 |
39
+ | --------- | ------------------------------------------------------ |
40
+ | 展示/选项 | 展示或隐藏;保留/去掉哪些选项;新建默认值 |
41
+ | 文案 | 原名称 → 新名称;界面、提示、校验文案一并统一 |
42
+ | 按钮 | 场景 + 按钮名 + 行为(对照哪个既有按钮、是否仅改文案) |
43
+ | 校验 | 场景 + 字段展示条件 + 必填时机(暂存 / 提交分别怎样) |
44
+ | 边界 | **不考虑** …(历史数据、迁移、导出等明确排除项) |
36
45
 
37
- ---
46
+ 材料中的技术名可译为业务表述,例如:`audit1` → 一级审批;保存不推进流程 → **暂存**;办理并推进流程 → **提交**。
38
47
 
39
- 若用户只需要「替换某几段」而非全文,可仅在回复中给出**修改后的完整段落** + 简短「相对原文的变更摘要」,不必机械填满所有章节。
48
+ ## 示例
49
+
50
+ ```markdown
51
+ ### 需求点 2:新增/编辑弹窗底部操作
52
+
53
+ - 在**新增、编辑**场景下,弹窗底部新增 **「暂存」** 按钮。
54
+ - **「暂存」**:沿用原 **「确定」** 按钮的业务逻辑(含校验与保存/流程行为),**仅将按钮展示文案改为「暂存」**。
55
+
56
+ ### 需求点 3:「科室医德考评小组人员名单」字段
57
+
58
+ - 原字段展示名 **「科室医德考评人员名单」** 统一改为 **「科室医德考评小组人员名单」**;凡界面、提示、校验文案等涉及该名称处**一并修改**。
59
+ - 当流程处于 **一级审批** 且该字段**展示**时,须校验为**必填**(在展示场景下触发,而非所有保存入口一律校验)。
60
+ ```
61
+
62
+ ## 落盘前自检
63
+
64
+ - [ ] 含 **背景与目标、范围、需求说明** 三章,叙述完整
65
+ - [ ] 2 分钟内可通读;每个需求点 2 ~ 5 条 bullet,一层列表
66
+ - [ ] 场景、按钮行为、校验时机表述清楚,前后一致
67
+ - [ ] 「范围」与需求点中的 **「不考虑」** 口径一致
68
+ - [ ] 全文使用业务语言,术语统一
69
+ - [ ] 仅材料明确涉及时才有「非功能」「待确认」
70
+
71
+ ## 局部替换
72
+
73
+ 只改部分段落时:保留未改章节,替换涉及段落为完整段落;结构仍符合上文骨架。
@@ -1,57 +1,103 @@
1
1
  ---
2
2
  name: apm-review
3
- description: 结合本仓库上下文对需求做结构化评审,只有当用户主动提及该技能时才可被使用,该技能调用依赖需求ID。
3
+ description: 结合本仓库上下文对需求做结构化评审,只有当用户主动提及该技能时才可被使用,该技能调用依赖需求 ID。
4
4
  ---
5
5
 
6
6
  # 需求评审
7
7
 
8
8
  用户仅提供 **需求 ID**(workitem id)。缺 ID 时索要,不猜测。
9
9
 
10
- ## 评审原则
10
+ ## 评审目标
11
11
 
12
- 1. **产品视角优先**:用户侧影响、业务闭环、风险与待确认点;避免晦涩技术术语堆砌。
13
- 2. **与仓库对齐(能力边界)**:阅读 `AGENTS.md`、子项目说明、`.apm/product-capability-inventory` 等与需求相关的部分,判断需求是否与既有能力/术语冲突、是否超出现有边界。**禁止**用「某文件某行」证明 PRD 对错;**禁止**根据代码**猜测** PRD 未写清的口径——口径不清应归入「问题」待产品澄清。
14
- 3. **业务合理性(按需)**:结合仓库可读的业务/产品上下文,判断需求是否想清楚、方案是否当下较优、是否与业务目标或流程冲突。**仅当**不合理、需再商榷或非当下较优时,才写 `- **业务合理性:**:`;合理则**不写**该字段。
15
- 4. **信息完备与落地(互斥规则)**:
16
- - 仅当 PRD/需求信息**不足以**形成开发可执行描述(关键落点、口径、范围、汇总规则等无法确定)时,写 `- **问题:**`,此时**不写**可行性与风险点。
17
- - 若已足以判断「可以实现」(非关键细节不阻塞落地),写 `- **可行性:**`(成本低/中/高 + 精简说明);影响面大时再追加 `- **风险点:**`。
18
- 5. **澄清优先但不泛问**:不阻塞落地则不提问;不问可推断的琐碎问题。
19
- 6. **隐性知识沉淀可执行**:对用户单独输出的每条沉淀建议应能回答「在仓库里补什么、能消除哪类隐性歧义」;若本次未发现值得沉淀的差异点,仍须给出一句结论(例如「未发现与通用假设显著偏离、需单独成文的隐性规则」)。
12
+ 帮助产品经理**梳理需求边界、发现文档中未写清的口径**,并在实现难度较高时给出提示。**不是**为了凑问题而提问——PRD 已足够清晰且无高难度改造时,仅写锚定即可,不必强行列出待澄清项。
20
13
 
21
- ## 执行步骤
14
+ ## 评审立场
22
15
 
23
- ### 步骤 1:读取 `prd.md`
16
+ 每轮评审从 **前端 / 后端 / 全栈** 中择定一种立场,按该视角撰写一篇评审正文,并执行一次 `apm comment`。
24
17
 
25
- 1. 使用 **Read** 读取 `.apm/workitems/<需求ID>/prd.md`。
26
- 2. 当 PRD 涉及到图片链接时,自动下载对应的图片,存放在 `.apm/workitems/<需求ID>/images/<对应图片名称>.png`,并理解图片内容
27
- 3. 若 Read **失败**(文件不存在或无法读取):本步骤记为失败,**终止**,不执行步骤 2、3。
18
+ ### 如何确定立场
28
19
 
29
- ### 步骤 2:读代码
20
+ 1. **用户明示**:用户说明「前端 / 后端 / 全栈 / 仅 UI / 仅接口」等,以用户为准。
21
+ 2. **用户未说明**:阅读仓库中与该需求相关的现有代码,推断评审者更贴近哪一端;仍无法判断时,**默认前端**,并在 `### 评审人` 中注明「未指定立场,按代码推断为 xxx」。
22
+ 3. **全栈**:在同一篇评审中同时覆盖 UI 与流程/数据关注点;同一业务点合并为一条待澄清项(见下文去重规则)。
23
+
24
+ ### 各端关注点(互斥优先,减少并行评审重复)
25
+
26
+ | 维度 | 前端 | 后端 |
27
+ | -------------- | -------------------------------------------------------------------------- | ------------------------------------------------------------------------- |
28
+ | **核心关注** | 页面、弹窗、Tab、按钮、文案、显隐、交互顺序、校验在界面上的反馈 | 流程节点、业务规则、数据落库、字段口径、汇总/计算逻辑、权限与校验时机 |
29
+ | **典型待澄清** | 哪些页面要改、按钮在什么场景出现、暂存/提交时界面如何提示、只读/可编辑范围 | 默认值与枚举、必填校验在暂存/提交下是否不同、主子表关系、汇总取哪一级数据 |
30
+ | **尽量不提** | 表结构、API 契约、payload 字段名(留给技术评审) | 组件名、具体控件选型、Tab 顺序(除非 PRD 已涉及且影响校验规则) |
31
+
32
+ **去重规则**(按立场选用):
33
+
34
+ - **前端立场**:只写界面与交互侧待澄清,不写落库/API 契约类问题。
35
+ - **后端立场**:只写流程、规则与数据侧待澄清,不写控件选型、Tab 布局等纯 UI 问题。
36
+ - **全栈立场**:同一业务点若 UI 与规则都涉及,合并为**一条**待澄清(优先产品能直接回答的业务表述),**不要**拆成两句意思重复的问题。
37
+
38
+ ## 表述规范(产品经理可读)
39
+
40
+ 1. **产品语言优先**:用「医德考评弹窗」「行风办审批页」「列表页分类筛选」等说法;**禁止**用文件路径、组件名、函数名作为论据或问题主体(`prd.md` 行号锚定除外)。
41
+ 2. **后端可略宽**:必要时可写**表名 / 主表字段名**帮助产品理解数据口径,但仍避免贴大段代码或接口路径。
42
+ 3. **锚定 PRD**:每条评审用 **`prd.md` 行号**指回原文(如 `prd.md` L44–L55),**不**另写半句概括或复述需求内容。
43
+ 4. **问题 = 文档缺口**:只写 PRD **未写清**且**影响本端理解边界**的点(例如:只说了改字段名,没说哪些页面/弹窗要改;只说了新增 Tab,没说各审批节点能否编辑)。已写清楚的规则不要重复质疑。
30
44
 
31
- 阅读仓库源码以及 `.apm/product-capability-inventory` 中与需求相关的条目;不展开无关模块代码。
45
+ ## 不在本轮的问题项
32
46
 
33
- ### 步骤 3:评审
47
+ 以下属于**技术评审 / 联调**阶段,**不得**写入「待澄清」:
34
48
 
35
- 1. **拆条**:多条诉求时拆成 `### 需求点 1..N`,每条独立走完「业务合理性(可选)→ 问题 或 可行性(+风险)」逻辑。
49
+ - 接口字段名、请求体结构、子表编码
50
+ - 前后端谁传哪个参数、现有 edit 接口是否够用
51
+ - 与现网代码实现对齐的改造方案
36
52
 
37
- 2. **成文(仅评审模板)**:按 `output-template.md` 拼出写入 comment 的 Markdown(顶格可加 `### 评审人` + 当前模型名);**不含**「隐性知识沉淀」段落;
53
+ 若**前端**评审时确知实现依赖某些后端信息,可在全文末尾单独增加 **`## 联调依赖项`**(见 `output-template.md`),**简短**记录接口/字段等诉求;**不用产品回复,仅作记录,待技术评审阶段处理**。
38
54
 
39
- 3. 使用 **Write** 将正文写入**临时文件**,路径建议使用**绝对路径**,例如 `/tmp/apm-review-<需求ID>.md`。**Markdown 评审正文**:**只写确实存在的问题**;用「需求背景 / 需求范围 / 交互与功能要求第 X 节 / 非目标」等文档自有结构**点名条款**(半句锚定即可),不先单独铺一节「对用户意图的理解」,**不写与 `prd-review` 类似的整条「需求描述」复述**。撰写前须完成代码检索并锁定本轮**评审立场**(见 reference 中「评审立场」),正文内容与措辞须与该立场一致,**不得超越可见范围下断定**。
55
+ ## 评审原则
56
+
57
+ 1. **产品视角优先**:用户侧影响、业务闭环、边界与风险;避免术语堆砌。
58
+ 2. **与仓库对齐(能力边界)**:阅读 `AGENTS.md`、`.apm/product-capability-inventory` 及与需求相关的代码,判断需求是否超出现有能力。**禁止**用代码证明 PRD 对错;**禁止**根据代码**猜测** PRD 未写清的口径——口径不清归入「待澄清」。
59
+ 3. **业务合理性(按需)**:仅当方案与业务目标明显冲突或明显非较优解时,写 `- **业务合理性:**`;合理则不写。
60
+ 4. **待澄清与实现难度(按需)**:
61
+ - 存在**阻碍本端理解或验收**的文档缺口 → 写 `- **待澄清:**`。
62
+ - 实现难度**高**时额外写 `- **实现难度高:**`(用产品语言简述原因);一般难度**不写**该字段。
63
+ - **高难度**指:对现有代码改造面大(涉及文件/模块较多),或业务逻辑变化影响较大。
64
+ 5. **澄清优先但不泛问**:不阻塞理解则不提问;不问可从 PRD 或常识推断的琐碎点。
65
+
66
+ ## 执行步骤
67
+
68
+ ### 步骤 1:读取 `prd.md`
69
+
70
+ 1. 使用 **Read** 读取 `.apm/workitems/<需求ID>/prd.md`。
71
+ 2. 当 PRD 涉及到图片链接时,自动下载对应的图片,存放在 `.apm/workitems/<需求ID>/images/<对应图片名称>.png`,并理解图片内容。
72
+ 3. 若 Read **失败**(文件不存在或无法读取):本步骤记为失败,**终止**,不执行后续步骤。
73
+
74
+ ### 步骤 2:读代码
40
75
 
41
- 4. **自检**:每条是否都有「需求描述」;「问题」与「可行性/风险」是否互斥符合 `output-template.md`;是否误引代码路径作论据;comment 全文是否**未混入**隐性知识段落。
76
+ 阅读仓库源码以及 `.apm/product-capability-inventory` 中与需求相关的条目,用于**理解现网能力与边界、判断改造面**,不展开无关模块。**读代码是为了校准评审立场和实现难度,不是为了把代码细节写进评审正文。**
42
77
 
43
- 5. **提交评审记录**:在项目根目录下执行:`apm comment <需求ID> --file=<临时文件绝对路径> --model=<评论使用的模型名称>`。
78
+ ### 步骤 3:评审与提交
44
79
 
45
- 6. 命令结束后 **删除临时文件**(**Delete** 工具或 `rm`),无论命令成功或失败都尽量清理(失败时保留文件仅供用户排错——技能默认仍删除,若需保留应在表格备注中说明)。
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`)。
84
+ 5. **自检**:
85
+ - 全文是否始终符合已锁定的评审立场;
86
+ - 待澄清项是否均为产品能回答的业务/交互/规则问题,而非接口契约;
87
+ - 是否误用代码路径、组件名作主要表述;
88
+ - 一般难度需求是否误写了「实现难度高」;
89
+ - 是否存在语义重复的待澄清项(应合并或删去一条)。
90
+ 6. 在项目根目录执行一次:`apm comment <需求ID> --file=<临时文件绝对路径> --model=<评论使用的模型名称>`。
91
+ 7. 命令结束后 **删除临时文件**(失败时若需排错可保留,默认仍删除)。
46
92
 
47
- ### 步骤 3:回复用户
93
+ ### 步骤 4:回复用户
48
94
 
49
- 对用户可见回复 **仅限一张 Markdown 表格**,汇总三步的**结果**(成功/失败、摘要信息)。**不得**在表格外输出任何其他内容(不在对话中粘贴完整评审正文;正文已通过 `apm comment` 提交)。
95
+ 对用户可见回复 **仅限一张 Markdown 表格**,汇总各步**结果**(成功/失败、摘要)。**不得**在表格外输出任何其他内容(完整评审正文已通过 `apm comment` 提交)。
50
96
 
51
97
  建议表头:
52
98
 
53
- | 步骤 | 结果 | 说明 |
54
- | ----------------- | ----------- | ----------------------------------------------------------------------------------------------------------------------------- |
55
- | 1. 读取 prd.md | 成功 / 失败 | 例如实际读取路径;失败时写错误原因 |
56
- | 2. 评审与 comment | 成功 / 失败 | 临时文件路径(已删可写「已清理」);`apm comment` 退出情况或 API 返回摘要;**建议**注明本轮评审立场(仅前台 / 仅后台 / 全栈) |
57
- | 3. 清理临时文件 | 成功 / 失败 | — |
99
+ | 步骤 | 结果 | 说明 |
100
+ | ----------------- | ----------- | ---------------------------------------------------------------------------------------------- |
101
+ | 1. 读取 prd.md | 成功 / 失败 | 实际路径;失败时写原因 |
102
+ | 2. 评审与 comment | 成功 / 失败 | 临时文件(已删可写「已清理」);`apm comment` 情况;**注明本轮评审立场**(前端 / 后端 / 全栈) |
103
+ | 3. 清理临时文件 | 成功 / 失败 | — |
@@ -1,43 +1,75 @@
1
+ # 评审输出模板
2
+
3
+ 顶格写评审人,随后按 PRD 章节逐条输出。全文使用**产品经理可读**的表述;锚定 PRD 时**只写行号**,不复述需求。
4
+
5
+ ```markdown
6
+ ### 评审人
7
+
8
+ {模型名}({前端 / 后端 / 全栈})
9
+
1
10
  ### 需求点 1
2
11
 
3
- - **需求描述:**: (一句话概括要做什么,产品视角)
4
- - (关键点 1)
5
- - (关键点 2)
12
+ - **锚定:** `prd.md` L44–L55
13
+
14
+ - **待澄清:**
15
+ - {文档未写清、影响本端理解边界的点;无则整段省略}
16
+ - {示例(前端):「类型」隐藏后,统计分析页的「分类」筛选是否一并去掉,导出是否统一走临床模板}
17
+ - {示例(后端):新建单据「默认临床」对应字典编码/文案未写明,历史非临床记录在列表是否仍展示原类型}
6
18
 
7
19
  ### 需求点 2
8
20
 
9
- - **需求描述:**: (一句话概括要做什么,产品视角)
21
+ - **锚定:** `prd.md` L59–L67
10
22
 
11
- - **问题:**
12
- - (问题 1:必须澄清“页面/表/口径/范围/汇总规则”等)
13
- - (问题 2)
14
- - (问题 3)
23
+ {PRD 已写清且改造面不大 → 仅锚定,不写其他字段}
15
24
 
16
25
  ### 需求点 3
17
26
 
18
- - **需求描述:**: (一句话概括要做什么,产品视角)
19
-
20
- - **可行性:**
21
- - 成本:低/中/高
22
- - (在不引入猜测的前提下,说明成本对应的业务/口径/环节调整)
27
+ - **锚定:** `prd.md` L69–L81
23
28
 
24
- ### 需求点 4
29
+ - **待澄清:**
30
+ - {仅列本端视角的缺口}
25
31
 
26
- - **需求描述:**: (一句话概括要做什么,产品视角)
27
- - **可行性:**
28
- - 成本:中/高
29
- - (在不引入猜测的前提下,说明成本对应的业务/口径/环节调整)
30
- - **风险点:**
31
- - (风险 1:可能影响的不止是展示文案,例如统计口径/汇总逻辑/跨模块联动等)
32
- - (风险 2)
32
+ ### 需求点 4(业务合理性:仅不合理时出现)
33
33
 
34
- ### 需求点 5(业务合理性:仅不合理时出现)
34
+ - **锚定:** `prd.md` L83–L106
35
35
 
36
- - **需求描述:**: (一句话概括要做什么,产品视角)
37
36
  - **业务合理性:**
38
- - (为何不合理/与用户目标或现有业务冲突/用户方案非当下较优;可写更可取的替代思路或需产品先拍板的点)
39
- - **可行性:**
40
- - 成本:低/中/高
41
- - (仍可在指出业务问题的同时评估落地成本;若信息仍不足以落地,则改用「需求点 2」结构:业务合理性 + **问题**,不出现可行性/风险点)
42
37
 
43
- 字段顺序与并存:`需求描述` 始终第一;`业务合理性` 仅在不合理时出现在 `需求描述` 之后。其后仍遵循「有问题则只写问题,无问题则写可行性(可加风险点)」。
38
+ - {为何与业务目标冲突或方案非较优;可给替代思路}
39
+
40
+ - **待澄清:**
41
+ - {若仍有文档缺口}
42
+
43
+ ### 需求点 5
44
+
45
+ - **锚定:** `prd.md` L108–L132
46
+
47
+ - **实现难度高:**
48
+ - {用产品语言说明:改造涉及哪些页面/流程/规则,为何面大;示例:加/减分明细需按审批层级分块改造,涉及弹窗表单、子表编辑与汇总多处联动,业务规则变化面大}
49
+
50
+ ---
51
+
52
+ ## 联调依赖项
53
+
54
+ {**仅当前端评审**且存在联调前置信息时出现;**可选**,无则省略整节}
55
+
56
+ > 不用产品回复,仅记录,待技术评审阶段处理。
57
+
58
+ {简短条目,记录后续需与后端对齐的接口/字段/能力诉求}
59
+
60
+ - 一票否决:需明确存储位置(主表字段或独立子表)及回显字段,以便行风办页勾选与查看页展示。
61
+ - 加/减分明细分级:需明确各级分值对应落库字段,以便分块编辑与汇总。
62
+ ```
63
+
64
+ ## 字段规则
65
+
66
+
67
+ | 字段 | 规则 |
68
+ | --------- | -------------------------------------------------------------- |
69
+ | **锚定** | 每条必有;写 `prd.md` L{起}–L{止},与 Read 工具读到的行号一致;**不**概括、**不**复述该段需求 |
70
+ | **待澄清** | 按需;无缺口则**不写**(不要写「无」) |
71
+ | **实现难度高** | 按需;**仅**改造面大或业务逻辑影响大时写;一般难度**不写** |
72
+ | **业务合理性** | 按需;仅方案明显不合理时出现 |
73
+ | **联调依赖项** | 全文最多一节;**不算待澄清**;不用产品回复,仅记录,待技术评审阶段处理 |
74
+
75
+