sdd-flow-kit 1.0.25 → 1.1.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.
@@ -0,0 +1,195 @@
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.runPhaseAdvance = runPhaseAdvance;
7
+ const path_1 = __importDefault(require("path"));
8
+ const runGate_1 = require("./runGate");
9
+ const resolveRun_1 = require("./resolveRun");
10
+ const sessionState_1 = require("../core/sessionState");
11
+ const fs_1 = require("../core/fs");
12
+ const gitDiffGuard_1 = require("../core/gitDiffGuard");
13
+ async function writeNextMd(outputRoot, lines) {
14
+ const body = ["# NEXT(脚本门禁 — 请按顺序执行)", "", ...lines, ""].join("\n");
15
+ await (0, fs_1.writeTextFileEnsuredDir)(path_1.default.join(outputRoot, "NEXT.md"), body);
16
+ }
17
+ /**
18
+ * 阶段推进:先跑 gate,通过后更新 .session-state.json 与 NEXT.md。
19
+ */
20
+ async function runPhaseAdvance(options) {
21
+ const loaded = await (0, resolveRun_1.loadRunSession)(options.projectRoot, options.runId);
22
+ if (!loaded || !loaded.resolved || !loaded.session) {
23
+ return { ok: false, runId: null, message: "未找到 run 或 .session-state.json" };
24
+ }
25
+ const { resolved, session } = loaded;
26
+ const outputRoot = resolved.outputRoot;
27
+ const runId = resolved.runId;
28
+ let gate = await (0, runGate_1.runGate)({
29
+ projectRoot: options.projectRoot,
30
+ runId,
31
+ expect: "prd-fetched",
32
+ change: options.change,
33
+ });
34
+ if (options.to === "after-prd") {
35
+ gate = await (0, runGate_1.runGate)({ projectRoot: options.projectRoot, runId, expect: "prd-fetched" });
36
+ if (!gate.ok) {
37
+ (0, runGate_1.printGateResult)(gate);
38
+ return { ok: false, runId, message: "gate prd-fetched 未通过" };
39
+ }
40
+ const next = {
41
+ ...session,
42
+ phase: "A",
43
+ status: "awaiting_user_confirmation",
44
+ docModeOnly: true,
45
+ };
46
+ if (!options.dryRun) {
47
+ await (0, sessionState_1.writeSession)(outputRoot, next);
48
+ await writeNextMd(outputRoot, [
49
+ "## 当前:阶段 A(需求分析)",
50
+ "1. 执行 `02-需求分析提示词.md`",
51
+ "2. 若未闭合:仅更新 `03-待确认问题清单.md`,然后执行:",
52
+ "```bash",
53
+ `npx sdd-flow-kit gate --project-root ${options.projectRoot} --run-id ${runId} --expect questions-open`,
54
+ "```",
55
+ "3. 用户确认后生成 01/02/04,再执行:",
56
+ "```bash",
57
+ `npx sdd-flow-kit phase advance --project-root ${options.projectRoot} --run-id ${runId} --to docs-done`,
58
+ "```",
59
+ ]);
60
+ }
61
+ return { ok: true, runId, message: "已进入阶段 A(PRD 已就绪)" };
62
+ }
63
+ if (options.to === "docs-done") {
64
+ gate = await (0, runGate_1.runGate)({ projectRoot: options.projectRoot, runId, expect: "docs-closed" });
65
+ if (!gate.ok) {
66
+ (0, runGate_1.printGateResult)(gate);
67
+ return { ok: false, runId, message: "gate docs-closed 未通过(禁止带 src 改动)" };
68
+ }
69
+ const next = {
70
+ ...session,
71
+ phase: "B",
72
+ status: "ready_for_propose",
73
+ docModeOnly: true,
74
+ };
75
+ if (!options.dryRun) {
76
+ await (0, sessionState_1.writeSession)(outputRoot, next);
77
+ await writeNextMd(outputRoot, [
78
+ "## 当前:阶段 B 完成 → 进入 C(提案)",
79
+ "1. 执行 openspec propose(或):",
80
+ "```bash",
81
+ `npx sdd-flow-kit propose --project-root ${options.projectRoot} --run-id ${runId} --change <kebab-name>`,
82
+ "```",
83
+ "2. 提案 apply-ready 后:",
84
+ "```bash",
85
+ `npx sdd-flow-kit phase advance --project-root ${options.projectRoot} --run-id ${runId} --to propose-done --change <name>`,
86
+ "```",
87
+ ]);
88
+ }
89
+ return { ok: true, runId, message: "阶段 B 完成,可进入 propose" };
90
+ }
91
+ if (options.to === "propose-done") {
92
+ const changeName = options.change || session.activeChange;
93
+ if (!changeName) {
94
+ return { ok: false, runId, message: "需要 --change <name>" };
95
+ }
96
+ gate = await (0, runGate_1.runGate)({
97
+ projectRoot: options.projectRoot,
98
+ runId,
99
+ expect: "propose-ready",
100
+ change: changeName,
101
+ });
102
+ if (!gate.ok) {
103
+ (0, runGate_1.printGateResult)(gate);
104
+ return { ok: false, runId, message: "gate propose-ready 未通过" };
105
+ }
106
+ const next = {
107
+ ...session,
108
+ phase: "C",
109
+ status: "propose_done",
110
+ docModeOnly: true,
111
+ activeChange: changeName,
112
+ };
113
+ if (!options.dryRun) {
114
+ await (0, sessionState_1.writeSession)(outputRoot, next);
115
+ await (0, fs_1.writeTextFileEnsuredDir)(path_1.default.join(options.projectRoot, ".opsx-active-change"), `${changeName}\n`);
116
+ const fe = path_1.default.join(options.projectRoot, "frontend", ".opsx-active-change");
117
+ await (0, fs_1.writeTextFileEnsuredDir)(fe, `${changeName}\n`).catch(() => undefined);
118
+ await writeNextMd(outputRoot, [
119
+ "## 当前:阶段 C 完成 → 进入 D(实现)",
120
+ "```bash",
121
+ `npx sdd-flow-kit phase advance --project-root ${options.projectRoot} --run-id ${runId} --to impl --change ${changeName}`,
122
+ "```",
123
+ ]);
124
+ }
125
+ return { ok: true, runId, message: `提案就绪: ${changeName}` };
126
+ }
127
+ if (options.to === "impl") {
128
+ const changeName = options.change || session.activeChange;
129
+ if (!changeName) {
130
+ return { ok: false, runId, message: "需要 --change <name>" };
131
+ }
132
+ gate = await (0, runGate_1.runGate)({
133
+ projectRoot: options.projectRoot,
134
+ runId,
135
+ expect: "propose-ready",
136
+ change: changeName,
137
+ });
138
+ if (!gate.ok) {
139
+ (0, runGate_1.printGateResult)(gate);
140
+ return { ok: false, runId, message: "propose 未就绪,禁止进入实现" };
141
+ }
142
+ const gitRoot = (0, gitDiffGuard_1.resolveGitRoot)(path_1.default.join(options.projectRoot, "frontend") === path_1.default.join(options.projectRoot)
143
+ ? options.projectRoot
144
+ : path_1.default.join(options.projectRoot, "frontend"));
145
+ const head = (0, gitDiffGuard_1.gitDiffNameOnly)(gitRoot).head;
146
+ const next = {
147
+ ...session,
148
+ phase: "D",
149
+ status: "apply_in_progress",
150
+ docModeOnly: false,
151
+ activeChange: changeName,
152
+ implGitBaseline: head,
153
+ };
154
+ if (!options.dryRun) {
155
+ await (0, sessionState_1.writeSession)(outputRoot, next);
156
+ await writeNextMd(outputRoot, [
157
+ "## 当前:阶段 D(允许修改 src/)",
158
+ "实现前门禁:",
159
+ "```bash",
160
+ `npx sdd-flow-kit gate --project-root ${options.projectRoot} --run-id ${runId} --expect impl-allowed --change ${changeName}`,
161
+ "```",
162
+ "完成后交付:",
163
+ "```bash",
164
+ `npx sdd-flow-kit deliver --project-root ${options.projectRoot} --run-id ${runId} --change ${changeName}`,
165
+ "```",
166
+ ]);
167
+ }
168
+ return { ok: true, runId, message: "已进入实现阶段(docModeOnly=false)" };
169
+ }
170
+ if (options.to === "shipped") {
171
+ const changeName = options.change || session.activeChange;
172
+ gate = await (0, runGate_1.runGate)({
173
+ projectRoot: options.projectRoot,
174
+ runId,
175
+ expect: "shipped",
176
+ change: changeName,
177
+ });
178
+ if (!gate.ok) {
179
+ (0, runGate_1.printGateResult)(gate);
180
+ return { ok: false, runId, message: "gate shipped 未通过" };
181
+ }
182
+ const next = {
183
+ ...session,
184
+ phase: "done",
185
+ status: "complete",
186
+ docModeOnly: false,
187
+ };
188
+ if (!options.dryRun) {
189
+ await (0, sessionState_1.writeSession)(outputRoot, next);
190
+ await writeNextMd(outputRoot, ["## ✅ SDD 流程已完成(delivery-pass)"]);
191
+ }
192
+ return { ok: true, runId, message: "流程完成" };
193
+ }
194
+ return { ok: false, runId, message: `未知 --to: ${options.to}` };
195
+ }
@@ -0,0 +1,82 @@
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.runPropose = runPropose;
7
+ const path_1 = __importDefault(require("path"));
8
+ const child_process_1 = require("child_process");
9
+ const resolveRun_1 = require("./resolveRun");
10
+ const runGate_1 = require("./runGate");
11
+ const fs_1 = require("../core/fs");
12
+ const opsxDelivery_1 = require("../core/opsxDelivery");
13
+ /**
14
+ * 创建/校验 openspec change,并检查 apply-ready(不代替 AI 写 proposal 正文,但强制 gate)。
15
+ */
16
+ async function runPropose(options) {
17
+ const loaded = await (0, resolveRun_1.loadRunSession)(options.projectRoot, options.runId);
18
+ if (!(loaded === null || loaded === void 0 ? void 0 : loaded.resolved)) {
19
+ return { ok: false, detail: "未找到 PRD run" };
20
+ }
21
+ const docsGate = await (0, runGate_1.runGate)({
22
+ projectRoot: options.projectRoot,
23
+ runId: loaded.resolved.runId,
24
+ expect: "docs-closed",
25
+ });
26
+ if (!docsGate.ok) {
27
+ (0, runGate_1.printGateResult)(docsGate);
28
+ return { ok: false, detail: "docs-closed gate 未通过,禁止 propose" };
29
+ }
30
+ const roots = [options.projectRoot, path_1.default.join(options.projectRoot, "frontend")];
31
+ let cwd = options.projectRoot;
32
+ for (const r of roots) {
33
+ if (await (0, opsxDelivery_1.pathExists)(path_1.default.join(r, "openspec"))) {
34
+ cwd = r;
35
+ break;
36
+ }
37
+ }
38
+ if (options.dryRun) {
39
+ return { ok: true, detail: `dry-run: would openspec new change ${options.change}` };
40
+ }
41
+ const exists = (0, child_process_1.spawnSync)("openspec", ["status", "--change", options.change, "--json"], {
42
+ cwd,
43
+ encoding: "utf8",
44
+ });
45
+ if (exists.status !== 0) {
46
+ const created = (0, child_process_1.spawnSync)("openspec", ["new", "change", options.change], {
47
+ cwd,
48
+ stdio: "inherit",
49
+ });
50
+ if (created.status !== 0) {
51
+ return { ok: false, detail: `openspec new change 失败: ${options.change}` };
52
+ }
53
+ }
54
+ await (0, fs_1.writeTextFileEnsuredDir)(path_1.default.join(cwd, ".opsx-active-change"), `${options.change}\n`);
55
+ const proposeNote = [
56
+ "# Propose 脚本检查点",
57
+ "",
58
+ `change: \`${options.change}\``,
59
+ "",
60
+ "AI 须按 openspec-propose skill 生成 proposal/design/specs/tasks,完成后执行:",
61
+ "```bash",
62
+ `npx sdd-flow-kit gate --project-root ${options.projectRoot} --run-id ${loaded.resolved.runId} --expect propose-ready --change ${options.change}`,
63
+ `npx sdd-flow-kit phase advance --project-root ${options.projectRoot} --run-id ${loaded.resolved.runId} --to propose-done --change ${options.change}`,
64
+ "```",
65
+ "",
66
+ ].join("\n");
67
+ await (0, fs_1.writeTextFileEnsuredDir)(path_1.default.join(loaded.resolved.outputRoot, "05-openspec-提案草稿.md"), proposeNote);
68
+ const ready = await (0, runGate_1.runGate)({
69
+ projectRoot: options.projectRoot,
70
+ runId: loaded.resolved.runId,
71
+ expect: "propose-ready",
72
+ change: options.change,
73
+ });
74
+ (0, runGate_1.printGateResult)(ready);
75
+ if (!ready.ok) {
76
+ return {
77
+ ok: false,
78
+ detail: "change 已创建,但尚未 apply-ready;请生成 artifacts 后重跑 gate propose-ready",
79
+ };
80
+ }
81
+ return { ok: true, detail: `change ${options.change} apply-ready` };
82
+ }
@@ -4,19 +4,21 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.setupProject = setupProject;
7
- const node_child_process_1 = require("node:child_process");
8
- const node_path_1 = __importDefault(require("node:path"));
9
- const promises_1 = require("node:readline/promises");
10
- const node_process_1 = __importDefault(require("node:process"));
7
+ const child_process_1 = require("child_process");
8
+ const path_1 = __importDefault(require("path"));
9
+ const process_1 = __importDefault(require("process"));
11
10
  const fs_1 = require("../core/fs");
