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.
Files changed (31) hide show
  1. package/README.md +71 -45
  2. package/dist/adapters/index.js +6 -6
  3. package/dist/cli.js +47 -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 +24 -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 +54 -5
  16. package/dist/steps/runPropose.js +6 -2
  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
@@ -0,0 +1,204 @@
1
+ "use strict";
2
+ var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
3
+ if (k2 === undefined) k2 = k;
4
+ var desc = Object.getOwnPropertyDescriptor(m, k);
5
+ if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
6
+ desc = { enumerable: true, get: function() { return m[k]; } };
7
+ }
8
+ Object.defineProperty(o, k2, desc);
9
+ }) : (function(o, m, k, k2) {
10
+ if (k2 === undefined) k2 = k;
11
+ o[k2] = m[k];
12
+ }));
13
+ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
14
+ Object.defineProperty(o, "default", { enumerable: true, value: v });
15
+ }) : function(o, v) {
16
+ o["default"] = v;
17
+ });
18
+ var __importStar = (this && this.__importStar) || (function () {
19
+ var ownKeys = function(o) {
20
+ ownKeys = Object.getOwnPropertyNames || function (o) {
21
+ var ar = [];
22
+ for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
23
+ return ar;
24
+ };
25
+ return ownKeys(o);
26
+ };
27
+ return function (mod) {
28
+ if (mod && mod.__esModule) return mod;
29
+ var result = {};
30
+ if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
31
+ __setModuleDefault(result, mod);
32
+ return result;
33
+ };
34
+ })();
35
+ var __importDefault = (this && this.__importDefault) || function (mod) {
36
+ return (mod && mod.__esModule) ? mod : { "default": mod };
37
+ };
38
+ Object.defineProperty(exports, "__esModule", { value: true });
39
+ exports.MAX_VALIDATE_RETRIES = void 0;
40
+ exports.runValidate = runValidate;
41
+ const path_1 = __importDefault(require("path"));
42
+ const child_process_1 = require("child_process");
43
+ const resolveRun_1 = require("./resolveRun");
44
+ const runGate_1 = require("./runGate");
45
+ const opsxDelivery_1 = require("../core/opsxDelivery");
46
+ const projectRoots_1 = require("../core/projectRoots");
47
+ const sessionState_1 = require("../core/sessionState");
48
+ const fs_1 = require("../core/fs");
49
+ const openspecGate_1 = require("../core/openspecGate");
50
+ /** 验收回退最大次数,超过需用户确认 */
51
+ exports.MAX_VALIDATE_RETRIES = 3;
52
+ /**
53
+ * 验收命令:跑 lint+type+unit(delivery-pipeline:short)+ e2e/Playwright,写入 .validate-pass.json 或 .validate-fail.json。
54
+ * 验收不通过时回退到 apply_in_progress,超过 MAX_VALIDATE_RETRIES 次则需用户确认。
55
+ */
56
+ async function runValidate(options) {
57
+ var _a, _b, _c;
58
+ const loaded = await (0, resolveRun_1.loadRunSession)(options.projectRoot, options.runId);
59
+ if (!(loaded === null || loaded === void 0 ? void 0 : loaded.resolved)) {
60
+ return { ok: false, detail: "未找到 PRD run", needUserConfirm: false };
61
+ }
62
+ // 前置门禁:impl-allowed
63
+ const implGate = await (0, runGate_1.runGate)({
64
+ projectRoot: options.projectRoot,
65
+ runId: loaded.resolved.runId,
66
+ expect: "impl-allowed",
67
+ change: options.change,
68
+ });
69
+ if (!implGate.ok) {
70
+ (0, runGate_1.printGateResult)(implGate);
71
+ return { ok: false, detail: "impl-allowed gate 未通过,不能验收", needUserConfirm: false };
72
+ }
73
+ const installRoot = (0, projectRoots_1.resolveInstallRoot)(options.projectRoot);
74
+ const pkgRoot = await (0, projectRoots_1.resolvePackageRoot)(installRoot);
75
+ const agent = options.agent || "cursor";
76
+ if (options.dryRun) {
77
+ return { ok: true, detail: `dry-run: would validate in ${pkgRoot} (installRoot=${installRoot})`, needUserConfirm: false };
78
+ }
79
+ // 确保 opsx 环境就绪
80
+ const ensured = await (0, opsxDelivery_1.ensureOpsxWorkflow)({ projectRoot: pkgRoot, agent, dryRun: false });
81
+ if (!ensured.ready) {
82
+ return { ok: false, detail: "ensure-opsx 未就绪", needUserConfirm: false };
83
+ }
84
+ // ---- 检查1: tasks.md 全完成 ----
85
+ const tasks = await (0, openspecGate_1.checkTasksAllDone)(installRoot, options.change);
86
+ // ---- 检查2: delivery-pipeline:short(lint + type check + unit test) ----
87
+ const shortRet = (0, child_process_1.spawnSync)("pnpm", ["run", "opsx:delivery-pipeline:short", "--", options.change], {
88
+ cwd: pkgRoot,
89
+ stdio: "inherit",
90
+ shell: process.platform === "win32",
91
+ });
92
+ const shortExitCode = (_a = shortRet.status) !== null && _a !== void 0 ? _a : 1;
93
+ // ---- 检查3: e2e / Playwright ----
94
+ let e2eExitCode = 0;
95
+ let e2eDetail = "e2e 未执行";
96
+ const hasPlaywrightScript = await hasScript(pkgRoot, "test:e2e");
97
+ if (hasPlaywrightScript) {
98
+ const e2eRet = (0, child_process_1.spawnSync)("pnpm", ["run", "test:e2e"], {
99
+ cwd: pkgRoot,
100
+ stdio: "inherit",
101
+ shell: process.platform === "win32",
102
+ env: { ...process.env, CI: "1" },
103
+ });
104
+ e2eExitCode = (_b = e2eRet.status) !== null && _b !== void 0 ? _b : 1;
105
+ e2eDetail = e2eExitCode === 0 ? "e2e/Playwright 全通过" : `e2e/Playwright 退出码 ${e2eExitCode}`;
106
+ }
107
+ else {
108
+ // 尝试直接用 npx playwright test
109
+ const hasPlaywrightCli = (0, child_process_1.spawnSync)("npx", ["playwright", "--version"], {
110
+ cwd: pkgRoot,
111
+ stdio: "ignore",
112
+ }).status === 0;
113
+ if (hasPlaywrightCli) {
114
+ const e2eRet = (0, child_process_1.spawnSync)("npx", ["playwright", "test"], {
115
+ cwd: pkgRoot,
116
+ stdio: "inherit",
117
+ shell: process.platform === "win32",
118
+ env: { ...process.env, CI: "1" },
119
+ });
120
+ e2eExitCode = (_c = e2eRet.status) !== null && _c !== void 0 ? _c : 1;
121
+ e2eDetail = e2eExitCode === 0 ? "Playwright test 全通过" : `Playwright test 退出码 ${e2eExitCode}`;
122
+ }
123
+ else {
124
+ e2eDetail = "未检测到 Playwright,跳过 e2e(建议配置 test:e2e 脚本)";
125
+ }
126
+ }
127
+ // ---- 综合判定 ----
128
+ const allPassed = tasks.ok && shortExitCode === 0 && e2eExitCode === 0;
129
+ const verdict = allPassed ? "pass" : "fail";
130
+ const payload = {
131
+ change: options.change,
132
+ verdict,
133
+ tasksOk: tasks.ok,
134
+ tasksDetail: tasks.detail,
135
+ pipelineShortExitCode: shortExitCode,
136
+ e2eExitCode,
137
+ e2eDetail,
138
+ at: new Date().toISOString(),
139
+ };
140
+ const markerFile = verdict === "pass" ? ".validate-pass.json" : ".validate-fail.json";
141
+ await (0, fs_1.writeTextFileEnsuredDir)(path_1.default.join(loaded.resolved.outputRoot, markerFile), `${JSON.stringify(payload, null, 2)}\n`);
142
+ // ---- 更新 session ----
143
+ let needUserConfirm = false;
144
+ if (loaded.session) {
145
+ if (verdict === "pass") {
146
+ const next = {
147
+ ...loaded.session,
148
+ status: "validate_done",
149
+ };
150
+ await (0, sessionState_1.writeSession)(loaded.resolved.outputRoot, next);
151
+ }
152
+ else {
153
+ // 验收失败:回退计数 +1
154
+ const newRetryCount = (loaded.session.validateRetryCount || 0) + 1;
155
+ needUserConfirm = newRetryCount > exports.MAX_VALIDATE_RETRIES;
156
+ const next = {
157
+ ...loaded.session,
158
+ status: "apply_in_progress",
159
+ validateRetryCount: newRetryCount,
160
+ };
161
+ await (0, sessionState_1.writeSession)(loaded.resolved.outputRoot, next);
162
+ // 清理上次验收标记(重新实现后需要重新验收)
163
+ await cleanupValidateMarkers(loaded.resolved.outputRoot);
164
+ }
165
+ }
166
+ if (verdict === "fail") {
167
+ const retryInfo = loaded.session
168
+ ? `(第 ${loaded.session.validateRetryCount + 1} 次验收失败${needUserConfirm ? ",已达上限,需用户确认后继续" : ""})`
169
+ : "";
170
+ return {
171
+ ok: false,
172
+ detail: `验收失败: tasks=${tasks.detail}, pipeline=${shortExitCode}, e2e=${e2eDetail} ${retryInfo}`,
173
+ needUserConfirm,
174
+ };
175
+ }
176
+ return { ok: true, detail: "验收通过(lint+type+unit+e2e),可进入交付", needUserConfirm: false };
177
+ }
178
+ /** 检查 package.json 是否有指定 script */
179
+ async function hasScript(pkgRoot, scriptName) {
180
+ var _a;
181
+ try {
182
+ const { readFile: readFileFs } = await Promise.resolve().then(() => __importStar(require("fs/promises")));
183
+ const pkgText = await readFileFs(path_1.default.join(pkgRoot, "package.json"), "utf8");
184
+ const pkg = JSON.parse(pkgText);
185
+ return Boolean((_a = pkg.scripts) === null || _a === void 0 ? void 0 : _a[scriptName]);
186
+ }
187
+ catch {
188
+ return false;
189
+ }
190
+ }
191
+ /** 清理验收标记文件,重新实现后需要重新验收 */
192
+ async function cleanupValidateMarkers(outputRoot) {
193
+ const { unlink } = await Promise.resolve().then(() => __importStar(require("fs/promises")));
194
+ for (const f of [".validate-pass.json", ".validate-fail.json"]) {
195
+ const p = path_1.default.join(outputRoot, f);
196
+ const content = await (0, fs_1.readFileIfExists)(p);
197
+ if (content !== null) {
198
+ try {
199
+ await unlink(p);
200
+ }
201
+ catch { /* ignore */ }
202
+ }
203
+ }
204
+ }
@@ -5,6 +5,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.setupProject = setupProject;
7
7
  const child_process_1 = require("child_process");
