sdd-flow-kit 1.0.22 → 1.0.27

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.
@@ -4,16 +4,16 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.loadTemplateText = loadTemplateText;
7
- const node_path_1 = __importDefault(require("node:path"));
7
+ const path_1 = __importDefault(require("path"));
8
8
  const fs_1 = require("./fs");
9
9
  function resolveCandidatePaths(relPathFromPackageRoot) {
10
10
  // 在 TS 开发态:<pkg>/src/templates/xxx
11
11
  // 在编译态:<pkg>/dist/cli.js -> __dirname=<pkg>/dist,所以 src/templates 要向上找两级
12
12
  const here = __dirname; // src/core/* 或 dist/*
13
- const pkgRoot = node_path_1.default.resolve(here, "../.."); // 兼容 src/ 与 dist/
13
+ const pkgRoot = path_1.default.resolve(here, "../.."); // 兼容 src/ 与 dist/
14
14
  return [
15
- node_path_1.default.join(pkgRoot, "src", "templates", relPathFromPackageRoot),
16
- node_path_1.default.join(pkgRoot, "dist", "templates", relPathFromPackageRoot),
15
+ path_1.default.join(pkgRoot, "src", "templates", relPathFromPackageRoot),
16
+ path_1.default.join(pkgRoot, "dist", "templates", relPathFromPackageRoot),
17
17
  ];
18
18
  }
19
19
  async function loadTemplateText(relPathFromPackageRoot) {
@@ -1,6 +1,7 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
3
  exports.formatYYYYMMDD = formatYYYYMMDD;
4
+ exports.replaceAllStr = replaceAllStr;
4
5
  exports.ensurePosixRelativePath = ensurePosixRelativePath;
5
6
  /**
6
7
  * 生成 YYYYMMDD 字符串,用于 openspec/PRD 目录命名。
@@ -11,7 +12,13 @@ function formatYYYYMMDD(d) {
11
12
  const dd = String(d.getDate()).padStart(2, "0");
12
13
  return `${yyyy}${mm}${dd}`;
13
14
  }
15
+ /** Node 14 无 String.prototype.replaceAll(需 Node 15+) */
16
+ function replaceAllStr(text, search, replacement) {
17
+ if (!search)
18
+ return text;
19
+ return text.split(search).join(replacement);
20
+ }
14
21
  function ensurePosixRelativePath(p) {
15
22
  // 用于模板占位符中展示,避免反斜杠导致工具误解析。
16
- return p.replaceAll("\\", "/");
23
+ return replaceAllStr(p, "\\", "/");
17
24
  }
@@ -36,20 +36,21 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
36
36
  return (mod && mod.__esModule) ? mod : { "default": mod };
37
37
  };
38
38
  Object.defineProperty(exports, "__esModule", { value: true });
39
- const node_path_1 = __importDefault(require("node:path"));
40
- const node_process_1 = __importDefault(require("node:process"));
39
+ const path_1 = __importDefault(require("path"));
40
+ const process_1 = __importDefault(require("process"));
41
41
  const inferProject_1 = require("./core/inferProject");
42
42
  const opsxDelivery_1 = require("./core/opsxDelivery");
43
43
  const setupProject_1 = require("./steps/setupProject");
