sdd-flow-kit 1.2.2 → 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.
- package/README.md +71 -45
- package/dist/adapters/index.js +6 -6
- package/dist/cli.js +47 -9
- package/dist/core/acMatrix.js +116 -0
- package/dist/core/bootstrap.js +14 -6
- package/dist/core/inferProject.js +9 -2
- package/dist/core/openspecGate.js +45 -0
- package/dist/core/prdArtifacts.js +26 -0
- package/dist/core/projectRoots.js +79 -2
- package/dist/core/sessionState.js +2 -0
- package/dist/core/writeNext.js +24 -2
- package/dist/postinstall.js +22 -8
- package/dist/steps/runDeliver.js +18 -6
- package/dist/steps/runGate.js +97 -0
- package/dist/steps/runPhaseAdvance.js +54 -5
- package/dist/steps/runPropose.js +6 -2
- package/dist/steps/runValidate.js +204 -0
- package/dist/steps/setupProject.js +65 -2
- package/dist/steps/startFlow.js +14 -11
- package/package.json +1 -1
- 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
- package/src/templates/artifacts/04-/346/212/200/346/234/257/346/226/207/346/241/243.template.md +272 -11
- package/src/templates/artifacts/05-/351/252/214/346/224/266/346/270/205/345/215/225.template.md +38 -0
- package/src/templates/artifacts/06-agent-skill.template.md +23 -9
- package/src/templates/artifacts/{05-openspec- → 06-openspec-}/346/217/220/346/241/210/350/215/211/347/250/277.template.md +16 -4
- package/src/templates/artifacts/{06-opsx-auto-chain.template.md → 07-opsx-auto-chain.template.md} +23 -9
- package/src/templates/prompts/02-sdd-loop-prompt.md +26 -9
- package/src/templates/rules/sdd-no-implement-until-apply.mdc.template +27 -8
- package/src/templates/skills/confluence-doc.py.template +25 -1
- package/src/templates/skills/doc-skill.SKILL.md.template +2 -0
- 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
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
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
|
|
96
|
+
**v1.1.2+ 防护**:`repairOpsxDeliveryScripts` 仅合并 `scripts`,写入前检查依赖条数与文件体积。
|
|
87
97
|
|
|
88
|
-
**
|
|
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 #
|
|
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
|
|
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
|
|
package/dist/adapters/index.js
CHANGED
|
@@ -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
|
|
45
|
-
"3)
|
|
46
|
-
"4) 03 闭环后默认同一回合:/opsx-propose(
|
|
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
|
|
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
|
|
100
|
-
"C) 若满足自动串联条件(autoChainOpsx!==false 且用户未说只提案):继续按
|
|
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)
|
|
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,13 +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>",
|
|
71
|
+
" npx sdd-flow-kit phase advance --project-root . --run-id <runId> --to impl --change <name>",
|
|
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>",
|
|
67
75
|
" npx sdd-flow-kit deliver --project-root . --run-id <runId> --change <name>",
|
|
68
76
|
"",
|
|
69
77
|
"可选参数:",
|
|
@@ -72,7 +80,7 @@ function printHelp() {
|
|
|
72
80
|
" 环境变量 SDD_FLOW_KIT_LEGACY_MONOREPO=1 恢复「仓库根无 package.json 时落到 frontend/」",
|
|
73
81
|
" --agent <name> cursor | claude-code | codex | openclaw | custom(默认 claude-code)",
|
|
74
82
|
" --branch <name> 开发分支;不存在会自动创建",
|
|
75
|
-
" --project <name> OMS | ADI | 欢盟 | AD Tools(默认 OMS,用于生成 docs/*-doc-skill)",
|
|
83
|
+
" --project <name> OMS | ADI | 欢盟 | AD Tools | BreakX(默认 OMS,用于生成 docs/*-doc-skill)",
|
|
76
84
|
" -y 非交互模式(使用默认值)",
|
|
77
85
|
" --dry-run 预演模式:仅检测并输出计划,不执行写入/安装/切分支",
|
|
78
86
|
" --plan-json 输出机器可读 JSON 计划结果",
|
|
@@ -92,8 +100,11 @@ async function main() {
|
|
|
92
100
|
"prd-fetched",
|
|
93
101
|
"questions-open",
|
|
94
102
|
"docs-closed",
|
|
103
|
+
"ac-ready",
|
|
104
|
+
"ac-signed",
|
|
95
105
|
"propose-ready",
|
|
96
106
|
"impl-allowed",
|
|
107
|
+
"validate-pass",
|
|
97
108
|
"shipped",
|
|
98
109
|
];
|
|
99
110
|
const PHASE_TARGETS = [
|
|
@@ -101,6 +112,7 @@ async function main() {
|
|
|
101
112
|
"docs-done",
|
|
102
113
|
"propose-done",
|
|
103
114
|
"impl",
|
|
115
|
+
"validate-done",
|
|
104
116
|
"shipped",
|
|
105
117
|
];
|
|
106
118
|
if (cmd !== "start" &&
|
|
@@ -113,6 +125,7 @@ async function main() {
|
|
|
113
125
|
cmd !== "gate" &&
|
|
114
126
|
cmd !== "phase" &&
|
|
115
127
|
cmd !== "propose" &&
|
|
128
|
+
cmd !== "validate" &&
|
|
116
129
|
cmd !== "deliver") {
|
|
117
130
|
printHelp();
|
|
118
131
|
process_1.default.exitCode = 1;
|
|
@@ -137,7 +150,7 @@ async function main() {
|
|
|
137
150
|
return;
|
|
138
151
|
}
|
|
139
152
|
const agent = agentValue;
|
|
140
|
-
const allowedProjects = ["OMS", "ADI", "欢盟", "AD Tools"];
|
|
153
|
+
const allowedProjects = ["OMS", "ADI", "欢盟", "AD Tools", "BreakX"];
|
|
141
154
|
if (!allowedProjects.includes(projectKind)) {
|
|
142
155
|
// eslint-disable-next-line no-console
|
|
143
156
|
console.error(`不支持的 --project: ${projectKind}(可选:${allowedProjects.join(" / ")})`);
|
|
@@ -155,6 +168,7 @@ async function main() {
|
|
|
155
168
|
"gate",
|
|
156
169
|
"phase",
|
|
157
170
|
"propose",
|
|
171
|
+
"validate",
|
|
158
172
|
"deliver",
|
|
159
173
|
]);
|
|
160
174
|
if (!skipBootstrap && !dryRun && bootstrapCommands.has(cmd)) {
|
|
@@ -230,6 +244,30 @@ async function main() {
|
|
|
230
244
|
process_1.default.exitCode = 1;
|
|
231
245
|
return;
|
|
232
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
|
+
}
|
|
233
271
|
if (cmd === "deliver") {
|
|
234
272
|
if (!changeName) {
|
|
235
273
|
// eslint-disable-next-line no-console
|
|
@@ -363,7 +401,7 @@ async function main() {
|
|
|
363
401
|
// eslint-disable-next-line no-console
|
|
364
402
|
console.log(`流程已初始化:openspec/PRD/${result.runId}/`);
|
|
365
403
|
// eslint-disable-next-line no-console
|
|
366
|
-
console.log(`请打开 openspec/PRD/${result.runId}/NEXT.md,严格按 gate→phase→propose→deliver 执行;确认点前禁止改 src/。`);
|
|
404
|
+
console.log(`请打开 openspec/PRD/${result.runId}/NEXT.md,严格按 gate→phase→propose→impl(/opsx-apply)→validate→deliver 执行;确认点前禁止改 src/。`);
|
|
367
405
|
// eslint-disable-next-line no-console
|
|
368
406
|
console.log(`agent=${result.adapterName}, cli=${result.adapterAvailable ? "detected" : "missing"}, autoExecute=${result.executed ? "yes" : "manual"}`);
|
|
369
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
|
+
}
|
package/dist/core/bootstrap.js
CHANGED
|
@@ -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
|
|
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
|
-
|
|
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(
|
|
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:
|
|
45
|
+
light: opsxReady,
|
|
39
46
|
skipGit: true,
|
|
40
|
-
projectKind: kind !== null && kind !== void 0 ? kind : "
|
|
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
|
-
|
|
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
|
+
}
|