8
+ const promises_1 = __importDefault(require("fs/promises"));
8
9
  const path_1 = __importDefault(require("path"));
9
10
  const process_1 = __importDefault(require("process"));
10
11
  const fs_1 = require("../core/fs");
@@ -15,7 +16,7 @@ const packageJsonPatch_1 = require("../core/packageJsonPatch");
15
16
  const projectRoots_1 = require("../core/projectRoots");
16
17
  const templates_1 = require("../core/templates");
17
18
  const EDITOR_CHOICES = ["claude-code", "cursor", "codex", "openclaw"];
18
- const PROJECT_CHOICES = ["OMS", "ADI", "欢盟", "AD Tools"];
19
+ const PROJECT_CHOICES = ["OMS", "ADI", "欢盟", "AD Tools", "BreakX"];
19
20
  function runGit(args, cwd) {
20
21
  var _a, _b;
21
22
  const out = (0, child_process_1.spawnSync)("git", args, { cwd, encoding: "utf8" });
@@ -87,6 +88,7 @@ async function askProjectKind(defaultKind) {
87
88
  "2) ADI",
88
89
  "3) 欢盟",
89
90
  "4) AD Tools",
91
+ "5) BreakX",
90
92
  "输入编号(回车默认 1):",
91
93
  ].join("\n");
92
94
  const answer = (await (0, readlinePrompt_1.promptLine)(rl, `${prompt}\n`)).trim();
@@ -131,6 +133,7 @@ async function installAgentSkill(projectRoot, projectKind) {
131
133
  if (targets.length === 0)
132
134
  return;
133
135
  const template = await (0, templates_1.loadTemplateText)("artifacts/06-agent-skill.template.md");
136
+ const layered = await (0, templates_1.loadTemplateText)("skills/layered-testing-strategy.md");
134
137
  for (const target of targets) {
135
138
  const agentName = target.includes("/.cursor/")
136
139
  ? "cursor"
@@ -145,6 +148,8 @@ async function installAgentSkill(projectRoot, projectKind) {
145
148
  DEFAULT_PRODUCT: projectKind === "AD Tools" ? "AD Tools" : projectKind,
146
149
  });
147
150
  await (0, fs_1.writeTextFileEnsuredDir)(target, content);
151
+ const layeredPath = path_1.default.join(path_1.default.dirname(target), "LAYERED-TESTING.md");
152
+ await (0, fs_1.writeTextFileEnsuredDir)(layeredPath, layered);
148
153
  }
149
154
  }