44
44
  async function inferAgent(projectRoot) {
45
- const fromEnv = (node_process_1.default.env.SDD_FLOW_KIT_AGENT ?? "").trim();
45
+ var _a;
46
+ const fromEnv = ((_a = process_1.default.env.SDD_FLOW_KIT_AGENT) !== null && _a !== void 0 ? _a : "").trim();
46
47
  const allowed = ["cursor", "claude-code", "codex", "openclaw"];
47
48
  if (allowed.includes(fromEnv))
48
49
  return fromEnv;
49
- const fs = await Promise.resolve().then(() => __importStar(require("node:fs/promises")));
50
+ const fs = await Promise.resolve().then(() => __importStar(require("fs/promises")));
50
51
  const exists = async (rel) => {
51
52
  try {
52
- await fs.access(node_path_1.default.join(projectRoot, rel));
53
+ await fs.access(path_1.default.join(projectRoot, rel));
53
54
  return true;
54
55
  }
55
56
  catch {
@@ -67,12 +68,12 @@ async function inferAgent(projectRoot) {
67
68
  return "cursor";
68
69
  }
69
70
  async function main() {
70
- if (node_process_1.default.env.SDD_FLOW_KIT_SKIP_POSTINSTALL === "1") {
71
+ if (process_1.default.env.SDD_FLOW_KIT_SKIP_POSTINSTALL === "1") {
71
72
  return;
72
73
  }
73
- const initCwd = node_process_1.default.env.INIT_CWD ? node_path_1.default.resolve(node_process_1.default.env.INIT_CWD) : null;
74
- const pkgDir = node_path_1.default.resolve(__dirname, "..");
75
- const projectRoot = initCwd ?? node_process_1.default.cwd();
74
+ const initCwd = process_1.default.env.INIT_CWD ? path_1.default.resolve(process_1.default.env.INIT_CWD) : null;
75
+ const pkgDir = path_1.default.resolve(__dirname, "..");
76
+ const projectRoot = initCwd !== null && initCwd !== void 0 ? initCwd : process_1.default.cwd();
76
77
  if (projectRoot === pkgDir)
77
78
  return;
78
79
  const projectKind = await (0, inferProject_1.inferProjectKind)(projectRoot);
@@ -4,12 +4,14 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.runDoctor = runDoctor;
7
- const node_child_process_1 = require("node:child_process");
8
- const promises_1 = __importDefault(require("node:fs/promises"));
9
- const node_path_1 = __importDefault(require("node:path"));
7
+ const child_process_1 = require("child_process");
8
+ const promises_1 = __importDefault(require("fs/promises"));
9
+ const path_1 = __importDefault(require("path"));
10
+ const process_1 = __importDefault(require("process"));
11
+ const nodeCompat_1 = require("../core/nodeCompat");
10
12
  const opsxDelivery_1 = require("../core/opsxDelivery");
11
13
  function hasCommand(cmd) {
12
- const out = (0, node_child_process_1.spawnSync)("which", [cmd], { stdio: "ignore" });
14
+ const out = (0, child_process_1.spawnSync)("which", [cmd], { stdio: "ignore" });
13
15
  return out.status === 0;
14
16
  }
15
17
  async function hasPath(filePath) {
@@ -34,6 +36,11 @@ function agentBin(agent) {
34
36
  }
35
37
  async function runDoctor(options) {
36
38
  const checks = [];
39
+ checks.push({
40
+ name: "node-version",
41
+ ok: (0, nodeCompat_1.isNodeVersionSupported)(),
42
+ detail: `Node ${process_1.default.version}(要求 ${(0, nodeCompat_1.nodeVersionRequirementLabel)()},推荐 18/20 LTS)`,
43
+ });
37
44
  checks.push({
38
45
  name: "git",
39
46
  ok: hasCommand("git"),
@@ -54,7 +61,7 @@ async function runDoctor(options) {
54
61
  }
55
62
  checks.push({
56
63
  name: "openspec-dir",
57
- ok: await hasPath(node_path_1.default.join(options.projectRoot, "openspec")),
64
+ ok: await hasPath(path_1.default.join(options.projectRoot, "openspec")),
58
65
  detail: "openspec 目录存在性",
59
66
  });
60
67
  const toolDirMap = {
@@ -68,7 +75,7 @@ async function runDoctor(options) {
68
75
  if (expectedDir) {
69
76
  checks.push({
70
77
  name: `${options.agent}-dir`,
71
- ok: await hasPath(node_path_1.default.join(options.projectRoot, expectedDir)),
78
+ ok: await hasPath(path_1.default.join(options.projectRoot, expectedDir)),
72
79
  detail: `${expectedDir} 目录存在性`,
73
80
  });
74
81
  }
@@ -17,6 +17,7 @@ function parseInvokeText(text) {
17
17
  return { product, version };
18
18
  }
19
19
  function toRunOptions(args) {
20
+ var _a, _b;
20
21
  return {
21
22
  product: args.parsed.product,
22
23
  version: args.parsed.version,
@@ -24,6 +25,6 @@ function toRunOptions(args) {
24
25
  branch: args.branch,
25
26
  yes: args.yes,
26
27
  dryRun: args.dryRun,
27
- agent: args.agent ?? args.parsed.agent ?? "claude-code",
28
+ agent: (_b = (_a = args.agent) !== null && _a !== void 0 ? _a : args.parsed.agent) !== null && _b !== void 0 ? _b : "claude-code",
28
29
  };
29
30
  }
@@ -4,7 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.runFlow = runFlow;
7
- const node_path_1 = __importDefault(require("node:path"));
7
+ const path_1 = __importDefault(require("path"));
8
8
  const adapters_1 = require("../adapters");
9
9
  const fs_1 = require("../core/fs");
10
10
  const startFlow_1 = require("./startFlow");
@@ -41,7 +41,7 @@ async function runFlow(options) {
41
41
  const scaffold = options.dryRun
42
42
  ? {
43
43
  runId: "dry-run-no-files-generated",
44
- outputRoot: node_path_1.default.join(options.projectRoot, "openspec", "PRD"),
44
+ outputRoot: path_1.default.join(options.projectRoot, "openspec", "PRD"),
45
45
  }
46
46
  : await (0, startFlow_1.startFlowScaffold)(options);
47
47
  const adapter = (0, adapters_1.getAdapter)(setup.agent);
@@ -63,7 +63,7 @@ async function runFlow(options) {
63
63
  version: options.version,
64
64
  });
65
65
  if (!options.dryRun) {
66
- await (0, fs_1.writeTextFileEnsuredDir)(node_path_1.default.join(scaffold.outputRoot, "AUTO-RUN-CHECKLIST.md"), checklist);
66
+ await (0, fs_1.writeTextFileEnsuredDir)(path_1.default.join(scaffold.outputRoot, "AUTO-RUN-CHECKLIST.md"), checklist);
67
67
  }
68
68
  return {
69
69
  ...scaffold,
@@ -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,8 +4,8 @@ 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");
@@ -13,15 +13,15 @@ const inferProject_1 = require("../core/inferProject");
13
13
  function renderTemplate(template, vars) {
14
14
  let out = template;
15
15
  for (const [k, v] of Object.entries(vars)) {
16
- out = out.replaceAll(`{{${k}}}`, v);
16
+ out = (0, utils_1.replaceAllStr)(out, `{{${k}}}`, v);
17
17
  }
18
18
  return out;
19
19
  }
20
20
  async function startFlowScaffold(options) {
21
21
  const now = new Date();
22
22
  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");
23
+ const outputRoot = path_1.default.join(options.projectRoot, "openspec", "PRD", runId);
24
+ const sourceDir = path_1.default.join(outputRoot, "source");
25
25
  await promises_1.default.mkdir(sourceDir, { recursive: true });
26
26
  // 1) Step1: doc-skill 指引(按产品解析脚本路径,支持 monorepo frontend)
27
27
  const kind = (0, inferProject_1.productToKind)(options.product);
@@ -33,23 +33,23 @@ async function startFlowScaffold(options) {
33
33
  DOC_SKILL_REL: docSkill.relFromRoot.replace(/\\/g, "/"),
34
34
  VERSION_EXAMPLE: options.version,
35
35
  });
36
- await (0, fs_1.writeTextFileEnsuredDir)(node_path_1.default.join(outputRoot, "01-获取需求文档指引.md"), step1);
36
+ await (0, fs_1.writeTextFileEnsuredDir)(path_1.default.join(outputRoot, "01-获取需求文档指引.md"), step1);
37
37
  // source 占位:让第 2 步提示词在任何工具里都能直接读
38
- await (0, fs_1.writeTextFileEnsuredDir)(node_path_1.default.join(sourceDir, "PRD.md"), [
38
+ await (0, fs_1.writeTextFileEnsuredDir)(path_1.default.join(sourceDir, "PRD.md"), [
39
39
  "# source/PRD.md",
40
40
  "",
41
41
  "请把《PRD》markdown 内容粘贴到这里(由 `adi-doc-skill` 下载并回填)。",
42
42
  "",
43
43
  "- 建议:保持原有标题层级与编号。",
44
44
  ].join("\n"));
45
- await (0, fs_1.writeTextFileEnsuredDir)(node_path_1.default.join(sourceDir, "接口文档.md"), [
45
+ await (0, fs_1.writeTextFileEnsuredDir)(path_1.default.join(sourceDir, "接口文档.md"), [
46
46
  "# source/接口文档.md",
47
47
  "",
48
48
  "(可选)如果你已经有接口文档,请粘贴到这里;没有也不影响后续 SDD 循环。",
49
49
  "",
50
50
  "- 注意:接口文档中的字段/示例越完整,分析报告越贴近落地。",
51
51
  ].join("\n"));
52
- await (0, fs_1.writeTextFileEnsuredDir)(node_path_1.default.join(sourceDir, "openspec-knowledge-base-summary.md"), [
52
+ await (0, fs_1.writeTextFileEnsuredDir)(path_1.default.join(sourceDir, "openspec-knowledge-base-summary.md"), [
53
53
  "# source/openspec-knowledge-base-summary.md",
54
54
  "",
55
55
  "(可选)现有知识库摘要/快速索引,建议由你在执行第 2 步前手动整理。",
@@ -60,32 +60,35 @@ async function startFlowScaffold(options) {
60
60
  const sddLoopPrompt = await (0, templates_1.loadTemplateText)("prompts/02-sdd-loop-prompt.md");
61
61
  const step2PromptTemplate = await (0, templates_1.loadTemplateText)("artifacts/02-需求分析提示词.template.md");
62
62
  const step2Prompt = renderTemplate(step2PromptTemplate, { SDD_LOOP_PROMPT: sddLoopPrompt });
63
- await (0, fs_1.writeTextFileEnsuredDir)(node_path_1.default.join(outputRoot, "02-需求分析提示词.md"), step2Prompt);
63
+ await (0, fs_1.writeTextFileEnsuredDir)(path_1.default.join(outputRoot, "02-需求分析提示词.md"), step2Prompt);
64
64
  // 3) Step2 输出文件占位(由 AI 工具生成并覆盖)
65
65
  const file01Template = await (0, templates_1.loadTemplateText)("artifacts/01-需求分析报告.template.md");
66
66
  const file02Template = await (0, templates_1.loadTemplateText)("artifacts/02-改动点清单.template.md");
67
67
  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);
68
+ await (0, fs_1.writeTextFileEnsuredDir)(path_1.default.join(outputRoot, "01-需求分析报告.md"), file01Template);
69
+ await (0, fs_1.writeTextFileEnsuredDir)(path_1.default.join(outputRoot, "02-改动点清单.md"), file02Template);
70
+ await (0, fs_1.writeTextFileEnsuredDir)(path_1.default.join(outputRoot, "03-待确认问题清单.md"), file03Template);
71
71
  // 4) Step4 技术文档入口(草稿)
72
72
  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);
73
+ await (0, fs_1.writeTextFileEnsuredDir)(path_1.default.join(outputRoot, "04-技术文档草稿.md"), techDocTemplate);
74
74
  // 5) Step5 openspec 提案入口(草稿)
75
75
  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);
77
- await (0, fs_1.writeTextFileEnsuredDir)(node_path_1.default.join(outputRoot, ".session-state.json"), JSON.stringify({
76
+ await (0, fs_1.writeTextFileEnsuredDir)(path_1.default.join(outputRoot, "05-openspec-提案草稿.md"), openspecEntryTemplate);
77
+ const opsxAutoChainTemplate = await (0, templates_1.loadTemplateText)("artifacts/06-opsx-auto-chain.template.md");
78
+ await (0, fs_1.writeTextFileEnsuredDir)(path_1.default.join(outputRoot, "06-opsx-自动串联指引.md"), opsxAutoChainTemplate);
79
+ await (0, fs_1.writeTextFileEnsuredDir)(path_1.default.join(outputRoot, ".session-state.json"), JSON.stringify({
78
80
  product: options.product,
79
81
  version: options.version,
80
82
  runId,
81
83
  outputRoot,
82
84
  status: "awaiting_user_confirmation",
83
85
  docModeOnly: true,
86
+ autoChainOpsx: true,
84
87
  phase: "A_or_B",
85
88
  createdAtISO: new Date().toISOString(),
86
89
  }, null, 2));
87
90
  // runDir 自述
88
- await (0, fs_1.writeTextFileEnsuredDir)(node_path_1.default.join(outputRoot, "RUNME.md"), [
91
+ await (0, fs_1.writeTextFileEnsuredDir)(path_1.default.join(outputRoot, "RUNME.md"), [
89
92
  "# RUNME(SDD 流程执行说明)",
90
93
  "",
91
94
  `本次 runId: \`${runId}\``,
@@ -110,12 +113,13 @@ async function startFlowScaffold(options) {
110
113
  "- `02-改动点清单.md`",
111
114
  "- `04-技术文档草稿.md`",
112
115
  "",
113
- "## Step 5:/opsx-propose 生成提案",
114
- "基于 `01`、`02` 与用户提供的 UI 参考,执行 `/opsx-propose`。",
115
- "产出在 `openspec/changes/<change-name>/`;在 `05-openspec-提案草稿.md` 记录 change 名称。",
116
- "门禁:仅当 03 已闭环且 01/02/04 完成后执行。",
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` 时暂停。",
117
121
  "",
118
- "## Step 6Apply + TDD + Playwright(阶段 D)",
122
+ "## Step 6 明细:Apply + TDD + Playwright(阶段 D)",
119
123
  "依次执行 skill:`openspec-apply-change` → `tdd-script` → `openspec-superpowers-pipeline` → `opsx-dev-delivery-pipeline`。",
120
124
  "仓库根执行:`pnpm run opsx:delivery-pipeline -- <change-name>`(须 exit 0)。",
121
125
  "禁止跳过 TDD / Playwright 裸写代码收尾。详见 `.cursor/skills/sdd-flow-kit/SKILL.md` 阶段 D。",
package/package.json CHANGED
@@ -1,9 +1,12 @@
1
1
  {
2
2
  "name": "sdd-flow-kit",
3
- "version": "1.0.22",
3
+ "version": "1.0.27",
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": [
@@ -6,7 +6,8 @@
6
6
  ## 前置条件(必须全部满足)
7
7
  - [ ] `03-待确认问题清单.md` 已标注 **矛盾点池状态:已闭合**
8
8
  - [ ] `01-需求分析报告.md`、`02-改动点清单.md`、`04-技术文档草稿.md` 已生成
9
- - [ ] 用户已确认可进入提案阶段
9
+ - [ ] 用户已确认可进入提案阶段(阶段 B 闭环)
10
+ - [ ] `autoChainOpsx` 为 true(默认)→ 与用户确认后**同一回合**自动 propose,无需再等「继续」
10
11
 
11
12
  ## /opsx-propose 输入清单
12
13
  - 需求分析:`01-需求分析报告.md`
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: sdd-flow-kit-trigger
3
- description: 开发某版本需求、SDD 流程、待确认问题澄清、用户回复确认点/矛盾点结论时触发。澄清与确认阶段仅改 openspec/PRD 文档,禁止写 src 等业务代码;仅 /opsx-apply 后可实现。
3
+ description: 开发某版本需求、SDD 流程、待确认问题澄清、用户回复确认点/矛盾点结论时触发。阶段 A/B 仅文档;阶段 B 闭环后**自动** /opsx-propose → /opsx-apply(除非用户说只提案或 autoChainOpsx=false)。
4
4
  ---
5
5
 
6
6
  # SDD Flow Trigger Skill
@@ -20,16 +20,18 @@ description: 开发某版本需求、SDD 流程、待确认问题澄清、用户
20
20
  | 模式 | 何时 | 允许改动的路径 | 禁止 |
21
21
  |------|------|----------------|------|
22
22
  | **文档模式** | 阶段 A/B/C,含「用户发来确认点」 | 仅 `openspec/PRD/**`(及阶段 C 的 `openspec/changes/**`) | 任何 `src/`、`pages/`、`components/` 等业务代码 |
23
- | **实现模式** | 仅阶段 D,且用户同意 `/opsx-apply` | 按 openspec change 与 pipeline 要求 | 跳过 propose |
23
+ | **实现模式** | 阶段 D(提案 apply-ready 后**自动进入**,无需用户再说「开始 apply」) | 按 openspec change 与 pipeline 要求 | 跳过 propose / 跳过 pipeline |
24
24
 
25
25
  **常见错误**:用户粘贴确认点后,你一边更新 `03` 一边改 `.tsx` —— **绝对禁止**。确认回复 = 只做文档,同一回合不得 touch 业务代码。
26
26
 
27
27
  ### 用户侧:零口令(不要要求加前缀)
28
28
  - 用户**直接粘贴**确认点、编号结论、「按 1/2/3 继续」即可;**禁止**要求用户输入【仅文档模式】等固定话术。
29
29
  - 你须在收到后**自动**判定为阶段 B 的文档模式;先读 `openspec/PRD/<runId>/.session-state.json`(`docModeOnly: true` 时严禁写业务代码)与 `03-待确认问题清单.md`。
30
- - 阶段切换口语即可,无需背提示词:
31
- - 提案:`继续` / `做提案` / `propose`(执行 `/opsx-propose`)
32
- - 实现:`开始 apply` / `执行 apply`(且 change 已 apply-ready)
30
+ - **默认自动串联**(`openspec/PRD/<runId>/.session-state.json` 中 `autoChainOpsx !== false`):
31
+ - 阶段 B 产出 01/02/闭环03/04 后,**同一回合**继续阶段 C→D,**不要**停下来问「是否继续」。
32
+ - `06-opsx-自动串联指引.md` 与 `.cursor/skills/opsx-superpowers-workflow/SKILL.md`。
33
+ - **仅当用户说**「只提案」「先别写代码」时:阶段 C 结束后停止,等待 `/opsx-apply`。
34
+ - 口语仍可用:`继续` / `做提案` / `开始 apply`(用于断点续跑)。
33
35
 
34
36
  ## 触发后执行规则(分阶段,禁止跳步)
35
37
 
@@ -64,7 +66,9 @@ description: 开发某版本需求、SDD 流程、待确认问题澄清、用户
64
66
  2. 将状态改为 **矛盾点池状态:已闭合**。
65
67
  3. 再生成 `01`、`02`、`04`(路径均在 `openspec/PRD/<runId>/`)。
66
68
  4. **本回合禁止**:`StrReplace`/`Write` 任何业务源码;禁止「顺手实现一版」。
67
- 5. 结束语简短即可(例:「已更新 01/02/03/04,未改业务代码。有 UI 参考请发路径,或回复继续做提案。」)——**不要**要求用户背固定口令。
69
+ 5. 更新 `.session-state.json`:`status: ready_for_propose`,`phase: B_done`。
70
+ 6. **若 `autoChainOpsx !== false` 且用户未说「只提案」**:**不要结束回合**——立即打开 `06-opsx-自动串联指引.md` 执行阶段 C→D(见下文)。
71
+ 7. 若 `autoChainOpsx === false`:简短说明 01/02/03/04 已就绪,等待用户说「继续」。
68
72
 
69
73
  **本阶段只允许产出:**
70
74
  1. 更新 `03-待确认问题清单.md`(标注 **矛盾点池状态:已闭合**,写入用户确认结论)
@@ -74,34 +78,34 @@ description: 开发某版本需求、SDD 流程、待确认问题澄清、用户
74
78
 
75
79
  **本阶段禁止:**
76
80
  - 修改 `src/`、`pages/`、`components/`、`api/` 等实现代码(`02-改动点清单` 只是**计划**,不是实施许可)
77
- - 执行 `/opsx-propose` 或 `/opsx-apply`
78
- - 生成 openspec change 目录或宣称“开发完成”
79
-
80
- 完成后告知用户:“01/02/03/04 已就绪。发 UI 参考路径,或回复「继续」我将执行 /opsx-propose。”
81
+ - **未闭合 03** 时执行 `/opsx-propose` 或 `/opsx-apply`
82
+ - `autoChainOpsx === false` 时未经用户同意就进入阶段 C/D
81
83
 
82
84
  ---
83
85
 
84
- ### 阶段 C — `/opsx-propose`(生成提案,对应你说的「05」)
85
- **触发条件**:阶段 B 已完成,且用户确认可进入提案(可提供 UI 参考路径,如原型页面文件)。
86
+ ### 阶段 C — `/opsx-propose`(生成提案,对应 Step5)
87
+ **触发条件**:
88
+ - 阶段 B 已完成(03 已闭合,01/02/04 已生成),**且**
89
+ - `autoChainOpsx !== false`(默认)→ **与用户确认同一回合内自动执行**;或用户说「继续/做提案」;或 `status` 为 `ready_for_propose` 且用户未说「只提案」。
86
90
 
87
91
  **必须执行:**
88
92
  1. 先读当前仓库 `.opsx/config.json` 与本地 OPSX skills(禁止跨项目 fallback)
89
- 2. 执行 **`/opsx-propose`**,输入至少包含:
90
- - `01-需求分析报告.md`
91
- - `02-改动点清单.md`
92
- - **UI 图/交互参考**(用户指定路径;无则说明缺失并继续文字 spec)
93
+ 2. 执行 **`/opsx-propose`**(遵循 `.cursor/commands/opsx-propose.md` 与 `openspec-propose` skill),输入至少包含:
94
+ - `openspec/PRD/<runId>/01-需求分析报告.md`
95
+ - `openspec/PRD/<runId>/02-改动点清单.md`
96
+ - **UI 图/交互参考**(按 `06-opsx-自动串联指引.md` 解析;无则注明缺失并继续)
93
97
  3. 一次性生成 openspec change(`proposal.md`、`design.md`、`specs/`、`tasks.md` 等)
98
+ 4. 更新 `05-openspec-提案草稿.md`;`.session-state.json` → `status: propose_done`
94
99
 
95
- **本阶段交付物 = openspec 提案产物**(即手动流程里的 Step5 结果)。
96
- 可在 `openspec/PRD/<runId>/05-openspec-提案草稿.md` 记录 change 名称与路径索引。
100
+ **本阶段交付物 = openspec 提案产物**。
101
+ **本阶段禁止**:写 `src/`、跑 delivery-pipeline。
97
102
 
98
- **本阶段禁止:**
99
- - 执行 `/opsx-apply`(留给阶段 D)
103
+ **默认**:提案 apply-ready 后 **立即** 进入阶段 D(勿问用户「是否 apply」),除非用户说了「只提案」。
100
104
 
101
105
  ---
102
106
 
103
107
  ### 阶段 D — Apply + TDD + Playwright 交付(禁止裸写代码收尾)
104
- **触发条件**:阶段 C 的 openspec 提案已 apply-ready,且用户同意实现(如「开始 apply」)。
108
+ **触发条件**:阶段 C 的 openspec 提案已 apply-ready(`autoChainOpsx` 默认下与阶段 C **同一连续执行**)。
105
109
 
106
110
  **严禁**:跳过 skill 链、不按 `tasks.md` TDD、不跑 delivery-pipeline 就改 `src/` 并宣称完成。
107
111