11
+ const readlinePrompt_1 = require("../core/readlinePrompt");
12
+ const utils_1 = require("../core/utils");
12
13
  const opsxDelivery_1 = require("../core/opsxDelivery");
13
14
  const templates_1 = require("../core/templates");
14
15
  const EDITOR_CHOICES = ["claude-code", "cursor", "codex", "openclaw"];
15
16
  const PROJECT_CHOICES = ["OMS", "ADI", "欢盟", "AD Tools"];
16
17
  function runGit(args, cwd) {
17
- const out = (0, node_child_process_1.spawnSync)("git", args, { cwd, encoding: "utf8" });
18
+ var _a, _b;
19
+ const out = (0, child_process_1.spawnSync)("git", args, { cwd, encoding: "utf8" });
18
20
  if (out.status !== 0) {
19
- const err = out.stderr?.trim() || out.stdout?.trim() || `git ${args.join(" ")} failed`;
21
+ const err = ((_a = out.stderr) === null || _a === void 0 ? void 0 : _a.trim()) || ((_b = out.stdout) === null || _b === void 0 ? void 0 : _b.trim()) || `git ${args.join(" ")} failed`;
20
22
  throw new Error(err);
21
23
  }
22
24
  return out.stdout.trim();
@@ -45,9 +47,9 @@ function listLocalBranches(projectRoot) {
45
47
  }
46
48
  }