150
155
  async function installSddCursorRule(projectRoot) {
@@ -167,6 +172,8 @@ function docSkillDirName(kind) {
167
172
  return { dir: "huan-doc-skill", prefix: "HUAN" };
168
173
  case "AD Tools":
169
174
  return { dir: "ad-tools-doc-skill", prefix: "ADTOOLS" };
175
+ case "BreakX":
176
+ return { dir: "breakx-doc-skill", prefix: "BreakX" };
170
177
  case "OMS":
171
178
  default:
172
179
  return { dir: "oms-doc-skill", prefix: "OMS" };
@@ -183,12 +190,67 @@ async function installDocsSkill(projectRoot, kind) {
183
190
  PRODUCT_PREFIX: prefix,
184
191
  PRODUCT_PREFIX_LOWER: prefix.toLowerCase(),
185
192
  SKILL_DIR: dir,
193
+ CONFLUENCE_SPACE_NOTE: kind === "BreakX"
194
+ ? "- BreakX PRD 位于 Confluence **xhs** 空间,页面标题通常以 `Vx.y.z` 开头(如 `V1.0.2 审稿子任务...`)\n- 脚本默认 `CONFLUENCE_SPACE=xhs`,优先匹配该空间下的页面"
195
+ : "",
186
196
  });
187
197
  const py = (0, utils_1.replaceAllStr)(pyTemplate, "{{DOC_PRODUCT_PREFIX}}", prefix);
188
198
  await (0, fs_1.writeTextFileEnsuredDir)(path_1.default.join(docsDir, "SKILL.md"), md);
189
199
  await (0, fs_1.writeTextFileEnsuredDir)(path_1.default.join(docsDir, "confluence-doc.py"), py);
190
200
  return { dirPath: docsDir };
191
201
  }
