sdd-flow-kit 1.2.3 → 1.3.0

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.
Files changed (31) hide show
  1. package/README.md +71 -45
  2. package/dist/adapters/index.js +6 -6
  3. package/dist/cli.js +45 -9
  4. package/dist/core/acMatrix.js +116 -0
  5. package/dist/core/bootstrap.js +14 -6
  6. package/dist/core/inferProject.js +9 -2
  7. package/dist/core/openspecGate.js +45 -0
  8. package/dist/core/prdArtifacts.js +26 -0
  9. package/dist/core/projectRoots.js +79 -2
  10. package/dist/core/sessionState.js +2 -0
  11. package/dist/core/writeNext.js +19 -2
  12. package/dist/postinstall.js +22 -8
  13. package/dist/steps/runDeliver.js +18 -6
  14. package/dist/steps/runGate.js +97 -0
  15. package/dist/steps/runPhaseAdvance.js +53 -6
  16. package/dist/steps/runPropose.js +3 -1
  17. package/dist/steps/runValidate.js +204 -0
  18. package/dist/steps/setupProject.js +65 -2
  19. package/dist/steps/startFlow.js +14 -11
  20. package/package.json +1 -1
  21. package/src/templates/artifacts/01-/351/234/200/346/261/202/345/210/206/346/236/220/346/212/245/345/221/212.template.md +6 -1
  22. package/src/templates/artifacts/04-/346/212/200/346/234/257/346/226/207/346/241/243.template.md +272 -11
  23. package/src/templates/artifacts/05-/351/252/214/346/224/266/346/270/205/345/215/225.template.md +38 -0
  24. package/src/templates/artifacts/06-agent-skill.template.md +23 -9
  25. package/src/templates/artifacts/{05-openspec- → 06-openspec-}/346/217/220/346/241/210/350/215/211/347/250/277.template.md +16 -4
  26. package/src/templates/artifacts/{06-opsx-auto-chain.template.md → 07-opsx-auto-chain.template.md} +23 -9
  27. package/src/templates/prompts/02-sdd-loop-prompt.md +26 -9
  28. package/src/templates/rules/sdd-no-implement-until-apply.mdc.template +27 -8
  29. package/src/templates/skills/confluence-doc.py.template +25 -1
  30. package/src/templates/skills/doc-skill.SKILL.md.template +2 -0
  31. package/src/templates/skills/layered-testing-strategy.md +35 -0
package/README.md CHANGED
@@ -30,46 +30,56 @@ npx sdd-flow-kit doctor --agent cursor
30
30
 
31
31
  #### 安装教程
32
32
 