47
49
  async function askAgent(defaultAgent) {
48
- if (!node_process_1.default.stdin.isTTY)
50
+ if (!process_1.default.stdin.isTTY)
49
51
  return defaultAgent;
50
- const rl = (0, promises_1.createInterface)({ input: node_process_1.default.stdin, output: node_process_1.default.stdout });
52
+ const rl = (0, readlinePrompt_1.createPromptInterface)();
51
53
  const prompt = [
52
54
  "请选择开发环境:",
53
55
  "1) claude-code (default)",
@@ -56,7 +58,7 @@ async function askAgent(defaultAgent) {
56
58
  "4) openclaw",
57
59
  "输入编号(回车默认 1):",
58
60
  ].join("\n");
59
- const answer = (await rl.question(`${prompt}\n`)).trim();
61
+ const answer = (await (0, readlinePrompt_1.promptLine)(rl, `${prompt}\n`)).trim();
60
62
  rl.close();
61
63
  if (!answer)
62
64
  return defaultAgent;
@@ -66,17 +68,17 @@ async function askAgent(defaultAgent) {
66
68
  return EDITOR_CHOICES[idx - 1];
67
69
  }
68
70
  async function askBranch(defaultBranch) {
69
- if (!node_process_1.default.stdin.isTTY)
71
+ if (!process_1.default.stdin.isTTY)
70
72
  return defaultBranch;
71
- const rl = (0, promises_1.createInterface)({ input: node_process_1.default.stdin, output: node_process_1.default.stdout });
72
- const answer = (await rl.question(`请输入开发分支(默认 ${defaultBranch}):\n`)).trim();
73
+ const rl = (0, readlinePrompt_1.createPromptInterface)();
74
+ const answer = (await (0, readlinePrompt_1.promptLine)(rl, `请输入开发分支(默认 ${defaultBranch}):\n`)).trim();
73
75
  rl.close();
74
76
  return answer || defaultBranch;
75
77
  }
76
78
  async function askProjectKind(defaultKind) {
77
- if (!node_process_1.default.stdin.isTTY)
79
+ if (!process_1.default.stdin.isTTY)
78
80
  return defaultKind;
79
- const rl = (0, promises_1.createInterface)({ input: node_process_1.default.stdin, output: node_process_1.default.stdout });
81
+ const rl = (0, readlinePrompt_1.createPromptInterface)();
80
82
  const prompt = [
81
83
  "请选择当前项目类型(用于生成 PRD 下载 skill):",
82
84
  "1) OMS (default)",
@@ -85,7 +87,7 @@ async function askProjectKind(defaultKind) {
85
87
  "4) AD Tools",
86
88
  "输入编号(回车默认 1):",
87
89
  ].join("\n");
88
- const answer = (await rl.question(`${prompt}\n`)).trim();
90
+ const answer = (await (0, readlinePrompt_1.promptLine)(rl, `${prompt}\n`)).trim();
89
91
  rl.close();
90
92
  if (!answer)
91
93
  return defaultKind;
@@ -114,7 +116,7 @@ function skillInstallPath(projectRoot, agent) {
114
116
  const rel = relMap[agent];
115
117
  if (!rel)
116
118
  return null;
117
- return node_path_1.default.join(projectRoot, rel);
119
+ return path_1.default.join(projectRoot, rel);
118
120
  }
119
121
  function allSkillInstallPaths(projectRoot) {
120
122
  const agents = ["cursor", "claude-code", "codex", "openclaw"];
@@ -135,19 +137,19 @@ async function installAgentSkill(projectRoot) {
135
137
  : target.includes("/.codex/")
136
138
  ? "codex"
137
139
  : "openclaw";
138
- const content = template.replaceAll("{{AGENT_NAME}}", agentName);
140
+ const content = (0, utils_1.replaceAllStr)(template, "{{AGENT_NAME}}", agentName);
139
141
  await (0, fs_1.writeTextFileEnsuredDir)(target, content);
140
142
  }
141
143
  }
142
144
  async function installSddCursorRule(projectRoot) {
143
- const rulePath = node_path_1.default.join(projectRoot, ".cursor", "rules", "sdd-flow-kit-gates.mdc");
145
+ const rulePath = path_1.default.join(projectRoot, ".cursor", "rules", "sdd-flow-kit-gates.mdc");
144
146
  const template = await (0, templates_1.loadTemplateText)("rules/sdd-no-implement-until-apply.mdc.template");
145
147
  await (0, fs_1.writeTextFileEnsuredDir)(rulePath, template);
146
148
  }
147
149
  function renderVars(template, vars) {
148
150
  let out = template;
149
151
  for (const [k, v] of Object.entries(vars)) {
150
- out = out.replaceAll(`{{${k}}}`, v);
152
+ out = (0, utils_1.replaceAllStr)(out, `{{${k}}}`, v);
151
153
  }
152
154
  return out;
153
155
  }
@@ -166,7 +168,7 @@ function docSkillDirName(kind) {
166
168
  }
167
169
  async function installDocsSkill(projectRoot, kind) {
168
170
  const { dir, prefix } = docSkillDirName(kind);
169
- const docsDir = node_path_1.default.join(projectRoot, "docs", dir);
171
+ const docsDir = path_1.default.join(projectRoot, "docs", dir);
170
172
  const pyTemplate = await (0, templates_1.loadTemplateText)("skills/confluence-doc.py.template");
171
173
  const mdTemplate = await (0, templates_1.loadTemplateText)("skills/doc-skill.SKILL.md.template");
172
174
  const md = renderVars(mdTemplate, {
@@ -176,9 +178,9 @@ async function installDocsSkill(projectRoot, kind) {
176
178
  PRODUCT_PREFIX_LOWER: prefix.toLowerCase(),
177
179
  SKILL_DIR: dir,
178
180
  });
179
- const py = pyTemplate.replaceAll("{{DOC_PRODUCT_PREFIX}}", prefix);
180
- await (0, fs_1.writeTextFileEnsuredDir)(node_path_1.default.join(docsDir, "SKILL.md"), md);
181
- await (0, fs_1.writeTextFileEnsuredDir)(node_path_1.default.join(docsDir, "confluence-doc.py"), py);
181
+ const py = (0, utils_1.replaceAllStr)(pyTemplate, "{{DOC_PRODUCT_PREFIX}}", prefix);
182
+ await (0, fs_1.writeTextFileEnsuredDir)(path_1.default.join(docsDir, "SKILL.md"), md);
183
+ await (0, fs_1.writeTextFileEnsuredDir)(path_1.default.join(docsDir, "confluence-doc.py"), py);
182
184
  return { dirPath: docsDir };
183
185
  }
184
186
  function getOpsxPathsByAgent(agent) {
@@ -196,7 +198,7 @@ function getOpsxPathsByAgent(agent) {
196
198
  }
197
199
  }
198
200
  async function installOpsxConfig(projectRoot, agent) {
199
- const target = node_path_1.default.join(projectRoot, ".opsx", "config.json");
201
+ const target = path_1.default.join(projectRoot, ".opsx", "config.json");
200
202
  const resolvedAgent = await (0, opsxDelivery_1.resolveOpsxAgent)(projectRoot, agent);
201
203
  const paths = getOpsxPathsByAgent(resolvedAgent);
202
204
  const config = {
@@ -216,11 +218,12 @@ async function installOpsxConfig(projectRoot, agent) {
216
218
  await (0, fs_1.writeTextFileEnsuredDir)(target, JSON.stringify(config, null, 2));
217
219
  }
218
220
  async function setupProject(options) {
219
- const defaultAgent = options.agent ?? "claude-code";
221
+ var _a, _b, _c;
222
+ const defaultAgent = (_a = options.agent) !== null && _a !== void 0 ? _a : "claude-code";
220
223
  const selectedAgent = options.yes ? defaultAgent : await askAgent(defaultAgent);
221
- const defaultKind = options.projectKind ?? "OMS";
224
+ const defaultKind = (_b = options.projectKind) !== null && _b !== void 0 ? _b : "OMS";
222
225
  const selectedKind = options.yes ? defaultKind : await askProjectKind(defaultKind);
223
- const defaultBranch = options.branch ?? pickDefaultBranch(options.projectRoot);
226
+ const defaultBranch = (_c = options.branch) !== null && _c !== void 0 ? _c : pickDefaultBranch(options.projectRoot);
224
227
  const selectedBranch = options.yes ? defaultBranch : await askBranch(defaultBranch);
225
228
  const plannedActions = [];
226
229
  const branches = listLocalBranches(options.projectRoot);
@@ -268,9 +271,9 @@ async function setupProject(options) {
268
271
  plannedActions.push(`would install skill: ${skillPath}`);
269
272
  }
270
273
  const { dir } = docSkillDirName(selectedKind);
271
- plannedActions.push(`would install docs skill: ${node_path_1.default.join(options.projectRoot, "docs", dir)}`);
272
- plannedActions.push(`would install opsx config: ${node_path_1.default.join(options.projectRoot, ".opsx", "config.json")}`);
273
- plannedActions.push(`would install cursor rule: ${node_path_1.default.join(options.projectRoot, ".cursor", "rules", "sdd-flow-kit-gates.mdc")}`);
274
+ plannedActions.push(`would install docs skill: ${path_1.default.join(options.projectRoot, "docs", dir)}`);
275
+ plannedActions.push(`would install opsx config: ${path_1.default.join(options.projectRoot, ".opsx", "config.json")}`);
276
+ plannedActions.push(`would install cursor rule: ${path_1.default.join(options.projectRoot, ".cursor", "rules", "sdd-flow-kit-gates.mdc")}`);
274
277
  }
275
278
  else {
276
279
  await installAgentSkill(options.projectRoot);
@@ -304,7 +307,7 @@ async function setupProject(options) {
304
307
  ...plannedActions.map((x) => `- ${x}`),
305
308
  "",
306
309
  ].join("\n");
307
- await (0, fs_1.writeTextFileEnsuredDir)(node_path_1.default.join(options.projectRoot, ".sdd-flow-kit", "setup-summary.md"), summary);
310
+ await (0, fs_1.writeTextFileEnsuredDir)(path_1.default.join(options.projectRoot, ".sdd-flow-kit", "setup-summary.md"), summary);
308
311
  return {
309
312
  agent: selectedAgent,
310
313
  projectKind: selectedKind,
@@ -4,24 +4,26 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.startFlowScaffold = startFlowScaffold;
7
- const node_path_1 = __importDefault(require("node:path"));
8
- const promises_1 = __importDefault(require("node:fs/promises"));
7
+ const path_1 = __importDefault(require("path"));
8
+ const promises_1 = __importDefault(require("fs/promises"));
9
9
  const utils_1 = require("../core/utils");
10
10
  const fs_1 = require("../core/fs");
11
11
  const templates_1 = require("../core/templates");
12
12
  const inferProject_1 = require("../core/inferProject");
13
+ const sessionState_1 = require("../core/sessionState");
14
+ const writeNext_1 = require("../core/writeNext");
13
15
  function renderTemplate(template, vars) {
14
16
  let out = template;
15
17
  for (const [k, v] of Object.entries(vars)) {
16
- out = out.replaceAll(`{{${k}}}`, v);
18
+ out = (0, utils_1.replaceAllStr)(out, `{{${k}}}`, v);
17
19
  }
18
20
  return out;
19
21
  }
20
22
  async function startFlowScaffold(options) {
21
23
  const now = new Date();
22
24
  const runId = `${(0, utils_1.formatYYYYMMDD)(now)}-${options.product}-${options.version}`;
23
- const outputRoot = node_path_1.default.join(options.projectRoot, "openspec", "PRD", runId);
24
- const sourceDir = node_path_1.default.join(outputRoot, "source");
25
+ const outputRoot = path_1.default.join(options.projectRoot, "openspec", "PRD", runId);
26
+ const sourceDir = path_1.default.join(outputRoot, "source");
25
27
  await promises_1.default.mkdir(sourceDir, { recursive: true });
26
28
  // 1) Step1: doc-skill 指引(按产品解析脚本路径,支持 monorepo frontend)
27
29
  const kind = (0, inferProject_1.productToKind)(options.product);
@@ -33,23 +35,23 @@ async function startFlowScaffold(options) {
33
35
  DOC_SKILL_REL: docSkill.relFromRoot.replace(/\\/g, "/"),
34
36
  VERSION_EXAMPLE: options.version,
35
37
  });
36
- await (0, fs_1.writeTextFileEnsuredDir)(node_path_1.default.join(outputRoot, "01-获取需求文档指引.md"), step1);
38
+ await (0, fs_1.writeTextFileEnsuredDir)(path_1.default.join(outputRoot, "01-获取需求文档指引.md"), step1);
37
39
  // source 占位:让第 2 步提示词在任何工具里都能直接读
38
- await (0, fs_1.writeTextFileEnsuredDir)(node_path_1.default.join(sourceDir, "PRD.md"), [
40
+ await (0, fs_1.writeTextFileEnsuredDir)(path_1.default.join(sourceDir, "PRD.md"), [
39
41
  "# source/PRD.md",
40
42
  "",
41
43
  "请把《PRD》markdown 内容粘贴到这里(由 `adi-doc-skill` 下载并回填)。",
42
44
  "",
43
45
  "- 建议:保持原有标题层级与编号。",
44
46
  ].join("\n"));
45
- await (0, fs_1.writeTextFileEnsuredDir)(node_path_1.default.join(sourceDir, "接口文档.md"), [
47
+ await (0, fs_1.writeTextFileEnsuredDir)(path_1.default.join(sourceDir, "接口文档.md"), [
46
48
  "# source/接口文档.md",
47
49
  "",
48
50
  "(可选)如果你已经有接口文档,请粘贴到这里;没有也不影响后续 SDD 循环。",
49
51
  "",
50
52
  "- 注意:接口文档中的字段/示例越完整,分析报告越贴近落地。",
51
53
  ].join("\n"));
52
- await (0, fs_1.writeTextFileEnsuredDir)(node_path_1.default.join(sourceDir, "openspec-knowledge-base-summary.md"), [
54
+ await (0, fs_1.writeTextFileEnsuredDir)(path_1.default.join(sourceDir, "openspec-knowledge-base-summary.md"), [
53
55
  "# source/openspec-knowledge-base-summary.md",
54
56
  "",
55
57
  "(可选)现有知识库摘要/快速索引,建议由你在执行第 2 步前手动整理。",
@@ -60,35 +62,32 @@ async function startFlowScaffold(options) {
60
62
  const sddLoopPrompt = await (0, templates_1.loadTemplateText)("prompts/02-sdd-loop-prompt.md");
61
63
  const step2PromptTemplate = await (0, templates_1.loadTemplateText)("artifacts/02-需求分析提示词.template.md");
62
64
  const step2Prompt = renderTemplate(step2PromptTemplate, { SDD_LOOP_PROMPT: sddLoopPrompt });
63
- await (0, fs_1.writeTextFileEnsuredDir)(node_path_1.default.join(outputRoot, "02-需求分析提示词.md"), step2Prompt);
65
+ await (0, fs_1.writeTextFileEnsuredDir)(path_1.default.join(outputRoot, "02-需求分析提示词.md"), step2Prompt);
64
66
  // 3) Step2 输出文件占位(由 AI 工具生成并覆盖)
65
67
  const file01Template = await (0, templates_1.loadTemplateText)("artifacts/01-需求分析报告.template.md");
66
68
  const file02Template = await (0, templates_1.loadTemplateText)("artifacts/02-改动点清单.template.md");
67
69
  const file03Template = await (0, templates_1.loadTemplateText)("artifacts/03-待确认问题清单.template.md");
68
- await (0, fs_1.writeTextFileEnsuredDir)(node_path_1.default.join(outputRoot, "01-需求分析报告.md"), file01Template);
69
- await (0, fs_1.writeTextFileEnsuredDir)(node_path_1.default.join(outputRoot, "02-改动点清单.md"), file02Template);
70
- await (0, fs_1.writeTextFileEnsuredDir)(node_path_1.default.join(outputRoot, "03-待确认问题清单.md"), file03Template);
70
+ await (0, fs_1.writeTextFileEnsuredDir)(path_1.default.join(outputRoot, "01-需求分析报告.md"), file01Template);
71
+ await (0, fs_1.writeTextFileEnsuredDir)(path_1.default.join(outputRoot, "02-改动点清单.md"), file02Template);
72
+ await (0, fs_1.writeTextFileEnsuredDir)(path_1.default.join(outputRoot, "03-待确认问题清单.md"), file03Template);
71
73
  // 4) Step4 技术文档入口(草稿)
72
74
  const techDocTemplate = await (0, templates_1.loadTemplateText)("artifacts/04-技术文档.template.md");
73
- await (0, fs_1.writeTextFileEnsuredDir)(node_path_1.default.join(outputRoot, "04-技术文档草稿.md"), techDocTemplate);
75
+ await (0, fs_1.writeTextFileEnsuredDir)(path_1.default.join(outputRoot, "04-技术文档草稿.md"), techDocTemplate);
74
76
  // 5) Step5 openspec 提案入口(草稿)
75
77
  const openspecEntryTemplate = await (0, templates_1.loadTemplateText)("artifacts/05-openspec-提案草稿.template.md");
76
- await (0, fs_1.writeTextFileEnsuredDir)(node_path_1.default.join(outputRoot, "05-openspec-提案草稿.md"), openspecEntryTemplate);
78
+ await (0, fs_1.writeTextFileEnsuredDir)(path_1.default.join(outputRoot, "05-openspec-提案草稿.md"), openspecEntryTemplate);
77
79
  const opsxAutoChainTemplate = await (0, templates_1.loadTemplateText)("artifacts/06-opsx-auto-chain.template.md");
78
- await (0, fs_1.writeTextFileEnsuredDir)(node_path_1.default.join(outputRoot, "06-opsx-自动串联指引.md"), opsxAutoChainTemplate);
79
- await (0, fs_1.writeTextFileEnsuredDir)(node_path_1.default.join(outputRoot, ".session-state.json"), JSON.stringify({
80
+ await (0, fs_1.writeTextFileEnsuredDir)(path_1.default.join(outputRoot, "06-opsx-自动串联指引.md"), opsxAutoChainTemplate);
81
+ const session = (0, sessionState_1.defaultSession)({
80
82
  product: options.product,
81
83
  version: options.version,
82
84
  runId,
83
85
  outputRoot,
84
- status: "awaiting_user_confirmation",
85
- docModeOnly: true,
86
- autoChainOpsx: true,
87
- phase: "A_or_B",
88
- createdAtISO: new Date().toISOString(),
89
- }, null, 2));
86
+ });
87
+ await (0, sessionState_1.writeSession)(outputRoot, session);
88
+ await (0, writeNext_1.writeInitialNext)(outputRoot, options.projectRoot, runId);
90
89
  // runDir 自述
91
- await (0, fs_1.writeTextFileEnsuredDir)(node_path_1.default.join(outputRoot, "RUNME.md"), [
90
+ await (0, fs_1.writeTextFileEnsuredDir)(path_1.default.join(outputRoot, "RUNME.md"), [
92
91
  "# RUNME(SDD 流程执行说明)",
93
92
  "",
94
93
  `本次 runId: \`${runId}\``,
@@ -113,16 +112,11 @@ async function startFlowScaffold(options) {
113
112
  "- `02-改动点清单.md`",
114
113
  "- `04-技术文档草稿.md`",
115
114
  "",
116
- "## Step 5–6:自动串联 OPSX(默认)",
117
- "03 闭环且 01/02/04 完成后,**同一 AI 回合**读 `06-opsx-自动串联指引.md`:",
118
- "1. `/opsx-propose`(输入 01、02、UI;UI 默认读 `source/PRD.md` 原型目录)",
119
- "2. 提案 apply-ready 后立即 `/opsx-apply` + `pnpm run opsx:delivery-pipeline`(exit 0)",
120
- "勿停下来问「是否继续」。仅当用户说「只提案」或 `.session-state.json` 中 `autoChainOpsx: false` 时暂停。",
115
+ "## Step 5–6:脚本门禁(禁止仅靠自然语言跳步)",
116
+ "每一步必须先执行 `NEXT.md` 中的 `npx sdd-flow-kit gate` / `phase advance` / `propose` / `deliver`。",
117
+ "命令失败 (exit≠0) **不得** `src/` 或宣称完成。",
121
118
  "",
122
- "## Step 6 明细:Apply + TDD + Playwright(阶段 D)",
123
- "依次执行 skill:`openspec-apply-change` → `tdd-script` → `openspec-superpowers-pipeline` → `opsx-dev-delivery-pipeline`。",
124
- "仓库根执行:`pnpm run opsx:delivery-pipeline -- <change-name>`(须 exit 0)。",
125
- "禁止跳过 TDD / Playwright 裸写代码收尾。详见 `.cursor/skills/sdd-flow-kit/SKILL.md` 阶段 D。",
119
+ "详见:`NEXT.md`、`06-opsx-自动串联指引.md`、`.cursor/skills/sdd-flow-kit/SKILL.md`。",
126
120
  ].join("\n"));
127
121
  return {
128
122
  runId,
package/package.json CHANGED
@@ -1,9 +1,12 @@
1
1
  {
2
2
  "name": "sdd-flow-kit",
3
- "version": "1.0.25",
3
+ "version": "1.1.0",
4
4
  "private": false,
5
5
  "description": "Cross-agent SDD automated development workflow kit (ADI/ADI-like).",
6
6
  "license": "MIT",
7
+ "engines": {
8
+ "node": ">=14.16.0"
9
+ },
7
10
  "type": "commonjs",
8
11
  "main": "dist/index.js",
9
12
  "files": [