202
+ function parseDevPortFromScript(script) {
203
+ var _a, _b;
204
+ // Common patterns:
205
+ // - vite --port 3000
206
+ // - next dev -p 3000
207
+ // - next dev --port 3000
208
+ // - nuxt dev -p 3000
209
+ const m = (_b = (_a = script.match(/(?:--port|-p)\s+(\d{2,5})\b/)) !== null && _a !== void 0 ? _a : script.match(/\bPORT=(\d{2,5})\b/)) !== null && _b !== void 0 ? _b : null;
210
+ if (!m)
211
+ return null;
212
+ const port = Number(m[1]);
213
+ if (!Number.isFinite(port) || port < 1 || port > 65535)
214
+ return null;
215
+ return port;
216
+ }
217
+ async function resolveDefaultDevServerUrl(projectRoot) {
218
+ var _a, _b, _c, _d, _e, _f, _g, _h;
219
+ const envUrl = ((_a = process_1.default.env.SDD_DEV_SERVER_URL) === null || _a === void 0 ? void 0 : _a.trim()) ||
220
+ ((_b = process_1.default.env.OPSX_DEV_SERVER_URL) === null || _b === void 0 ? void 0 : _b.trim()) ||
221
+ ((_c = process_1.default.env.DEV_SERVER_URL) === null || _c === void 0 ? void 0 : _c.trim()) ||
222
+ "";
223
+ if (envUrl)
224
+ return envUrl;
225
+ const envPortRaw = ((_d = process_1.default.env.SDD_DEV_SERVER_PORT) === null || _d === void 0 ? void 0 : _d.trim()) ||
226
+ ((_e = process_1.default.env.OPSX_DEV_SERVER_PORT) === null || _e === void 0 ? void 0 : _e.trim()) ||
227
+ ((_f = process_1.default.env.DEV_SERVER_PORT) === null || _f === void 0 ? void 0 : _f.trim()) ||
228
+ "";
229
+ if (envPortRaw) {
230
+ const port = Number(envPortRaw);
231
+ if (Number.isFinite(port) && port >= 1 && port <= 65535) {
232
+ return `http://localhost:${port}`;
233
+ }
234
+ }
235
+ try {
236
+ const pkgText = await promises_1.default.readFile(path_1.default.join(projectRoot, "package.json"), "utf8");
237
+ const pkg = JSON.parse(pkgText);
238
+ const devScript = (_h = (_g = pkg.scripts) === null || _g === void 0 ? void 0 : _g.dev) !== null && _h !== void 0 ? _h : "";
239
+ const parsedPort = devScript ? parseDevPortFromScript(devScript) : null;
240
+ if (parsedPort)
241
+ return `http://localhost:${parsedPort}`;
242
+ const deps = { ...pkg.dependencies, ...pkg.devDependencies };
243
+ // Heuristics: Next.js defaults to 3000; Vite defaults to 5173.
244
+ if (deps.next)
245
+ return "http://localhost:3000";
246
+ if (deps.vite)
247
+ return "http://localhost:5173";
248
+ }
249
+ catch {
250
+ // ignore and fallback
251
+ }
252
+ return "http://localhost:5173";
253
+ }
192
254
  function getOpsxPathsByAgent(agent) {
193
255
  switch (agent) {
194
256
  case "cursor":
@@ -207,6 +269,7 @@ async function installOpsxConfig(projectRoot, agent) {
207
269
  const target = path_1.default.join(projectRoot, ".opsx", "config.json");
208
270
  const resolvedAgent = await (0, opsxDelivery_1.resolveOpsxAgent)(projectRoot, agent);
209
271
  const paths = getOpsxPathsByAgent(resolvedAgent);
272
+ const devServerUrl = await resolveDefaultDevServerUrl(projectRoot);
210
273
  const config = {
211
274
  version: 1,
212
275
  editor: paths.editor,
@@ -217,7 +280,7 @@ async function installOpsxConfig(projectRoot, agent) {
217
280
  packageManager: "pnpm",
218
281
  unitScript: "unit",
219
282
  playwrightCommand: "exec playwright test",
220
- devServerUrl: "http://localhost:5173",
283
+ devServerUrl,
221
284
  playwrightPicDir: "playwright-pic",
222
285
  },
223
286
  };
@@ -69,17 +69,19 @@ async function startFlowScaffold(options) {
69
69
  const file01Template = await (0, templates_1.loadTemplateText)("artifacts/01-需求分析报告.template.md");
70
70
  const file02Template = await (0, templates_1.loadTemplateText)("artifacts/02-改动点清单.template.md");
71
71
  const file03Template = await (0, templates_1.loadTemplateText)("artifacts/03-待确认问题清单.template.md");
72
+ const file05Template = await (0, templates_1.loadTemplateText)("artifacts/05-验收清单.template.md");
72
73
  await (0, fs_1.writeTextFileEnsuredDir)(path_1.default.join(outputRoot, "01-需求分析报告.md"), file01Template);
73
74
  await (0, fs_1.writeTextFileEnsuredDir)(path_1.default.join(outputRoot, "02-改动点清单.md"), file02Template);
74
75
  await (0, fs_1.writeTextFileEnsuredDir)(path_1.default.join(outputRoot, "03-待确认问题清单.md"), file03Template);
76
+ await (0, fs_1.writeTextFileEnsuredDir)(path_1.default.join(outputRoot, "05-验收清单.md"), file05Template);
75
77
  // 4) Step4 技术文档入口(草稿)
76
78
  const techDocTemplate = await (0, templates_1.loadTemplateText)("artifacts/04-技术文档.template.md");
77
79
  await (0, fs_1.writeTextFileEnsuredDir)(path_1.default.join(outputRoot, "04-技术文档草稿.md"), techDocTemplate);
78
80
  // 5) Step5 openspec 提案入口(草稿)
79
- const openspecEntryTemplate = await (0, templates_1.loadTemplateText)("artifacts/05-openspec-提案草稿.template.md");
80
- await (0, fs_1.writeTextFileEnsuredDir)(path_1.default.join(outputRoot, "05-openspec-提案草稿.md"), openspecEntryTemplate);
81
- const opsxAutoChainTemplate = await (0, templates_1.loadTemplateText)("artifacts/06-opsx-auto-chain.template.md");
82
- await (0, fs_1.writeTextFileEnsuredDir)(path_1.default.join(outputRoot, "06-opsx-自动串联指引.md"), opsxAutoChainTemplate);
81
+ const openspecEntryTemplate = await (0, templates_1.loadTemplateText)("artifacts/06-openspec-提案草稿.template.md");
82
+ await (0, fs_1.writeTextFileEnsuredDir)(path_1.default.join(outputRoot, "06-openspec-提案草稿.md"), openspecEntryTemplate);
83
+ const opsxAutoChainTemplate = await (0, templates_1.loadTemplateText)("artifacts/07-opsx-auto-chain.template.md");
84
+ await (0, fs_1.writeTextFileEnsuredDir)(path_1.default.join(outputRoot, "07-opsx-自动串联指引.md"), opsxAutoChainTemplate);
83
85
  const session = (0, sessionState_1.defaultSession)({
84
86
  product: options.product,
85
87
  version: options.version,
@@ -104,21 +106,22 @@ async function startFlowScaffold(options) {
104
106
  "",
105
107
  "## Step 3:人工核验(暂停点)",
106
108
  "用户补充待确认点并确认闭环后,才进入 Step 4。",
107
- "门禁:未闭环时禁止生成 01/02/04,禁止 /opsx-propose。",
109
+ "门禁:未闭环时禁止生成 01/02/04/05,禁止 /opsx-propose。",
108
110
  "**重要**:用户粘贴确认点时,AI 仅更新 `openspec/PRD` 文档,**禁止**修改 `src/` 等业务代码。",
109
111
  "",
110
- "## Step 4:生成 01 / 02 / 闭环03 / 04",
111
- "用户确认后产出:",
112
+ "## Step 4:生成 05 → 04 → 01 / 02 / 闭环03",
113
+ "用户确认后产出(顺序重要):",
112
114
  "- `03-待确认问题清单.md`(已闭合)",
113
- "- `01-需求分析报告.md`",
115
+ "- `05-验收清单.md`(从 `source/PRD.md` 逐条抽取 AC,标注「定义状态:已定义」)",
116
+ "- `04-技术文档草稿.md`(引用 AC-ID)",
117
+ "- `01-需求分析报告.md`(业务概述,非验收 SSOT)",
114
118
  "- `02-改动点清单.md`",
115
- "- `04-技术文档草稿.md`",
116
119
  "",
117
- "## Step 5–6:脚本门禁(禁止仅靠自然语言跳步)",
120
+ "## Step 5–7:脚本门禁(禁止仅靠自然语言跳步)",
118
121
  "每一步必须先执行 `NEXT.md` 中的 `npx sdd-flow-kit gate` / `phase advance` / `propose` / `deliver`。",
119
122
  "命令失败 (exit≠0) 时 **不得** 改 `src/` 或宣称完成。",
120
123
  "",
121
- "详见:`NEXT.md`、`06-opsx-自动串联指引.md`、`.cursor/skills/sdd-flow-kit/SKILL.md`。",
124
+ "详见:`NEXT.md`、`07-opsx-自动串联指引.md`、`.cursor/skills/sdd-flow-kit/SKILL.md`。",
122
125
  ].join("\n"));
123
126
  return {
124
127
  runId,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sdd-flow-kit",
3
- "version": "1.2.2",
3
+ "version": "1.3.0",
4
4
  "private": false,
5
5
  "description": "Cross-agent SDD automated development workflow kit (ADI/ADI-like).",
6
6
  "license": "MIT",
@@ -1,5 +1,8 @@
1
1
  # 01-需求分析报告
2
2
 
3
+ > **定位**:业务概述、范围、风险与确认点摘要。
4
+ > **不是**完整验收标准 — implement/deliver 的唯一验收 SSOT 为 `05-验收清单.md`。
5
+
3
6
  ## 业务概述
4
7
  - (在此处总结 PRD 核心目标)
5
8
 
@@ -16,7 +19,9 @@
16
19
  - 兼容性问题:
17
20
  - 副作用分析:
18
21
 
22
+ ## 已闭合确认点摘要
23
+ - (引用 `03-待确认问题清单.md` 中的结论;Intentional 偏差须同步到 05)
24
+
19
25
  ---
20
26
 
21
27
  > 矛盾点池状态:待填写(提问模式/交付模式)
22
-