33
- 1. 进入你要开发的项目目录
34
- 2. 安装:`pnpm add -D sdd-flow-kit `
35
- 检查是否安装(Cursor 用户看 `.cursor`,不是 `.claude`):
36
- - `ls .cursor/skills/sdd-flow-kit/SKILL.md`
37
- - `ls docs/adi-doc-skill/SKILL.md`(adInsight 应为 **adi**,不是 oms)
38
- - Claude Code 用户:`ls .claude/skills/sdd-flow-kit/SKILL.md`
39
-
40
- > 若在 `adInsight-web/frontend` 等子目录安装,工具会**向上识别**仓库名;仍不对时:`SDD_FLOW_KIT_PROJECT=ADI pnpm add -D sdd-flow-kit` 或 `npx sdd-flow-kit install --project ADI -y`
41
- 3. 若 skill 未生成,执行手动兜底:
42
- - `node ./node_modules/sdd-flow-kit/dist/postinstall.js`(轻量,秒级)
43
- - `npx sdd-flow-kit install -y`(完整环境)
44
-
45
- 4. (可选)预演安装:`npx sdd-flow-kit install --dry-run --plan-json`
46
- 5. (可选)环境健检:`npx sdd-flow-kit doctor --agent claude-code`
47
- 6. 完成后,直接在 Cursor / Claude Code / Codex / OpenClaw 中用自然语言发起需求,例如:`开发 ADI 2.3.2 需求`
48
-
49
- > 可通过环境变量覆盖安装时默认项目类型:`SDD_FLOW_KIT_PROJECT=ADI npm i -D sdd-flow-kit`
50
-
51
- #### 安装失败排查(推荐复制执行)
52
-
53
- 当安装后没有生成 `.cursor/skills/sdd-flow-kit/SKILL.md` 时,按以下顺序处理:
54
-
55
- 1. 检查包是否已安装:`pnpm list sdd-flow-kit --depth 0`
56
- 2. 若出现 `ERR_PNPM_UNEXPECTED_STORE`,先对齐 store(示例):
57
- - `pnpm config set store-dir /Users/<your-user>/Library/pnpm/store/v10 --global`
58
- 3. 重新触发脚本:`pnpm rebuild sdd-flow-kit`
59
- 4. 若仍未生成,执行手动兜底(100%触发初始化):
60
- - `node ./node_modules/sdd-flow-kit/dist/postinstall.js`
61
- 5. 验证产物:
62
- - `ls .cursor/skills/sdd-flow-kit/SKILL.md`
63
- - `ls .claude/skills/sdd-flow-kit/SKILL.md`
64
- - `ls docs/*-doc-skill/SKILL.md`
65
-
66
- 团队推荐安装命令(稳妥):
67
-
68
- `pnpm add -D sdd-flow-kit`
69
-
70
- > `postinstall` 仅做**轻量**初始化(写入 skill / Cursor 规则 / docs-skill / `.opsx/config.json`),**不会**交互提问,也**不会**跑 `npx opsx-workflow`(避免 `pnpm i` 卡住)。
71
- > 完整环境(分支切换 + opsx-workflow)请另执行:`npx sdd-flow-kit install -y`
72
- > 跳过 postinstall:`SDD_FLOW_KIT_SKIP_POSTINSTALL=1 pnpm add -D sdd-flow-kit`
33
+ **推荐两步(团队标准)**:
34
+
35
+ ```bash
36
+ cd <你的项目目录> # cd 到哪,装到哪
37
+ pnpm add -D sdd-flow-kit # postinstall 自动轻量写入 skill
38
+ npx sdd-flow-kit install --project <类型> -y # ② 全量 OPSX(按需)
39
+ ```
40
+
41
+ `<类型>` 按项目选择:`ADI` | `OMS` | `欢盟` | `AD Tools`(工具会按目录名自动推断,不对时用 `--project` 覆盖)。
42
+
43
+ | 项目示例 | `--project` | docs skill 目录 |
44
+ |----------|-------------|-----------------|
45
+ | adInsight-web | `ADI` | `docs/adi-doc-skill/` |
46
+ | operation-web | `OMS` | `docs/oms-doc-skill/` |
47
+ | 欢盟 | `欢盟` | `docs/huan-doc-skill/` |
48
+
49
+ 安装后验证(Cursor 用户看 `.cursor`,不是 `.claude`):
50
+
51
+ ```bash
52
+ ls .cursor/skills/sdd-flow-kit/SKILL.md
53
+ ls docs/*-doc-skill/SKILL.md
54
+ # Claude Code 用户:
55
+ ls .claude/skills/sdd-flow-kit/SKILL.md
56
+ ```
57
+
58
+ **postinstall 行为(v1.2.4+)**:
59
+
60
+ - 默认 **light**:写入 SDD skill、Cursor 规则、docs-skill、`.opsx/config.json`;**不**跑 `opsx-workflow`(避免 `pnpm i` 卡住)
61
+ - 自动解析项目根:`INIT_CWD`(pnpm/npm)→ 从 `node_modules` 向上找消费方 `package.json` → `process.cwd()`(手动执行时)
62
+ - 全量 postinstall:`SDD_FLOW_KIT_POSTINSTALL_FULL=1 pnpm add -D sdd-flow-kit`
63
+ - 跳过 postinstall:`SDD_FLOW_KIT_SKIP_POSTINSTALL=1 pnpm add -D sdd-flow-kit`
64
+
65
+ > 环境变量覆盖项目类型:`SDD_FLOW_KIT_PROJECT=ADI pnpm add -D sdd-flow-kit`
66
+
67
+ #### 安装失败排查
68
+
69
+ 当没有生成 `.cursor/skills/sdd-flow-kit/SKILL.md` 时:
70
+
71
+ 1. 检查包是否已安装:`pnpm list sdd-flow-kit --depth 0`
72
+ 2. **pnpm v10** 若出现 `Ignored build scripts: sdd-flow-kit`:
73
+ - `pnpm approve-builds` 选中 `sdd-flow-kit`,或
74
+ - 在 `package.json` 加 `"pnpm": { "onlyBuiltDependencies": ["sdd-flow-kit"] }` 后 `pnpm install`
75
+ 3. 若出现 `ERR_PNPM_UNEXPECTED_STORE`,对齐 store 后重装:`pnpm install`
76
+ 4. 手动触发轻量安装(任选其一,**须在项目根目录**):
77
+ - `node ./node_modules/sdd-flow-kit/dist/postinstall.js`(v1.2.4+ 无需 `INIT_CWD`)
78
+ - `pnpm rebuild sdd-flow-kit`
79
+ - `npx sdd-flow-kit install --project OMS -y`(按项目改类型,全量)
80
+ 5. 验证:`ls .cursor/skills/sdd-flow-kit/SKILL.md docs/*-doc-skill/SKILL.md`
81
+
82
+ 首次跑任意 CLI(`doctor` / `run` / `gate` 等)也会自动 bootstrap 补装缺失的 SDD skill 或 OPSX。
73
83
 
74
84
  ##### `pnpm add` 后依赖全被删掉(`Packages: +1 -932`)
75
85
 
@@ -83,9 +93,9 @@ npx sdd-flow-kit doctor --agent cursor
83
93
  - `cd frontend && pnpm add -D sdd-flow-kit@1.1.2`
84
94
  - 或本地联调:`pnpm add -D file:../../sdd-flow-kit`
85
95
 
86
- **v1.1.2+ 防护**:`repairOpsxDeliveryScripts` 仅合并 `scripts`,写入前检查依赖条数与文件体积;`postinstall` 不再改 `package.json`,且必须有 `INIT_CWD` 才运行。
96
+ **v1.1.2+ 防护**:`repairOpsxDeliveryScripts` 仅合并 `scripts`,写入前检查依赖条数与文件体积。
87
97
 
