sdd-flow-kit 1.1.1 → 1.2.1

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 CHANGED
@@ -71,6 +71,22 @@ npx sdd-flow-kit doctor --agent cursor
71
71
  > 完整环境(分支切换 + opsx-workflow)请另执行:`npx sdd-flow-kit install -y`
72
72
  > 跳过 postinstall:`SDD_FLOW_KIT_SKIP_POSTINSTALL=1 pnpm add -D sdd-flow-kit`
73
73
 
74
+ ##### `pnpm add` 后依赖全被删掉(`Packages: +1 -932`)
75
+
76
+ **原因**:`pnpm` 会按当前 `package.json` **同步** `node_modules`。若 manifest 里只剩 `sdd-flow-kit`(或文件被误保存成几行空壳),就会卸载其它所有包——**不是** `pnpm add` 单独删包,而是 manifest 已不完整。
77
+
78
+ **恢复**(在业务仓如 `adInsight-web/frontend`):
79
+
80
+ 1. 用 Git 恢复完整 `package.json`:`git checkout -- package.json`
81
+ 2. 重装:`pnpm install`
82
+ 3. 再装 kit(建议 **≥ 1.1.2**,且必须在有完整 `package.json` 的目录执行):
83
+ - `cd frontend && pnpm add -D sdd-flow-kit@1.1.2`
84
+ - 或本地联调:`pnpm add -D file:../../sdd-flow-kit`
85
+
86
+ **v1.1.2+ 防护**:`repairOpsxDeliveryScripts` 仅合并 `scripts`,写入前检查依赖条数与文件体积;`postinstall` 不再改 `package.json`,且必须有 `INIT_CWD` 才运行。
87
+
88
+ **pnpm v10 构建脚本**:若出现 `Ignored build scripts: sdd-flow-kit`,执行 `pnpm approve-builds` 选中 `sdd-flow-kit`,或安装后手动:`node ./node_modules/sdd-flow-kit/dist/postinstall.js`
89
+
74
90
  #### 使用说明
75
91
 
76
92
  ##### 安装根:cd 到哪,装到哪(v1.2+)
package/dist/cli.js CHANGED
@@ -16,6 +16,7 @@ const runGate_1 = require("./steps/runGate");
16
16
  const runPhaseAdvance_1 = require("./steps/runPhaseAdvance");
17
17
  const runPropose_1 = require("./steps/runPropose");
18
18
  const runDeliver_1 = require("./steps/runDeliver");
19
+ const bootstrap_1 = require("./core/bootstrap");
19
20
  const projectRoots_1 = require("./core/projectRoots");
20
21
  const ALLOWED_AGENTS = ["cursor", "claude-code", "codex", "openclaw", "custom"];