88
- **pnpm v10 构建脚本**:若出现 `Ignored build scripts: sdd-flow-kit`,执行 `pnpm approve-builds` 选中 `sdd-flow-kit`,或安装后手动:`node ./node_modules/sdd-flow-kit/dist/postinstall.js`
98
+ **v1.2.4+ postinstall**:默认 light;自动解析消费方项目根(不依赖 `INIT_CWD`);首次安装后会写入 `pnpm.onlyBuiltDependencies` 以便后续 `pnpm install` 执行脚本。
89
99
 
90
100
  #### 使用说明
91
101
 
@@ -115,9 +125,11 @@ npx sdd-flow-kit run --product ADI --version 2.3.2 -y
115
125
  ```bash
116
126
  # 门禁(失败 exit 1)
117
127
  npx sdd-flow-kit gate --project-root . --run-id <runId> --expect prd-fetched
118
- npx sdd-flow-kit gate --project-root . --run-id <runId> --expect docs-closed # 检测 git 是否误改 src
128
+ npx sdd-flow-kit gate --project-root . --run-id <runId> --expect docs-closed # 05 已定义 + 无 src 改动
119
129
  npx sdd-flow-kit gate --project-root . --run-id <runId> --expect propose-ready --change <name>
130
+ npx sdd-flow-kit gate --project-root . --run-id <runId> --expect ac-ready --change <name>
120
131
  npx sdd-flow-kit gate --project-root . --run-id <runId> --expect impl-allowed --change <name>
132
+ npx sdd-flow-kit gate --project-root . --run-id <runId> --expect ac-signed
121
133
  npx sdd-flow-kit gate --project-root . --run-id <runId> --expect shipped --change <name>
122
134
 
123
135
  # 阶段推进(更新 .session-state.json + NEXT.md)
@@ -132,12 +144,26 @@ npx sdd-flow-kit propose --project-root . --run-id <runId> --change <name>
132
144
  npx sdd-flow-kit deliver --project-root . --run-id <runId> --change <name>
133
145
  ```
134
146
 
135
- `docs-closed` 会在 git 仓库中扫描 `src/`、`frontend/src/` 等路径;文档阶段若已有实现层改动,**gate 失败**。
147
+ `docs-closed` 会在 git 仓库中扫描 `src/`、`frontend/src/` 等路径;文档阶段若已有实现层改动,**gate 失败**。同时要求 `05-验收清单.md` 已标注「定义状态:已定义」且含 P0 项。
148
+
149
+ ##### v1.3.0 — 验收清单(AC Matrix)流程
150
+
151
+ | 文档/门禁 | 说明 |
152
+ |-----------|------|
153
+ | `05-验收清单.md` | 从 `source/PRD.md` 逐条抽 AC;**唯一验收 SSOT** |
154
+ | `gate docs-closed` | 含 05 已定义 + 01/02/04 + 03 闭合 |
155
+ | `gate ac-ready` | propose 后:每条 P0 在 `tasks.md`/`spec.md` 含 `[AC-XX]` |
156
+ | `gate ac-signed` | deliver 前:05 中 P0 已验收 + 「签收状态:已签收」 |
157
+ | `LAYERED-TESTING.md` | 安装到 `.cursor/skills/sdd-flow-kit/`(L1–L4 测试策略) |
158
+
159
+ 推荐文档顺序:**03 闭合 → 05 → 04(引用 AC)→ 01/02**。
160
+
161
+ 原 `05-openspec-提案草稿` / `06-opsx` 已顺延为 **`06-openspec-提案草稿`** / **`07-opsx-自动串联指引`**。
136
162
 
137
163
  ##### 需求澄清:用户无需背提示词
138
164
 
139
165
  `install` 会写入 `.cursor/rules/sdd-flow-kit-gates.mdc` 与触发 Skill(已改为**脚本优先**)。
140
- 用户粘贴确认点后:先更新 01/02/03/04,再执行 `gate docs-closed` → `phase advance --to docs-done`;**禁止**跳过 gate 直接改代码。
166
+ 用户粘贴确认点后:先更新 **05→04→01/02/03**,再执行 `gate docs-closed` → `phase advance --to docs-done`;propose 后 `gate ac-ready`;deliver 前 `gate ac-signed`。**禁止**跳过 gate 直接改代码。
141
167
 
142
168
  ##### `/opsx-apply` 与 TDD + Playwright 检查机制
143
169
 
@@ -41,9 +41,9 @@ async function runClaudeFlow(args) {
41
41
  `你在项目目录内执行需求流程,runId=${args.runId}。`,
42
42
  "请按分阶段门禁执行(禁止跳步):",
43
43
  "1) 01-获取需求文档指引:回填 source/PRD.md(及接口文档,如有)。",
44
- "2) 02-需求分析:若矛盾点未闭合,仅更新 03-待确认问题清单.md 并停止;用户确认后再生成 01/02/闭环03。",
45
- "3) 用户确认后补全 04-技术文档草稿.md",
46
- "4) 03 闭环后默认同一回合:/opsx-propose(01+02+UI)→ /opsx-apply → delivery-pipeline exit 0(见 06-opsx-自动串联指引.md)。",
44
+ "2) 02-需求分析:若矛盾点未闭合,仅更新 03-待确认问题清单.md 并停止;用户确认后生成 05→04→01/02/闭环03。",
45
+ "3) 用户确认后从 source/PRD.md 填写 05-验收清单.md(定义状态:已定义),再补全 04-技术文档草稿.md(引用 AC-ID)。",
46
+ "4) 03 闭环后默认同一回合:/opsx-propose(05+04+02+UI)→ gate ac-ready → /opsx-apply → gate ac-signed → delivery-pipeline exit 0(见 07-opsx-自动串联指引.md)。",
47
47
  "5) 仅当用户说「只提案」或 autoChainOpsx=false 时,在 propose 后暂停等待 apply。",
48
48
  "完成后输出一句:SDD flow completed",
49
49
  "",
@@ -92,12 +92,12 @@ async function runCursorFlow(args) {
92
92
  "强约束:",
93
93
  "1) 在 docs 阶段(docModeOnly=true)只允许改 openspec/PRD/**,禁止改 src/。",
94
94
  "2) 若矛盾点池未闭合:仅更新 03-待确认问题清单.md 并停止,等待用户确认点。",
95
- "3) 若矛盾点池已闭合:同一回合生成 01/02/04,并自动衔接 openspec+superpowers(autoChainOpsx=true 时)。",
95
+ "3) 若矛盾点池已闭合:同一回合生成 05→04→01/02,并自动衔接 openspec+superpowers(autoChainOpsx=true 时)。",
96
96
  "",
97
97
  "执行顺序:",
98
98
  "A) 先按 01-获取需求文档指引.md 执行 confluence-doc.py,把 PRD 回填到 source/PRD.md。",
99
- "B) 再执行 02-需求分析提示词.md(如下)生成/更新 03(必要时)或生成 01/02/04。",
100
- "C) 若满足自动串联条件(autoChainOpsx!==false 且用户未说只提案):继续按 06-opsx-自动串联指引.md 走 propose→apply→delivery-pipeline。",
99
+ "B) 再执行 02-需求分析提示词.md(如下)生成/更新 03(必要时)或生成 05/04/01/02。",
100
+ "C) 若满足自动串联条件(autoChainOpsx!==false 且用户未说只提案):继续按 07-opsx-自动串联指引.md 走 propose→ac-ready→apply→ac-signed→delivery-pipeline。",
101
101
  "",
102
102
  "完成判定:",
103
103
  "- 若停在澄清:输出一句 `SDD_STATUS=AWAIT_CONFIRMATION`。",
package/dist/cli.js CHANGED
@@ -15,6 +15,7 @@ const invokeFlow_1 = require("./steps/invokeFlow");
15
15
  const runGate_1 = require("./steps/runGate");
16
16
  const runPhaseAdvance_1 = require("./steps/runPhaseAdvance");
17
17
  const runPropose_1 = require("./steps/runPropose");
18
+ const runValidate_1 = require("./steps/runValidate");
18
19
  const runDeliver_1 = require("./steps/runDeliver");
19
20
  const bootstrap_1 = require("./core/bootstrap");
20
21
  const projectRoots_1 = require("./core/projectRoots");
@@ -36,10 +37,10 @@ function printHelp() {
36
37
  "1) 生成流程产物骨架:",
37
38
  " npx sdd-flow-kit start --product ADI --version 2.3.2",
38
39
  "",
39
- "2) 安装到当前目录(全量:SDD + OPSX + Superpowers,推荐):",
40
- " cd <你的项目目录> # 如 adInsight-web/frontend",
41
- " pnpm add -D sdd-flow-kit",
42
- " npx sdd-flow-kit install --project ADI -y",
40
+ "2) 安装到当前目录(推荐两步):",
41
+ " cd <你的项目目录> # 如 adInsight-web/frontend 或 operation-web",
42
+ " pnpm add -D sdd-flow-kit # postinstall 自动轻量写入 skill(pnpm v10 见 README)",
43
+ " npx sdd-flow-kit install --project ADI -y # 全量:OPSX + Superpowers(按项目改 ADI/OMS)",
43
44
  " 本地 OPSX: SDD_OPSX_WORKFLOW_PACKAGE=file:../../opsx-workflow npx sdd-flow-kit install -y",
44
45
  "",
45
46
  "3) 生成骨架并进入可执行模式(推荐):",
@@ -57,15 +58,20 @@ function printHelp() {
57
58
  "7) 脚本门禁(失败 exit 1,不依赖自然语言):",
58
59
  " npx sdd-flow-kit gate --project-root . --run-id <runId> --expect prd-fetched",
59
60
  " npx sdd-flow-kit gate --project-root . --run-id <runId> --expect docs-closed",
61
+ " npx sdd-flow-kit gate --project-root . --run-id <runId> --expect ac-ready --change <name>",
62
+ " npx sdd-flow-kit gate --project-root . --run-id <runId> --expect ac-signed",
60
63
  " npx sdd-flow-kit gate --project-root . --run-id <runId> --expect impl-allowed --change <name>",
64
+ " npx sdd-flow-kit gate --project-root . --run-id <runId> --expect validate-pass --change <name>",
61
65
  "",
62
66
  "8) 阶段推进(更新 session + NEXT.md):",
63
- " npx sdd-flow-kit phase advance --project-root . --run-id <runId> --to after-prd|docs-done|propose-done|impl|shipped",
67
+ " npx sdd-flow-kit phase advance --project-root . --run-id <runId> --to after-prd|docs-done|propose-done|impl|validate-done|shipped",
64
68
  "",
65
- "9) 提案 / 实现 / 交付:",
69
+ "9) 提案 / 实现 / 验收 / 交付:",
66
70
  " npx sdd-flow-kit propose --project-root . --run-id <runId> --change <name>",
67
71
  " npx sdd-flow-kit phase advance --project-root . --run-id <runId> --to impl --change <name>",
68
72
  " # 在 AI 工具执行 /opsx-apply,按 tasks.md 逐条实现并勾选 [x]",
73
+ " npx sdd-flow-kit validate --project-root . --run-id <runId> --change <name>",
74
+ " npx sdd-flow-kit phase advance --project-root . --run-id <runId> --to validate-done --change <name>",
69
75
  " npx sdd-flow-kit deliver --project-root . --run-id <runId> --change <name>",
70
76
  "",
71
77
  "可选参数:",
@@ -74,7 +80,7 @@ function printHelp() {
74
80
  " 环境变量 SDD_FLOW_KIT_LEGACY_MONOREPO=1 恢复「仓库根无 package.json 时落到 frontend/」",
75
81
  " --agent <name> cursor | claude-code | codex | openclaw | custom(默认 claude-code)",
76
82
  " --branch <name> 开发分支;不存在会自动创建",
77
- " --project <name> OMS | ADI | 欢盟 | AD Tools(默认 OMS,用于生成 docs/*-doc-skill)",
83
+ " --project <name> OMS | ADI | 欢盟 | AD Tools | BreakX(默认 OMS,用于生成 docs/*-doc-skill)",
78
84
  " -y 非交互模式(使用默认值)",
79
85
  " --dry-run 预演模式:仅检测并输出计划,不执行写入/安装/切分支",
80
86
  " --plan-json 输出机器可读 JSON 计划结果",
@@ -94,8 +100,11 @@ async function main() {
94
100
  "prd-fetched",
95
101
  "questions-open",
96
102
  "docs-closed",
103
+ "ac-ready",
104
+ "ac-signed",
97
105
  "propose-ready",
98
106
  "impl-allowed",
107
+ "validate-pass",
99
108
  "shipped",
100
109
  ];
101
110
  const PHASE_TARGETS = [
@@ -103,6 +112,7 @@ async function main() {
103
112
  "docs-done",
104
113
  "propose-done",
105
114
  "impl",
115
+ "validate-done",
106
116
  "shipped",
107
117
  ];
108
118
  if (cmd !== "start" &&
@@ -115,6 +125,7 @@ async function main() {
115
125
  cmd !== "gate" &&
116
126
  cmd !== "phase" &&
117
127
  cmd !== "propose" &&
128
+ cmd !== "validate" &&
118
129
  cmd !== "deliver") {
119
130
  printHelp();
120
131
  process_1.default.exitCode = 1;
@@ -139,7 +150,7 @@ async function main() {
139
150
  return;
140
151
  }
141
152
  const agent = agentValue;
142
- const allowedProjects = ["OMS", "ADI", "欢盟", "AD Tools"];
153
+ const allowedProjects = ["OMS", "ADI", "欢盟", "AD Tools", "BreakX"];
143
154
  if (!allowedProjects.includes(projectKind)) {
144
155
  // eslint-disable-next-line no-console
145
156
  console.error(`不支持的 --project: ${projectKind}(可选:${allowedProjects.join(" / ")})`);
@@ -157,6 +168,7 @@ async function main() {
157
168
  "gate",
158
169
  "phase",
159
170
  "propose",
171
+ "validate",
160
172
  "deliver",
161
173
  ]);
162
174
  if (!skipBootstrap && !dryRun && bootstrapCommands.has(cmd)) {
@@ -232,6 +244,30 @@ async function main() {
232
244
  process_1.default.exitCode = 1;
233
245
  return;
234
246
  }
247
+ if (cmd === "validate") {
248
+ if (!changeName) {
249
+ // eslint-disable-next-line no-console
250
+ console.error("validate 需要 --change <kebab-name>");
251
+ process_1.default.exitCode = 1;
252
+ return;
253
+ }
254
+ const result = await (0, runValidate_1.runValidate)({
255
+ projectRoot,
256
+ runId,
257
+ change: changeName,
258
+ agent,
259
+ dryRun,
260
+ });
261
+ // eslint-disable-next-line no-console
262
+ console.log(result.ok ? `✅ ${result.detail}` : `❌ ${result.detail}`);
263
+ if (result.needUserConfirm) {
264
+ // eslint-disable-next-line no-console
265
+ console.warn(`⚠️ 验收回退已达 ${result.ok ? 0 : 3} 次上限,需要用户确认后继续实现。`);
266
+ }
267
+ if (!result.ok)
268
+ process_1.default.exitCode = 1;
269
+ return;
270
+ }
235
271
  if (cmd === "deliver") {
236
272
  if (!changeName) {
237
273
  // eslint-disable-next-line no-console
@@ -365,7 +401,7 @@ async function main() {
365
401
  // eslint-disable-next-line no-console
366
402
  console.log(`流程已初始化:openspec/PRD/${result.runId}/`);
367
403
  // eslint-disable-next-line no-console
368
- console.log(`请打开 openspec/PRD/${result.runId}/NEXT.md,严格按 gate→phase→propose→impl(/opsx-apply)→deliver 执行;确认点前禁止改 src/。`);
404
+ console.log(`请打开 openspec/PRD/${result.runId}/NEXT.md,严格按 gate→phase→propose→impl(/opsx-apply)→validate→deliver 执行;确认点前禁止改 src/。`);
369
405
  // eslint-disable-next-line no-console
370
406
  console.log(`agent=${result.adapterName}, cli=${result.adapterAvailable ? "detected" : "missing"}, autoExecute=${result.executed ? "yes" : "manual"}`);
371
407
  // eslint-disable-next-line no-console
@@ -0,0 +1,116 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.parseAcMatrix = parseAcMatrix;
7
+ exports.areAllP0Accepted = areAllP0Accepted;
8
+ const path_1 = __importDefault(require("path"));
9
+ const fs_1 = require("./fs");
10
+ const AC_ID_RE = /^AC-\d{2,}$/i;
11
+ const P0_RE = /^P0$/i;
12
+ /**
13
+ * 读取 05-验收清单.md 并解析表格中的 AC 行(支持 Markdown 表格)。
14
+ */
15
+ async function parseAcMatrix(outputRoot) {
16
+ var _a, _b, _c, _d, _e;
17
+ const filePath = path_1.default.join(outputRoot, "05-验收清单.md");
18
+ const content = await (0, fs_1.readFileIfExists)(filePath);
19
+ if (!content || content.trim().length < 80) {
20
+ return {
21
+ ok: false,
22
+ detail: "05-验收清单.md 缺失或过短",
23
+ items: [],
24
+ p0Ids: [],
25
+ definitionReady: false,
26
+ signed: false,
27
+ };
28
+ }
29
+ const definitionReady = /定义状态[::]\s*[`「]*已定义/.test(content);
30
+ const signed = /签收状态[::]\s*[`「]*已签收/.test(content);
31
+ const items = [];
32
+ const lines = content.split("\n");
33
+ for (const line of lines) {
34
+ const trimmed = line.trim();
35
+ if (!trimmed.startsWith("|"))
36
+ continue;
37
+ if (/^\|\s*[-:]+/.test(trimmed))
38
+ continue;
39
+ if (/^\|\s*ID\s*\|/i.test(trimmed))
40
+ continue;
41
+ const cells = trimmed
42
+ .split("|")
43
+ .map((c) => c.trim())
44
+ .filter((_, i, arr) => i > 0 && i < arr.length - 1);
45
+ if (cells.length < 5)
46
+ continue;
47
+ const id = (_b = (_a = cells[0]) === null || _a === void 0 ? void 0 : _a.toUpperCase()) !== null && _b !== void 0 ? _b : "";
48
+ if (!AC_ID_RE.test(id))
49
+ continue;
50
+ const priority = (_c = cells[4]) !== null && _c !== void 0 ? _c : "";
51
+ const acceptanceStatus = (_e = (_d = cells[6]) !== null && _d !== void 0 ? _d : cells[cells.length - 1]) !== null && _e !== void 0 ? _e : "待验收";
52
+ items.push({
53
+ id,
54
+ priority,
55
+ acceptanceStatus,
56
+ prdRef: cells[1],
57
+ criterion: cells[2],
58
+ testType: cells[3],
59
+ });
60
+ }
61
+ const p0Ids = items.filter((i) => P0_RE.test(i.priority.trim())).map((i) => i.id);
62
+ if (items.length === 0) {
63
+ return {
64
+ ok: false,
65
+ detail: "05 中未解析到任何 AC-XX 行",
66
+ items,
67
+ p0Ids,
68
+ definitionReady,
69
+ signed,
70
+ };
71
+ }
72
+ if (!definitionReady) {
73
+ return {
74
+ ok: false,
75
+ detail: "05 未标注「定义状态:已定义」",
76
+ items,
77
+ p0Ids,
78
+ definitionReady: false,
79
+ signed,
80
+ };
81
+ }
82
+ if (p0Ids.length === 0) {
83
+ return {
84
+ ok: false,
85
+ detail: "05 中至少需 1 条 P0 验收项",
86
+ items,
87
+ p0Ids,
88
+ definitionReady,
89
+ signed,
90
+ };
91
+ }
92
+ return {
93
+ ok: true,
94
+ detail: `已解析 ${items.length} 条 AC(P0=${p0Ids.length})`,
95
+ items,
96
+ p0Ids,
97
+ definitionReady,
98
+ signed,
99
+ };
100
+ }
101
+ /**
102
+ * 判断所有 P0 项是否已在签收清单中标记为已验收。
103
+ */
104
+ function areAllP0Accepted(items, p0Ids) {
105
+ const pending = p0Ids.filter((id) => {
106
+ const row = items.find((i) => i.id === id);
107
+ if (!row)
108
+ return true;
109
+ const st = row.acceptanceStatus.trim();
110
+ return !/(已验收|\[x\])/i.test(st);
111
+ });
112
+ if (pending.length > 0) {
113
+ return { ok: false, detail: `P0 未全部签收: ${pending.join(", ")}` };
114
+ }
115
+ return { ok: true, detail: `全部 ${p0Ids.length} 条 P0 已验收` };
116
+ }
@@ -11,20 +11,27 @@ const opsxDelivery_1 = require("./opsxDelivery");
11
11
  const projectRoots_1 = require("./projectRoots");
12
12
  const setupProject_1 = require("../steps/setupProject");
13
13
  /**
14
- * pnpm v10 常拦截 postinstall;首次跑 CLI 时补全 SDD + OPSX 全栈(不切分支)。
14
+ * pnpm v10 常拦截 postinstall;首次跑 CLI 时补全 SDD skill OPSX 全栈(不切分支)。
15
15
  */
16
16
  async function bootstrapProjectIfNeeded(options) {
17
17
  var _a;
18
18
  const installRoot = (0, projectRoots_1.resolveInstallRoot)(options.projectRoot);
19
- if (await (0, opsxDelivery_1.isOpsxApplyReady)(installRoot)) {
19
+ const opsxReady = await (0, opsxDelivery_1.isOpsxApplyReady)(installRoot);
20
+ const sddReady = await (0, projectRoots_1.isSddKitSkillInstalled)(installRoot);
21
+ if (opsxReady && sddReady) {
20
22
  return false;
21
23
  }
22
24
  if (options.dryRun) {
23
25
  return true;
24
26
  }
25
27
  const kind = (_a = options.projectKind) !== null && _a !== void 0 ? _a : (await (0, inferProject_1.inferProjectKind)(installRoot));
28
+ const missing = [];
29
+ if (!sddReady)
30
+ missing.push("SDD skill");
31
+ if (!opsxReady)
32
+ missing.push("OPSX");
26
33
  // eslint-disable-next-line no-console
27
- console.log("[sdd-flow-kit] 检测到 OPSX 未就绪,正在自动 bootstrap(等价 install -y)…");
34
+ console.log(`[sdd-flow-kit] 检测到 ${missing.join(" / ")} 未就绪,正在自动 bootstrap(等价 install -y)…`);
28
35
  const pkgRoot = await (0, projectRoots_1.resolvePackageRoot)(installRoot);
29
36
  const pnpmPatched = await (0, packageJsonPatch_1.ensurePnpmAllowSddFlowKitBuild)(path_1.default.join(pkgRoot, "package.json"));
30
37
  if (pnpmPatched) {
@@ -35,9 +42,9 @@ async function bootstrapProjectIfNeeded(options) {
35
42
  projectRoot: installRoot,
36
43
  agent: options.agent,
37
44
  yes: true,
38
- light: false,
45
+ light: opsxReady,
39
46
  skipGit: true,
40
- projectKind: kind !== null && kind !== void 0 ? kind : "ADI",
47
+ projectKind: kind !== null && kind !== void 0 ? kind : "OMS",
41
48
  });
42
49
  if (!(await (0, opsxDelivery_1.isOpsxApplyReady)(installRoot))) {
43
50
  await (0, opsxDelivery_1.ensureOpsxWorkflow)({
@@ -46,7 +53,8 @@ async function bootstrapProjectIfNeeded(options) {
46
53
  });
47
54
  }
48
55
  const ready = await (0, opsxDelivery_1.isOpsxApplyReady)(installRoot);
49
- if (ready) {
56
+ const skillReady = await (0, projectRoots_1.isSddKitSkillInstalled)(installRoot);
57
+ if (ready && skillReady) {
50
58
  // eslint-disable-next-line no-console
51
59
  console.log("[sdd-flow-kit] bootstrap 完成:SDD + OPSX 已就绪");
52
60
  }
@@ -9,18 +9,20 @@ exports.resolveDocSkillDir = resolveDocSkillDir;
9
9
  exports.inferProjectKind = inferProjectKind;
10
10
  const promises_1 = __importDefault(require("fs/promises"));
11
11
  const path_1 = __importDefault(require("path"));
12
- exports.ALLOWED_PROJECTS = ["OMS", "ADI", "欢盟", "AD Tools"];
12
+ exports.ALLOWED_PROJECTS = ["OMS", "ADI", "欢盟", "AD Tools", "BreakX"];
13
13
  exports.DOC_SKILL_BY_KIND = {
14
14
  ADI: "adi-doc-skill",
15
15
  OMS: "oms-doc-skill",
16
16
  欢盟: "huan-doc-skill",
17
17
  "AD Tools": "ad-tools-doc-skill",
18
+ BreakX: "breakx-doc-skill",
18
19
  };
19
20
  exports.PRODUCT_PREFIX_BY_KIND = {
20
21
  ADI: "ADI",
21
22
  OMS: "OMS",
22
23
  欢盟: "HUAN",
23
24
  "AD Tools": "ADTOOLS",
25
+ BreakX: "BreakX",
24
26
  };
25
27
  function productToKind(product) {
26
28
  const p = product.trim().toUpperCase();
@@ -32,6 +34,8 @@ function productToKind(product) {
32
34
  return "欢盟";
33
35
  if (p.includes("AD TOOLS") || p === "ADTOOLS")
34
36
  return "AD Tools";
37
+ if (p === "BREAKX" || p.includes("POQUAN") || p.includes("破圈"))
38
+ return "BreakX";
35
39
  return "OMS";
36
40
  }
37
41
  /** 从 projectRoot 向上查找 docs/*-doc-skill(适配 frontend 子目录 + 根目录 docs) */
@@ -80,6 +84,9 @@ function matchKindInText(text) {
80
84
  if (t.includes("ad-tools") || t.includes("adtools")) {
81
85
  return "AD Tools";
82
86
  }
87
+ if (t.includes("poquan") || t.includes("breakx") || t.includes("破圈")) {
88
+ return "BreakX";
89
+ }
83
90
  return null;
84
91
  }
85
92
  /** 从 projectRoot 向上查找已存在的 docs/*-doc-skill */
@@ -124,7 +131,7 @@ async function inferFromPackageJson(dir) {
124
131
  }
125
132
  }
126
133
  /**
127
- * 推断 OMS / ADI / 欢盟 / AD Tools。
134
+ * 推断 OMS / ADI / 欢盟 / AD Tools / BreakX
128
135
  * monorepo 子目录(如 adInsight-web/frontend)会向上扫描路径,避免误判为 OMS。
129
136
  */
130
137
  async function inferProjectKind(projectRoot) {
@@ -6,6 +6,7 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.checkOpenspecApplyReady = checkOpenspecApplyReady;
7
7
  exports.checkTasksAllDone = checkTasksAllDone;
8
8
  exports.changeDirExists = changeDirExists;
9
+ exports.checkP0AcMappedInChange = checkP0AcMappedInChange;
9
10
  const child_process_1 = require("child_process");
10
11
  const promises_1 = __importDefault(require("fs/promises"));
11
12
  const path_1 = __importDefault(require("path"));
@@ -81,3 +82,47 @@ async function changeDirExists(projectRoot, changeName) {
81
82
  return false;
82
83
  }
83
84
  }
85
+ /**
86
+ * 递归读取 change 目录下所有 .md,检查 P0 AC-ID 是否在 spec/tasks 中有 [AC-XX] 映射。
87
+ */
88
+ async function checkP0AcMappedInChange(projectRoot, changeName, p0Ids) {
89
+ if (p0Ids.length === 0) {
90
+ return { ok: false, detail: "无 P0 AC 可校验映射" };
91
+ }
92
+ const changeRoot = path_1.default.join(projectRoot, "openspec", "changes", changeName);
93
+ try {
94
+ await promises_1.default.access(changeRoot);
95
+ }
96
+ catch {
97
+ return { ok: false, detail: `change 目录不存在: ${changeRoot}` };
98
+ }
99
+ const mdFiles = [];
100
+ async function walk(dir) {
101
+ const entries = await promises_1.default.readdir(dir, { withFileTypes: true });
102
+ for (const ent of entries) {
103
+ const full = path_1.default.join(dir, ent.name);
104
+ if (ent.isDirectory()) {
105
+ await walk(full);
106
+ }
107
+ else if (ent.isFile() && ent.name.endsWith(".md")) {
108
+ mdFiles.push(full);
109
+ }
110
+ }
111
+ }
112
+ await walk(changeRoot);
113
+ const combined = (await Promise.all(mdFiles.map((f) => (0, fs_1.readFileIfExists)(f))))
114
+ .filter(Boolean)
115
+ .join("\n");
116
+ const missing = p0Ids.filter((id) => {
117
+ const bracket = `[${id}]`;
118
+ const bare = id;
119
+ return !combined.includes(bracket) && !combined.includes(bare);
120
+ });
121
+ if (missing.length > 0) {
122
+ return {
123
+ ok: false,
124
+ detail: `P0 未映射到 spec/tasks: ${missing.join(", ")}(须在 tasks.md 或 spec.md 含 [AC-XX])`,
125
+ };
126
+ }
127
+ return { ok: true, detail: `全部 ${p0Ids.length} 条 P0 已映射到 change 文档` };
128
+ }
@@ -6,7 +6,10 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.isPrdFetched = isPrdFetched;
7
7
  exports.isQuestionsClosed = isQuestionsClosed;
8
8
  exports.areAnalysisDocsReady = areAnalysisDocsReady;
9
+ exports.isAcceptanceChecklistDefined = isAcceptanceChecklistDefined;
10
+ exports.isAcceptanceChecklistSigned = isAcceptanceChecklistSigned;
9
11
  const path_1 = __importDefault(require("path"));
12
+ const acMatrix_1 = require("./acMatrix");
10
13
  const fs_1 = require("./fs");
11
14
  const PRD_PLACEHOLDER_MARKERS = [
12
15
  "请把《PRD》",
@@ -51,3 +54,26 @@ async function areAnalysisDocsReady(outputRoot) {
51
54
  }
52
55
  return { ok: true, detail: "01/02/04 文档齐全" };
53
56
  }
57
+ /** 05-验收清单是否已从 PRD 定义完成(含至少一条 P0)。 */
58
+ async function isAcceptanceChecklistDefined(outputRoot) {
59
+ const parsed = await (0, acMatrix_1.parseAcMatrix)(outputRoot);
60
+ if (!parsed.ok) {
61
+ return { ok: false, detail: parsed.detail };
62
+ }
63
+ return { ok: true, detail: parsed.detail };
64
+ }
65
+ /** deliver 前 AC 签收:P0 全部已验收且清单标注已签收。 */
66
+ async function isAcceptanceChecklistSigned(outputRoot) {
67
+ const parsed = await (0, acMatrix_1.parseAcMatrix)(outputRoot);
68
+ if (!parsed.ok) {
69
+ return { ok: false, detail: parsed.detail };
70
+ }
71
+ if (!parsed.signed) {
72
+ return { ok: false, detail: "05 未标注「签收状态:已签收」" };
73
+ }
74
+ const p0 = (0, acMatrix_1.areAllP0Accepted)(parsed.items, parsed.p0Ids);
75
+ if (!p0.ok) {
76
+ return { ok: false, detail: p0.detail };
77
+ }
78
+ return { ok: true, detail: `AC 签收完成(${parsed.p0Ids.length} 条 P0)` };
79
+ }