21
22
  function getArgValue(flag) {
@@ -35,9 +36,11 @@ function printHelp() {
35
36
  "1) 生成流程产物骨架:",
36
37
  " npx sdd-flow-kit start --product ADI --version 2.3.2",
37
38
  "",
38
- "2) 安装流程到当前项目(推荐,安装即用):",
39
- " npx sdd-flow-kit install",
40
- " (等价于: npx sdd-flow-kit setup)",
39
+ "2) 安装到当前目录(全量:SDD + OPSX + Superpowers,推荐):",
40
+ " cd <你的项目目录> # 如 adInsight-web/frontend",
41
+ " pnpm add -D sdd-flow-kit",
42
+ " npx sdd-flow-kit install --project ADI -y",
43
+ " 本地 OPSX: SDD_OPSX_WORKFLOW_PACKAGE=file:../../opsx-workflow npx sdd-flow-kit install -y",
41
44
  "",
42
45
  "3) 生成骨架并进入可执行模式(推荐):",
43
46
  " npx sdd-flow-kit run --product ADI --version 2.3.2 --agent claude-code",
@@ -141,6 +144,26 @@ async function main() {
141
144
  process_1.default.exitCode = 1;
142
145
  return;
143
146
  }
147
+ const skipBootstrap = process_1.default.env.SDD_FLOW_KIT_SKIP_BOOTSTRAP === "1";
148
+ const bootstrapCommands = new Set([
149
+ "doctor",
150
+ "ensure-opsx",
151
+ "run",
152
+ "invoke",
153
+ "install",
154
+ "setup",
155
+ "gate",
156
+ "phase",
157
+ "propose",
158
+ "deliver",
159
+ ]);
160
+ if (!skipBootstrap && !dryRun && bootstrapCommands.has(cmd)) {
161
+ await (0, bootstrap_1.bootstrapProjectIfNeeded)({
162
+ projectRoot,
163
+ agent,
164
+ projectKind: projectKind,
165
+ });
166
+ }
144
167
  if (cmd === "gate") {
145
168
  const expect = getArgValue("--expect");
146
169
  if (!expect || !GATE_EXPECTS.includes(expect)) {
@@ -245,15 +268,6 @@ async function main() {
245
268
  // eslint-disable-next-line no-console
246
269
  console.log(JSON.stringify({ command: "doctor", projectRoot: path_1.default.resolve(projectRoot), result: doctor }, null, 2));
247
270
  }
248
- if (cmd === "ensure-opsx" && !dryRun) {
249
- await (0, setupProject_1.setupProject)({
250
- projectRoot,
251
- agent,
252
- yes: true,
253
- light: true,
254
- projectKind: projectKind,
255
- });
256
- }
257
271
  if (!doctor.ok)
258
272
  process_1.default.exitCode = 1;
259
273
  return;
@@ -297,7 +311,7 @@ async function main() {
297
311
  const text = (fromFlag !== null && fromFlag !== void 0 ? fromFlag : raw).trim();
298
312
  const parsed = (0, invokeFlow_1.parseInvokeText)(text);
299
313
  if (!parsed) {
300
- throw new Error("invoke 解析失败,请使用:开发 ADI 2.3.2 需求");
314
+ throw new Error("invoke 解析失败,示例:开发 ADI 2.3.2 需求 | 进行ADI_2.3.2版本开发");
301
315
  }
302
316
  const options = (0, invokeFlow_1.toRunOptions)({
303
317
  parsed,
@@ -349,7 +363,7 @@ async function main() {
349
363
  // eslint-disable-next-line no-console
350
364
  console.log(`流程已初始化:openspec/PRD/${result.runId}/`);
351
365
  // eslint-disable-next-line no-console
352
- console.log(`请打开 openspec/PRD/${result.runId}/NEXT.md,按脚本顺序执行 gate / phase / propose / deliver。`);
366
+ console.log(`请打开 openspec/PRD/${result.runId}/NEXT.md,严格按 gatephasepropose→deliver 执行;确认点前禁止改 src/。`);
353
367
  // eslint-disable-next-line no-console
354
368
  console.log(`agent=${result.adapterName}, cli=${result.adapterAvailable ? "detected" : "missing"}, autoExecute=${result.executed ? "yes" : "manual"}`);
355
369
  // eslint-disable-next-line no-console
@@ -0,0 +1,58 @@
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.bootstrapProjectIfNeeded = bootstrapProjectIfNeeded;
7
+ const path_1 = __importDefault(require("path"));
8
+ const inferProject_1 = require("./inferProject");
9
+ const packageJsonPatch_1 = require("./packageJsonPatch");
10
+ const opsxDelivery_1 = require("./opsxDelivery");
11
+ const projectRoots_1 = require("./projectRoots");
12
+ const setupProject_1 = require("../steps/setupProject");
13
+ /**
14
+ * pnpm v10 常拦截 postinstall;首次跑 CLI 时补全 SDD + OPSX 全栈(不切分支)。
15
+ */
16
+ async function bootstrapProjectIfNeeded(options) {
17
+ var _a;
18
+ const installRoot = (0, projectRoots_1.resolveInstallRoot)(options.projectRoot);
19
+ if (await (0, opsxDelivery_1.isOpsxApplyReady)(installRoot)) {
20
+ return false;
21
+ }
22
+ if (options.dryRun) {
23
+ return true;
24
+ }
25
+ const kind = (_a = options.projectKind) !== null && _a !== void 0 ? _a : (await (0, inferProject_1.inferProjectKind)(installRoot));
26
+ // eslint-disable-next-line no-console
27
+ console.log("[sdd-flow-kit] 检测到 OPSX 未就绪,正在自动 bootstrap(等价 install -y)…");
28
+ const pkgRoot = await (0, projectRoots_1.resolvePackageRoot)(installRoot);
29
+ const pnpmPatched = await (0, packageJsonPatch_1.ensurePnpmAllowSddFlowKitBuild)(path_1.default.join(pkgRoot, "package.json"));
30
+ if (pnpmPatched) {
31
+ // eslint-disable-next-line no-console
32
+ console.log("[sdd-flow-kit] 已写入 pnpm.onlyBuiltDependencies: sdd-flow-kit;下次 pnpm install 将执行 postinstall");
33
+ }
34
+ await (0, setupProject_1.setupProject)({
35
+ projectRoot: installRoot,
36
+ agent: options.agent,
37
+ yes: true,
38
+ light: false,
39
+ skipGit: true,
40
+ projectKind: kind !== null && kind !== void 0 ? kind : "ADI",
41
+ });
42
+ if (!(await (0, opsxDelivery_1.isOpsxApplyReady)(installRoot))) {
43
+ await (0, opsxDelivery_1.ensureOpsxWorkflow)({
44
+ projectRoot: installRoot,
45
+ agent: options.agent,
46
+ });
47
+ }
48
+ const ready = await (0, opsxDelivery_1.isOpsxApplyReady)(installRoot);
49
+ if (ready) {
50
+ // eslint-disable-next-line no-console
51
+ console.log("[sdd-flow-kit] bootstrap 完成:SDD + OPSX 已就绪");
52
+ }
53
+ else {
54
+ // eslint-disable-next-line no-console
55
+ console.warn("[sdd-flow-kit] bootstrap 后仍有检查未通过,请执行: npx sdd-flow-kit doctor --agent cursor");
56
+ }
57
+ return true;
58
+ }
@@ -3,6 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
3
3
  return (mod && mod.__esModule) ? mod : { "default": mod };
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.resolveOpsxWorkflowPackage = resolveOpsxWorkflowPackage;
6
7
  exports.deliveryPipelineNpmScripts = deliveryPipelineNpmScripts;
7
8
  exports.pathExists = pathExists;
8
9
  exports.resolveOpsxAgent = resolveOpsxAgent;
@@ -14,8 +15,12 @@ exports.collectDeliverySetupChecks = collectDeliverySetupChecks;
14
15
  const child_process_1 = require("child_process");
15
16
  const promises_1 = __importDefault(require("fs/promises"));
16
17
  const path_1 = __importDefault(require("path"));
17
- const fs_1 = require("./fs");
18
+ const packageJsonPatch_1 = require("./packageJsonPatch");
18
19
  const projectRoots_1 = require("./projectRoots");
20
+ const opsxWorkflowPackage_1 = require("./opsxWorkflowPackage");
21
+ function resolveOpsxWorkflowPackage() {
22
+ return (0, opsxWorkflowPackage_1.resolveOpsxWorkflowPackageForCwd)(process.cwd());
23
+ }
19
24
  const DELIVERY_SCRIPT_REL = ".cursor/skills/opsx-dev-delivery-pipeline/scripts/opsx-delivery-pipeline.sh";
20
25
  function deliveryPipelineNpmScripts() {
21
26
  const full = `bash ${DELIVERY_SCRIPT_REL}`;
@@ -45,7 +50,7 @@ async function resolveOpsxAgent(projectRoot, preferred) {
45
50
  return "claude-code";
46
51
  return preferred;
47
52
  }
48
- /** 将 package.json 中过期的 poquan-delivery-pipeline 脚本修正为 opsx-dev-delivery-pipeline */
53
+ /** 将 package.json 中过期的 poquan 脚本修正为 opsx-dev-delivery-pipeline(仅合并 scripts) */
49
54
  async function repairOpsxDeliveryScripts(projectRoot) {
50
55
  const pkgPath = path_1.default.join(projectRoot, "package.json");
51
56
  if (!(await pathExists(pkgPath)))
@@ -55,29 +60,35 @@ async function repairOpsxDeliveryScripts(projectRoot) {
55
60
  return false;
56
61
  const { full, short } = deliveryPipelineNpmScripts();
57
62
  const text = await promises_1.default.readFile(pkgPath, "utf8");
58
- const pkg = JSON.parse(text);
63
+ let pkg;
64
+ try {
65
+ pkg = JSON.parse(text);
66
+ }
67
+ catch {
68
+ return false;
69
+ }
59
70
  if (!pkg.scripts)
60
71
  pkg.scripts = {};
61
- let changed = false;
62
72
  const needsFix = (v) => !v || v.includes("poquan-dev-delivery-pipeline") || v.includes("poquan-delivery-pipeline");
63
- if (needsFix(pkg.scripts["opsx:delivery-pipeline"])) {
64
- pkg.scripts["opsx:delivery-pipeline"] = full;
65
- changed = true;
66
- }
73
+ const updates = {};
74
+ if (needsFix(pkg.scripts["opsx:delivery-pipeline"]))
75
+ updates["opsx:delivery-pipeline"] = full;
67
76
  if (needsFix(pkg.scripts["opsx:delivery-pipeline:short"])) {
68
- pkg.scripts["opsx:delivery-pipeline:short"] = short;
69
- changed = true;
77
+ updates["opsx:delivery-pipeline:short"] = short;
70
78
  }
71
- if (!changed)
79
+ if (Object.keys(updates).length === 0)
72
80
  return false;
73
- await (0, fs_1.writeTextFileEnsuredDir)(pkgPath, `${JSON.stringify(pkg, null, 2)}\n`);
74
- return true;
81
+ const result = await (0, packageJsonPatch_1.patchPackageJsonScripts)(pkgPath, updates, {
82
+ minDependencyCount: 2,
83
+ minBytes: 120,
84
+ });
85
+ return result.ok && result.changed;
75
86
  }
76
87
  function hasCommand(cmd) {
77
88
  return (0, child_process_1.spawnSync)("which", [cmd], { stdio: "ignore" }).status === 0;
78
89
  }
79
90
  function buildOpsxWorkflowSetupCommand(projectRoot, agent) {
80
- const baseArgs = ["@lixin5257xxx/opsx-workflow", "setup"];
91
+ const baseArgs = [resolveOpsxWorkflowPackage(), "setup"];
81
92
  if (agent === "cursor") {
82
93
  return {
83
94
  cmd: "npx",
@@ -99,6 +110,7 @@ async function isOpsxApplyReady(projectRoot) {
99
110
  }
100
111
  /** 缺 openspec/TDD/Playwright 工作流时安装 @lixin5257xxx/opsx-workflow,并修复 delivery 脚本 */
101
112
  async function ensureOpsxWorkflow(options) {
113
+ var _a;
102
114
  const installRoot = await (0, projectRoots_1.resolvePackageRoot)((0, projectRoots_1.resolveInstallRoot)(options.projectRoot));
103
115
  const plannedActions = [];
104
116
  let checks = await collectDeliverySetupChecks(installRoot);
@@ -129,7 +141,10 @@ async function ensureOpsxWorkflow(options) {
129
141
  env: process.env,
130
142
  });
131
143
  if (ret.status !== 0) {
132
- throw new Error(`opsx-workflow 安装失败:${cmd} ${args.join(" ")}`);
144
+ const hint = ((_a = process.env.SDD_OPSX_WORKFLOW_PACKAGE) === null || _a === void 0 ? void 0 : _a.trim()) ?
145
+ ""
146
+ : " 本地 OPSX 可设: SDD_OPSX_WORKFLOW_PACKAGE=file:../../opsx-workflow";
147
+ throw new Error(`opsx-workflow 安装失败:${cmd} ${args.join(" ")}${hint}`);
133
148
  }
134
149
  const repairedScripts = await repairOpsxDeliveryScripts(installRoot);
135
150
  if (repairedScripts) {
@@ -159,10 +174,12 @@ async function collectDeliverySetupChecks(installRoot) {
159
174
  ok: await pathExists(path_1.default.join(openspecRoot, "openspec")),
160
175
  detail: openspecRoot === root ? "openspec/ 目录(安装根)" : `openspec/ 目录(上级 ${openspecRoot})`,
161
176
  });
177
+ const openspecOk = hasCommand("openspec") ||
178
+ (0, child_process_1.spawnSync)("npx", ["openspec", "--version"], { stdio: "ignore", cwd: root }).status === 0;
162
179
  checks.push({
163
180
  name: "openspec-cli",
164
- ok: hasCommand("openspec"),
165
- detail: "openspec CLI(npx openspec 或全局安装)",
181
+ ok: openspecOk,
182
+ detail: openspecOk ? "openspec CLI 可用" : "请安装 openspec CLI 或执行 opsx-workflow setup",
166
183
  });
167
184
  const skillPath = path_1.default.join(root, ".cursor", "skills", "opsx-dev-delivery-pipeline", "SKILL.md");
168
185
  checks.push({
@@ -0,0 +1,23 @@
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.resolveOpsxWorkflowPackageForCwd = resolveOpsxWorkflowPackageForCwd;
7
+ const fs_1 = __importDefault(require("fs"));
8
+ const path_1 = __importDefault(require("path"));
9
+ /** 本地联调 SDD_OPSX_WORKFLOW_PACKAGE=file:../../opsx-workflow;不存在则回退 npm 包 */
10
+ function resolveOpsxWorkflowPackageForCwd(cwd) {
11
+ var _a;
12
+ const fromEnv = (_a = process.env.SDD_OPSX_WORKFLOW_PACKAGE) === null || _a === void 0 ? void 0 : _a.trim();
13
+ if (fromEnv === null || fromEnv === void 0 ? void 0 : fromEnv.startsWith("file:")) {
14
+ const localPath = path_1.default.resolve(cwd, fromEnv.slice("file:".length));
15
+ if (fs_1.default.existsSync(localPath)) {
16
+ return fromEnv;
17
+ }
18
+ // eslint-disable-next-line no-console
19
+ console.warn(`[sdd-flow-kit] SDD_OPSX_WORKFLOW_PACKAGE=${fromEnv} 不存在 (${localPath}),改用 @lixin5257xxx/opsx-workflow`);
20
+ return "@lixin5257xxx/opsx-workflow";
21
+ }
22
+ return fromEnv || "@lixin5257xxx/opsx-workflow";
23
+ }
@@ -0,0 +1,89 @@
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.countPackageDependencies = countPackageDependencies;
7
+ exports.patchPackageJsonScripts = patchPackageJsonScripts;
8
+ exports.ensurePnpmAllowSddFlowKitBuild = ensurePnpmAllowSddFlowKitBuild;
9
+ const promises_1 = __importDefault(require("fs/promises"));
10
+ const fs_1 = require("./fs");
11
+ function countPackageDependencies(pkg) {
12
+ const deps = pkg.dependencies;
13
+ const devDeps = pkg.devDependencies;
14
+ return Object.keys(deps !== null && deps !== void 0 ? deps : {}).length + Object.keys(devDeps !== null && devDeps !== void 0 ? devDeps : {}).length;
15
+ }
16
+ async function patchPackageJsonScripts(pkgPath, scriptUpdates, options = {}) {
17
+ var _a, _b, _c, _d;
18
+ const minDependencyCount = (_a = options.minDependencyCount) !== null && _a !== void 0 ? _a : 1;
19
+ const minBytes = (_b = options.minBytes) !== null && _b !== void 0 ? _b : 80;
20
+ const minSizeRatio = (_c = options.minSizeRatio) !== null && _c !== void 0 ? _c : 0.5;
21
+ let text;
22
+ try {
23
+ text = await promises_1.default.readFile(pkgPath, "utf8");
24
+ }
25
+ catch {
26
+ return { ok: false, reason: "package.json 不存在或不可读" };
27
+ }
28
+ if (text.length < minBytes) {
29
+ return { ok: false, reason: `package.json 过小(${text.length} 字节)` };
30
+ }
31
+ let pkg;
32
+ try {
33
+ pkg = JSON.parse(text);
34
+ }
35
+ catch {
36
+ return { ok: false, reason: "package.json 不是合法 JSON" };
37
+ }
38
+ if (countPackageDependencies(pkg) < minDependencyCount) {
39
+ return { ok: false, reason: "dependencies 过少" };
40
+ }
41
+ const beforeKeys = Object.keys(pkg);
42
+ const scripts = (_d = pkg.scripts) !== null && _d !== void 0 ? _d : {};
43
+ let changed = false;
44
+ for (const [key, value] of Object.entries(scriptUpdates)) {
45
+ if (scripts[key] !== value) {
46
+ scripts[key] = value;
47
+ changed = true;
48
+ }
49
+ }
50
+ if (!changed)
51
+ return { ok: true, changed: false };
52
+ pkg.scripts = scripts;
53
+ for (const key of beforeKeys) {
54
+ if (!Object.keys(pkg).includes(key)) {
55
+ return { ok: false, reason: `会丢失字段: ${key}` };
56
+ }
57
+ }
58
+ const out = `${JSON.stringify(pkg, null, 2)}\n`;
59
+ if (out.length < text.length * minSizeRatio) {
60
+ return { ok: false, reason: "写入后体积异常缩小" };
61
+ }
62
+ await (0, fs_1.writeTextFileEnsuredDir)(pkgPath, out);
63
+ return { ok: true, changed: true };
64
+ }
65
+ async function ensurePnpmAllowSddFlowKitBuild(pkgPath) {
66
+ var _a, _b;
67
+ let text;
68
+ try {
69
+ text = await promises_1.default.readFile(pkgPath, "utf8");
70
+ }
71
+ catch {
72
+ return false;
73
+ }
74
+ let pkg;
75
+ try {
76
+ pkg = JSON.parse(text);
77
+ }
78
+ catch {
79
+ return false;
80
+ }
81
+ const pnpm = (_a = pkg.pnpm) !== null && _a !== void 0 ? _a : {};
82
+ const list = (_b = pnpm.onlyBuiltDependencies) !== null && _b !== void 0 ? _b : [];
83
+ if (list.includes("sdd-flow-kit"))
84
+ return false;
85
+ pnpm.onlyBuiltDependencies = [...list, "sdd-flow-kit"];
86
+ pkg.pnpm = pnpm;
87
+ await (0, fs_1.writeTextFileEnsuredDir)(pkgPath, `${JSON.stringify(pkg, null, 2)}\n`);
88
+ return true;
89
+ }
@@ -40,6 +40,7 @@ const path_1 = __importDefault(require("path"));
40
40
  const process_1 = __importDefault(require("process"));
41
41
  const inferProject_1 = require("./core/inferProject");
42
42
  const opsxDelivery_1 = require("./core/opsxDelivery");
43
+ const projectRoots_1 = require("./core/projectRoots");
43
44
  const setupProject_1 = require("./steps/setupProject");
44
45
  async function inferAgent(projectRoot) {
45
46
  var _a;
@@ -68,50 +69,53 @@ async function inferAgent(projectRoot) {
68
69
  return "cursor";
69
70
  }
70
71
  async function main() {
72
+ var _a;
71
73
  if (process_1.default.env.SDD_FLOW_KIT_SKIP_POSTINSTALL === "1") {
72
74
  return;
73
75
  }
74
- const initCwd = process_1.default.env.INIT_CWD ? path_1.default.resolve(process_1.default.env.INIT_CWD) : null;
76
+ const initCwd = (_a = process_1.default.env.INIT_CWD) === null || _a === void 0 ? void 0 : _a.trim();
75
77
  const pkgDir = path_1.default.resolve(__dirname, "..");
76
- const projectRoot = initCwd !== null && initCwd !== void 0 ? initCwd : process_1.default.cwd();
78
+ if (!initCwd) {
79
+ // eslint-disable-next-line no-console
80
+ console.warn("[sdd-flow-kit] postinstall: 缺少 INIT_CWD,已跳过。请执行: npx sdd-flow-kit install --project ADI -y");
81
+ return;
82
+ }
83
+ const projectRoot = (0, projectRoots_1.resolveInstallRoot)(initCwd);
77
84
  if (projectRoot === pkgDir)
78
85
  return;
79
86
  const projectKind = await (0, inferProject_1.inferProjectKind)(projectRoot);
80
87
  const agent = await inferAgent(projectRoot);
88
+ const light = process_1.default.env.SDD_FLOW_KIT_POSTINSTALL_LIGHT === "1";
81
89
  // eslint-disable-next-line no-console
82
- console.log(`[sdd-flow-kit] postinstall (light) at: ${projectRoot}`);
90
+ console.log(`[sdd-flow-kit] postinstall (${light ? "light" : "full"}) at: ${projectRoot}`);
83
91
  // eslint-disable-next-line no-console
84
92
  console.log(`[sdd-flow-kit] agent=${agent}, project=${projectKind}`);
85
- await (0, setupProject_1.setupProject)({
93
+ const result = await (0, setupProject_1.setupProject)({
86
94
  projectRoot,
87
95
  agent,
88
96
  yes: true,
89
- light: true,
97
+ light,
98
+ skipGit: true,
90
99
  dryRun: false,
91
100
  projectKind,
92
101
  });
93
- const repaired = await (0, opsxDelivery_1.repairOpsxDeliveryScripts)(projectRoot);
94
- if (repaired) {
95
- // eslint-disable-next-line no-console
96
- console.log("[sdd-flow-kit] repaired package.json opsx:delivery-pipeline scripts");
97
- }
98
102
  const ready = await (0, opsxDelivery_1.isOpsxApplyReady)(projectRoot);
99
103
  if (!ready) {
100
104
  // eslint-disable-next-line no-console
101
- console.warn("[sdd-flow-kit] OPSX/TDD/Playwright 工作流未就绪。/opsx-apply 前请执行: npx sdd-flow-kit ensure-opsx -y");
105
+ console.warn("[sdd-flow-kit] OPSX 未完全就绪。请执行: npx sdd-flow-kit install --project " +
106
+ `${projectKind} -y` +
107
+ (process_1.default.env.SDD_OPSX_WORKFLOW_PACKAGE ? "" : "(或设 SDD_OPSX_WORKFLOW_PACKAGE 指向本地 opsx-workflow)"));
102
108
  }
103
109
  else {
104
110
  // eslint-disable-next-line no-console
105
- console.log("[sdd-flow-kit] opsx apply stack ready");
111
+ console.log("[sdd-flow-kit] 安装完成:SDD + OPSX + Superpowers 已就绪");
106
112
  }
107
113
  // eslint-disable-next-line no-console
108
- console.log("[sdd-flow-kit] light setup done (skills/rules). Full install: npx sdd-flow-kit install --project ADI -y");
109
- if (agent === "cursor") {
110
- // eslint-disable-next-line no-console
111
- console.log("[sdd-flow-kit] Cursor skill: .cursor/skills/sdd-flow-kit/SKILL.md");
112
- }
114
+ console.log(`[sdd-flow-kit] 开发需求: npx sdd-flow-kit invoke "开发 ${projectKind} x.x.x 需求" -y`);
115
+ // eslint-disable-next-line no-console
116
+ console.log(`[sdd-flow-kit] environmentReady=${result.environmentReady}, setupExecuted=${result.setupExecuted}`);
113
117
  }
114
118
  main().catch((err) => {
115
119
  // eslint-disable-next-line no-console
116
- console.warn("[sdd-flow-kit] postinstall setup skipped:", err instanceof Error ? err.message : err);
120
+ console.warn("[sdd-flow-kit] postinstall skipped:", err instanceof Error ? err.message : err);
117
121
  });
@@ -9,6 +9,7 @@ const promises_1 = __importDefault(require("fs/promises"));
9
9
  const path_1 = __importDefault(require("path"));
10
10
  const process_1 = __importDefault(require("process"));
11
11
  const nodeCompat_1 = require("../core/nodeCompat");
12
+ const bootstrap_1 = require("../core/bootstrap");
12
13
  const opsxDelivery_1 = require("../core/opsxDelivery");
13
14
  function hasCommand(cmd) {
14
15
  const out = (0, child_process_1.spawnSync)("which", [cmd], { stdio: "ignore" });
@@ -83,6 +84,24 @@ async function runDoctor(options) {
83
84
  checks.push(c);
84
85
  }
85
86
  let ok = checks.every((c) => c.ok);
87
+ const opsxStackFailed = checks.some((c) => !c.ok &&
88
+ [
89
+ "opsx-dev-delivery-pipeline-skill",
90
+ "opsx-config",
91
+ "delivery-pipeline-script",
92
+ "package-json-delivery-script",
93
+ ].includes(c.name));
94
+ if (!ok && opsxStackFailed && !options.fix && !options.dryRun) {
95
+ await (0, bootstrap_1.bootstrapProjectIfNeeded)({
96
+ projectRoot: options.projectRoot,
97
+ agent: options.agent,
98
+ });
99
+ checks.length = 0;
100
+ for (const c of await (0, opsxDelivery_1.collectDeliverySetupChecks)(options.projectRoot)) {
101
+ checks.push(c);
102
+ }
103
+ ok = checks.every((c) => c.ok);
104
+ }
86
105
  if (!ok && options.fix) {
87
106
  const ensured = await (0, opsxDelivery_1.ensureOpsxWorkflow)({
88
107
  projectRoot: options.projectRoot,
@@ -4,17 +4,24 @@ exports.parseInvokeText = parseInvokeText;
4
4
  exports.toRunOptions = toRunOptions;
5
5
  function parseInvokeText(text) {
6
6
  const normalized = text.trim();
7
- // 兼容以下常见表达:
8
- // - 开发 OMS 1.24.4 需求
9
- // - 开发 OMS-V1.24.4 版本
10
- // - 开发OMS-V1.24.4版本
11
- // - 开发 AD Tools V1.2.3 PRD
12
- const match = normalized.match(/开发\s*(.+?)(?:[-_\s]*)V?([0-9]+(?:\.[0-9]+){1,3})\s*(?:需求|版本|PRD)?/i);
13
- if (!match)
14
- return null;
15
- const product = match[1].replace(/[-_\s]+$/g, "").toUpperCase();
16
- const version = match[2];
17
- return { product, version };
7
+ const patterns = [
8
+ /(?:开发|进行)\s*(.+?)(?:[-_\s]*)V?([0-9]+(?:\.[0-9]+){1,3})\s*(?:版本)?\s*(?:需求|PRD)?/i,
9
+ /([A-Za-z\u4e00-\u9fa5]+)[_\s-]?V?([0-9]+(?:\.[0-9]+){1,3})\s*版本/i,
10
+ /([A-Za-z]+)[_\s-]([0-9]+(?:\.[0-9]+){2,3})(?:\s|$)/i,
11
+ ];
12
+ for (const re of patterns) {
13
+ const match = normalized.match(re);
14
+ if (!match)
15
+ continue;
16
+ let product = match[1].replace(/[-_\s]+$/g, "").trim();
17
+ product = product.replace(/^进行/, "").trim();
18
+ if (/^adi$/i.test(product) || product.toUpperCase() === "ADI")
19
+ product = "ADI";
20
+ if (/^oms$/i.test(product))
21
+ product = "OMS";
22
+ return { product: product.toUpperCase(), version: match[2] };
23
+ }
24
+ return null;
18
25
  }
19
26
  function toRunOptions(args) {
20
27
  var _a, _b;
@@ -7,6 +7,7 @@ exports.runFlow = runFlow;
7
7
  const path_1 = __importDefault(require("path"));
8
8
  const adapters_1 = require("../adapters");
9
9
  const fs_1 = require("../core/fs");
10
+ const inferProject_1 = require("../core/inferProject");
10
11
  const startFlow_1 = require("./startFlow");
11
12
  const setupProject_1 = require("./setupProject");
12
13
  function buildChecklist(args) {
@@ -37,6 +38,7 @@ async function runFlow(options) {
37
38
  branch: options.branch,
38
39
  yes: options.yes,
39
40
  dryRun: options.dryRun,
41
+ projectKind: (0, inferProject_1.productToKind)(options.product),
40
42
  });
41
43
  const scaffold = options.dryRun
42
44
  ? {
@@ -11,6 +11,7 @@ const fs_1 = require("../core/fs");
11
11
  const readlinePrompt_1 = require("../core/readlinePrompt");
12
12
  const utils_1 = require("../core/utils");
13
13
  const opsxDelivery_1 = require("../core/opsxDelivery");
14
+ const packageJsonPatch_1 = require("../core/packageJsonPatch");
14
15
  const projectRoots_1 = require("../core/projectRoots");
15
16
  const templates_1 = require("../core/templates");
16
17
  const EDITOR_CHOICES = ["claude-code", "cursor", "codex", "openclaw"];
@@ -125,7 +126,7 @@ function allSkillInstallPaths(projectRoot) {
125
126
  .map((agent) => skillInstallPath(projectRoot, agent))
126
127
  .filter((p) => Boolean(p));
127
128
  }
128
- async function installAgentSkill(projectRoot) {
129
+ async function installAgentSkill(projectRoot, projectKind) {
129
130
  const targets = allSkillInstallPaths(projectRoot);
130
131
  if (targets.length === 0)
131
132
  return;
@@ -138,7 +139,11 @@ async function installAgentSkill(projectRoot) {
138
139
  : target.includes("/.codex/")
139
140
  ? "codex"
140
141
  : "openclaw";
141
- const content = (0, utils_1.replaceAllStr)(template, "{{AGENT_NAME}}", agentName);
142
+ const content = renderVars(template, {
143
+ AGENT_NAME: agentName,
144
+ PROJECT_KIND: projectKind,
145
+ DEFAULT_PRODUCT: projectKind === "AD Tools" ? "AD Tools" : projectKind,
146
+ });
142
147
  await (0, fs_1.writeTextFileEnsuredDir)(target, content);
143
148
  }
144
149
  }
@@ -231,9 +236,9 @@ async function setupProject(options) {
231
236
  const branches = listLocalBranches(installRoot);
232
237
  const branchExists = branches.includes(selectedBranch);
233
238
  let branchResult;
234
- if (options.light) {
239
+ if (options.light || options.skipGit) {
235
240
  branchResult = { branch: selectedBranch, created: false };
236
- plannedActions.push("light mode: skipped git branch checkout");
241
+ plannedActions.push(options.skipGit ? "skipGit: skipped git branch checkout" : "light mode: skipped git branch checkout");
237
242
  }
238
243
  else if (options.dryRun) {
239
244
  branchResult = { branch: selectedBranch, created: !branchExists };
@@ -278,14 +283,18 @@ async function setupProject(options) {
278
283
  plannedActions.push(`would install cursor rule: ${path_1.default.join(installRoot, ".cursor", "rules", "sdd-flow-kit-gates.mdc")}`);
279
284
  }
280
285
  else {
281
- await installAgentSkill(installRoot);
286
+ const pkgRoot = await (0, projectRoots_1.resolvePackageRoot)(installRoot);
287
+ const pnpmPatched = await (0, packageJsonPatch_1.ensurePnpmAllowSddFlowKitBuild)(path_1.default.join(pkgRoot, "package.json"));
288
+ if (pnpmPatched) {
289
+ plannedActions.push("added pnpm.onlyBuiltDependencies: sdd-flow-kit");
290
+ }
291
+ await installAgentSkill(installRoot, selectedKind);
282
292
  await installSddCursorRule(installRoot);
283
293
  skillInstalled = true;
284
294
  await installDocsSkill(installRoot, selectedKind);
285
295
  docsSkillInstalled = true;
286
296
  await installOpsxConfig(installRoot, selectedAgent);
287
297
  opsxConfigInstalled = true;
288
- const pkgRoot = await (0, projectRoots_1.resolvePackageRoot)(installRoot);
289
298
  const repaired = await (0, opsxDelivery_1.repairOpsxDeliveryScripts)(pkgRoot);
290
299
  if (repaired) {
291
300
  plannedActions.push("repaired package.json opsx:delivery-pipeline scripts (poquan → opsx)");
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sdd-flow-kit",
3
- "version": "1.1.1",
3
+ "version": "1.2.1",
4
4
  "private": false,
5
5
  "description": "Cross-agent SDD automated development workflow kit (ADI/ADI-like).",
6
6
  "license": "MIT",
@@ -1,73 +1,52 @@
1
1
  ---
2
- name: sdd-flow-kit-trigger
3
- description: 开发某版本需求、SDD 流程、用户回复确认点时触发。**必须**按 openspec/PRD/<runId>/NEXT.md 执行 `npx sdd-flow-kit gate|phase|propose|deliver`;命令 exit≠0 时禁止进入下一阶段或改 src/。
2
+ name: sdd-flow-kit
3
+ description: 用户说「开发/进行 {{DEFAULT_PRODUCT}} x.x.x 版本/需求」「ADI_2.3.2」等时触发。**禁止**未过 gate 直接改 src。必须先 invoke/run,确认点闭合后走 OpenSpec+Superpowers(gatepropose→apply→deliver)。
4
4
  ---
5
5
 
6
- # SDD Flow(脚本门禁版不依赖自然语言跳步)
6
+ # SDD Flow Kit({{PROJECT_KIND}} 脚本门禁)
7
7
 
8
- ## 铁律
8
+ ## 触发即执行(第一步,禁止跳过)
9
9
 
10
- 1. 每步开始前读 `openspec/PRD/<runId>/NEXT.md` `.session-state.json`。
11
- 2. **必须**在终端执行其中的 `npx sdd-flow-kit ...`;根据 **exit code** 判断是否可继续(0=通过,非0=停止)。
12
- 3. `docModeOnly: true` 时 **禁止** 修改 `src/`、`e2e/`、`frontend/src/` 等实现路径。
13
- 4. 只有 `gate --expect impl-allowed` 通过后才允许改业务代码。
14
- 5. 只有 `deliver` 成功且 `phase advance --to shipped` 通过后才可宣称「开发完成」。
15
-
16
- ## 准备
10
+ 用户一旦表达版本开发意图,**在本回合终端执行**(项目根 = 安装 sdd-flow-kit 的目录,如 `frontend/`):
17
11
 
18
12
  ```bash
19
- npx sdd-flow-kit run --product <PRODUCT> --version <VERSION> -y
13
+ npx sdd-flow-kit invoke "开发 {{DEFAULT_PRODUCT}} <版本号> 需求" -y --agent {{AGENT_NAME}}
20
14
  ```
21
15
 
22
- 工作目录:`openspec/PRD/<runId>/`(已生成 `NEXT.md`)。
23
-
24
- ## 标准命令序列(复制 NEXT.md,按序执行)
25
-
26
- | 阶段 | 门禁 / 推进 |
27
- |------|-------------|
28
- | PRD 已拉取 | `gate --expect prd-fetched` → `phase advance --to after-prd` |
29
- | 提问模式 | `gate --expect questions-open` |
30
- | 用户确认后(仅文档) | 更新 01/02/03/04 → `gate --expect docs-closed` → `phase advance --to docs-done` |
31
- | 提案 | `propose --change <name>` →(AI 生成 openspec artifacts)→ `gate --expect propose-ready` → `phase advance --to propose-done` |
32
- | 实现 | `phase advance --to impl --change <name>` → `gate --expect impl-allowed` → 按 tasks 改代码 |
33
- | 交付 | `deliver --change <name>` → `phase advance --to shipped` |
34
-
35
- 统一参数(示例):
16
+ 或:
36
17
 
37
18
  ```bash
38
- export PR=<项目根目录>
39
- export RID=<runId>
40
- export CH=<change-name>
41
-
42
- npx sdd-flow-kit gate --project-root "$PR" --run-id "$RID" --expect docs-closed
43
- npx sdd-flow-kit phase advance --project-root "$PR" --run-id "$RID" --to docs-done
19
+ npx sdd-flow-kit run --product {{DEFAULT_PRODUCT}} --version <版本号> -y --agent {{AGENT_NAME}}
44
20
  ```
45
21
 
46
- ## 各阶段说明(辅助,不可替代脚本)
47
-
48
- ### 阶段 A
49
- - Step1:`01-获取需求文档指引.md` → 回填 `source/PRD.md`
50
- - Step2:`02-需求分析提示词.md`;未闭合时 **只** 改 `03`
51
-
52
- ### 阶段 B
53
- - 用户确认后:生成 01/02/04,**然后必须** `gate docs-closed`(会检测 git 是否误改 src)
22
+ 然后 **只读** `openspec/PRD/<runId>/NEXT.md` 与 `.session-state.json`,按其中命令顺序执行;**exit code ≠ 0 必须停止**。
54
23
 
55
- ### 阶段 C
56
- - `propose` 创建 change;AI 按 openspec-propose 写 artifacts;`gate propose-ready` 校验 apply-ready
24
+ ## 铁律(防 vibe coding)
57
25
 
58
- ### 阶段 D
59
- - `phase advance --to impl` 将 `docModeOnly` 设为 false
60
- - 实现后 `deliver` 跑 `ensure-opsx` + `pnpm run opsx:delivery-pipeline`
26
+ 1. `docModeOnly: true` 时 **禁止** 修改 `src/`、`frontend/src/`、`e2e/`。
27
+ 2. 用户粘贴确认点、更新 03 后:先产出 01/02/04,再 **必须**:
28
+ ```bash
29
+ npx sdd-flow-kit gate --project-root <安装根> --run-id <runId> --expect docs-closed
30
+ npx sdd-flow-kit phase advance --project-root <安装根> --run-id <runId> --to docs-done
31
+ ```
32
+ 3. **仅当** `gate --expect impl-allowed` 通过后,才允许按 `openspec/changes/<change>/tasks.md` 改业务代码。
33
+ 4. 提案/实现 **必须** 读并执行:
34
+ - `.cursor/skills/opsx-superpowers-workflow/SKILL.md`(总编排)
35
+ - `openspec-propose` → `openspec-apply-change` → `pnpm run opsx:delivery-pipeline -- <change>`
36
+ 5. **禁止** 跳过 `propose` / `deliver` 或手写 tasks 全勾 `[x]` 宣称完成。
61
37
 
62
- ## 严禁
38
+ ## 阶段摘要
63
39
 
64
- - 跳过 `gate` / `phase` 直接改代码或宣称完成
65
- - `docs-closed` 未通过时修改 `src/`
66
- - 手写 `tasks.md` 全勾 `[x]` 而不跑 `deliver`
67
- - 用自然语言代替 exit code 判断
40
+ | 阶段 | 动作 |
41
+ |------|------|
42
+ | A | Step1 PRD → `gate prd-fetched` `phase after-prd` |
43
+ | A2 | 提问模式仅 03 → `gate questions-open` |
44
+ | B | 用户确认后 01/02/04 → `gate docs-closed` → `phase docs-done` |
45
+ | C | `propose` + OpenSpec artifacts → `gate propose-ready` → `phase propose-done` |
46
+ | D | `phase impl` → `gate impl-allowed` → 改代码 → `deliver` → `phase shipped` |
68
47
 
69
48
  ## 环境
70
49
 
71
50
  - agent: {{AGENT_NAME}}
72
- - 安装: `npx sdd-flow-kit install -y`
73
- - 脚本帮助: `npx sdd-flow-kit --help`
51
+ - 全量安装: `npx sdd-flow-kit install --project {{PROJECT_KIND}} -y`
52
+ - 本地 OPSX: `SDD_OPSX_WORKFLOW_PACKAGE=file:../../opsx-workflow`
@@ -22,6 +22,13 @@ alwaysApply: true
22
22
  - **禁止** 修改 `src/`、`e2e/`、`frontend/src/` 等
23
23
  - 用户粘贴确认点后:先 `gate docs-closed`,通过后再 `phase advance --to docs-done`;**不得**同回合改 ts/vue
24
24
 
25
+ ## 自然语言触发(ADI / OMS 等)
26
+
27
+ 用户说「开发/进行 ADI 2.3.2 版本」「ADI_2.3.2」等时:
28
+
29
+ 1. **先** 在终端执行:`npx sdd-flow-kit invoke "开发 ADI 2.3.2 需求" -y`
30
+ 2. 打开 `openspec/PRD/<runId>/NEXT.md`,**禁止**直接改 `src/`
31
+
25
32
  ## 用户确认点(无需口令)
26
33
 
27
34
  1. 更新 `03` → 生成 `01/02/04`