sdd-flow-kit 1.1.0 → 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,8 +71,43 @@ 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
 
92
+ ##### 安装根:cd 到哪,装到哪(v1.2+)
93
+
94
+ 默认 **`installRoot = 你执行命令时的当前目录`**(`process.cwd()`),也可用 `--project-root` / `SDD_FLOW_KIT_INSTALL_ROOT` 指定。
95
+
96
+ | 你 cd 到 | 会安装到 |
97
+ |----------|----------|
98
+ | `adInsight-web/frontend` | `frontend/package.json`、`.cursor/skills`、`.opsx`、`pnpm run opsx:delivery-pipeline` |
99
+ | `adInsight-web`(仓库根) | 仓库根(需根目录也有 `package.json`,否则会失败) |
100
+
101
+ ```bash
102
+ cd /path/to/adInsight-web/frontend
103
+ npx sdd-flow-kit install --project ADI -y
104
+ npx sdd-flow-kit run --product ADI --version 2.3.2 -y
105
+ ```
106
+
107
+ **PRD / gate 的 `--project-root`**:与安装根相同;若 `openspec/` 在上级目录,工具会**向上查找**已有 `openspec/PRD`(不把安装写到上级)。
108
+
109
+ 旧行为(在仓库根执行时自动落到 `frontend/`):设置 `SDD_FLOW_KIT_LEGACY_MONOREPO=1`。
110
+
76
111
  ##### 脚本门禁(v1.1+,**不依赖自然语言**)
77
112
 
78
113
  `run` 会在 `openspec/PRD/<runId>/NEXT.md` 写入**必须按顺序执行的命令**。AI 与人都以 **exit code** 为准:失败则不得进入下一阶段、不得改 `src/`。
package/dist/cli.js CHANGED
@@ -16,6 +16,8 @@ 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");
20
+ const projectRoots_1 = require("./core/projectRoots");
19
21
  const ALLOWED_AGENTS = ["cursor", "claude-code", "codex", "openclaw", "custom"];
20
22
  function getArgValue(flag) {
21
23
  const idx = process_1.default.argv.indexOf(flag);
@@ -34,9 +36,11 @@ function printHelp() {
34
36
  "1) 生成流程产物骨架:",
35
37
  " npx sdd-flow-kit start --product ADI --version 2.3.2",
36
38
  "",
37
- "2) 安装流程到当前项目(推荐,安装即用):",
38
- " npx sdd-flow-kit install",
39
- " (等价于: 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",
40
44
  "",
41
45
  "3) 生成骨架并进入可执行模式(推荐):",
42
46
  " npx sdd-flow-kit run --product ADI --version 2.3.2 --agent claude-code",
@@ -63,7 +67,9 @@ function printHelp() {
63
67
  " npx sdd-flow-kit deliver --project-root . --run-id <runId> --change <name>",
64
68
  "",
65
69
  "可选参数:",
66
- " --projectRoot <path> 目标项目根目录(默认当前目录)",
70
+ " --projectRoot <path> 目标项目根目录(默认当前目录 = 安装根,cd 到哪装到哪)",
71
+ " 环境变量 SDD_FLOW_KIT_INSTALL_ROOT 可覆盖安装根(绝对路径)",
72
+ " 环境变量 SDD_FLOW_KIT_LEGACY_MONOREPO=1 恢复「仓库根无 package.json 时落到 frontend/」",
67
73
  " --agent <name> cursor | claude-code | codex | openclaw | custom(默认 claude-code)",
68
74
  " --branch <name> 开发分支;不存在会自动创建",
69
75
  " --project <name> OMS | ADI | 欢盟 | AD Tools(默认 OMS,用于生成 docs/*-doc-skill)",
@@ -138,6 +144,26 @@ async function main() {
138
144
  process_1.default.exitCode = 1;
139
145
  return;
140
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
+ }
141
167
  if (cmd === "gate") {
142
168
  const expect = getArgValue("--expect");
143
169
  if (!expect || !GATE_EXPECTS.includes(expect)) {
@@ -242,20 +268,13 @@ async function main() {
242
268
  // eslint-disable-next-line no-console
243
269
  console.log(JSON.stringify({ command: "doctor", projectRoot: path_1.default.resolve(projectRoot), result: doctor }, null, 2));
244
270
  }
245
- if (cmd === "ensure-opsx" && !dryRun) {
246
- await (0, setupProject_1.setupProject)({
247
- projectRoot,
248
- agent,
249
- yes: true,
250
- light: true,
251
- projectKind: projectKind,
252
- });
253
- }
254
271
  if (!doctor.ok)
255
272
  process_1.default.exitCode = 1;
256
273
  return;
257
274
  }
258
275
  if (cmd === "setup" || cmd === "install") {
276
+ // eslint-disable-next-line no-console
277
+ console.log(`[sdd-flow-kit] 安装根 installRoot=${(0, projectRoots_1.resolveInstallRoot)(projectRoot)} (cwd=${process_1.default.cwd()})`);
259
278
  const result = await (0, setupProject_1.setupProject)({
260
279
  projectRoot,
261
280
  agent,
@@ -292,7 +311,7 @@ async function main() {
292
311
  const text = (fromFlag !== null && fromFlag !== void 0 ? fromFlag : raw).trim();
293
312
  const parsed = (0, invokeFlow_1.parseInvokeText)(text);
294
313
  if (!parsed) {
295
- throw new Error("invoke 解析失败,请使用:开发 ADI 2.3.2 需求");
314
+ throw new Error("invoke 解析失败,示例:开发 ADI 2.3.2 需求 | 进行ADI_2.3.2版本开发");
296
315
  }
297
316
  const options = (0, invokeFlow_1.toRunOptions)({
298
317
  parsed,
@@ -326,6 +345,8 @@ async function main() {
326
345
  console.log(`已生成 SDD 流程产物骨架:openspec/PRD/${scaffoldRunId}/`);
327
346
  return;
328
347
  }
348
+ // eslint-disable-next-line no-console
349
+ console.log(`[sdd-flow-kit] 安装根 installRoot=${(0, projectRoots_1.resolveInstallRoot)(projectRoot)} (cwd=${process_1.default.cwd()})`);
329
350
  const result = await (0, runFlow_1.runFlow)({
330
351
  product,
331
352
  version,
@@ -342,7 +363,7 @@ async function main() {
342
363
  // eslint-disable-next-line no-console
343
364
  console.log(`流程已初始化:openspec/PRD/${result.runId}/`);
344
365
  // eslint-disable-next-line no-console
345
- console.log(`请打开 openspec/PRD/${result.runId}/NEXT.md,按脚本顺序执行 gate / phase / propose / deliver。`);
366
+ console.log(`请打开 openspec/PRD/${result.runId}/NEXT.md,严格按 gatephasepropose→deliver 执行;确认点前禁止改 src/。`);
346
367
  // eslint-disable-next-line no-console
347
368
  console.log(`agent=${result.adapterName}, cli=${result.adapterAvailable ? "detected" : "missing"}, autoExecute=${result.executed ? "yes" : "manual"}`);
348
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,7 +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");
19
+ const projectRoots_1 = require("./projectRoots");
20
+ const opsxWorkflowPackage_1 = require("./opsxWorkflowPackage");
21
+ function resolveOpsxWorkflowPackage() {
22
+ return (0, opsxWorkflowPackage_1.resolveOpsxWorkflowPackageForCwd)(process.cwd());
23
+ }
18
24
  const DELIVERY_SCRIPT_REL = ".cursor/skills/opsx-dev-delivery-pipeline/scripts/opsx-delivery-pipeline.sh";
19
25
  function deliveryPipelineNpmScripts() {
20
26
  const full = `bash ${DELIVERY_SCRIPT_REL}`;
@@ -44,7 +50,7 @@ async function resolveOpsxAgent(projectRoot, preferred) {
44
50
  return "claude-code";
45
51
  return preferred;
46
52
  }
47
- /** 将 package.json 中过期的 poquan-delivery-pipeline 脚本修正为 opsx-dev-delivery-pipeline */
53
+ /** 将 package.json 中过期的 poquan 脚本修正为 opsx-dev-delivery-pipeline(仅合并 scripts) */
48
54
  async function repairOpsxDeliveryScripts(projectRoot) {
49
55
  const pkgPath = path_1.default.join(projectRoot, "package.json");
50
56
  if (!(await pathExists(pkgPath)))
@@ -54,29 +60,35 @@ async function repairOpsxDeliveryScripts(projectRoot) {
54
60
  return false;
55
61
  const { full, short } = deliveryPipelineNpmScripts();
56
62
  const text = await promises_1.default.readFile(pkgPath, "utf8");
57
- const pkg = JSON.parse(text);
63
+ let pkg;
64
+ try {
65
+ pkg = JSON.parse(text);
66
+ }
67
+ catch {
68
+ return false;
69
+ }
58
70
  if (!pkg.scripts)
59
71
  pkg.scripts = {};
60
- let changed = false;
61
72
  const needsFix = (v) => !v || v.includes("poquan-dev-delivery-pipeline") || v.includes("poquan-delivery-pipeline");
62
- if (needsFix(pkg.scripts["opsx:delivery-pipeline"])) {
63
- pkg.scripts["opsx:delivery-pipeline"] = full;
64
- changed = true;
65
- }
73
+ const updates = {};
74
+ if (needsFix(pkg.scripts["opsx:delivery-pipeline"]))
75
+ updates["opsx:delivery-pipeline"] = full;
66
76
  if (needsFix(pkg.scripts["opsx:delivery-pipeline:short"])) {
67
- pkg.scripts["opsx:delivery-pipeline:short"] = short;
68
- changed = true;
77
+ updates["opsx:delivery-pipeline:short"] = short;
69
78
  }
70
- if (!changed)
79
+ if (Object.keys(updates).length === 0)
71
80
  return false;
72
- await (0, fs_1.writeTextFileEnsuredDir)(pkgPath, `${JSON.stringify(pkg, null, 2)}\n`);
73
- 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;
74
86
  }
75
87
  function hasCommand(cmd) {
76
88
  return (0, child_process_1.spawnSync)("which", [cmd], { stdio: "ignore" }).status === 0;
77
89
  }
78
90
  function buildOpsxWorkflowSetupCommand(projectRoot, agent) {
79
- const baseArgs = ["@lixin5257xxx/opsx-workflow", "setup"];
91
+ const baseArgs = [resolveOpsxWorkflowPackage(), "setup"];
80
92
  if (agent === "cursor") {
81
93
  return {
82
94
  cmd: "npx",
@@ -92,13 +104,16 @@ function buildOpsxWorkflowSetupCommand(projectRoot, agent) {
92
104
  return { cmd: "npx", args: [...baseArgs, "--editor", "codex", "-y"] };
93
105
  }
94
106
  async function isOpsxApplyReady(projectRoot) {
95
- const checks = await collectDeliverySetupChecks(projectRoot);
107
+ const installRoot = await (0, projectRoots_1.resolvePackageRoot)((0, projectRoots_1.resolveInstallRoot)(projectRoot));
108
+ const checks = await collectDeliverySetupChecks(installRoot);
96
109
  return checks.every((c) => c.ok);
97
110
  }
98
111
  /** 缺 openspec/TDD/Playwright 工作流时安装 @lixin5257xxx/opsx-workflow,并修复 delivery 脚本 */
99
112
  async function ensureOpsxWorkflow(options) {
113
+ var _a;
114
+ const installRoot = await (0, projectRoots_1.resolvePackageRoot)((0, projectRoots_1.resolveInstallRoot)(options.projectRoot));
100
115
  const plannedActions = [];
101
- let checks = await collectDeliverySetupChecks(options.projectRoot);
116
+ let checks = await collectDeliverySetupChecks(installRoot);
102
117
  if (checks.every((c) => c.ok)) {
103
118
  return {
104
119
  ready: true,
@@ -108,8 +123,8 @@ async function ensureOpsxWorkflow(options) {
108
123
  plannedActions,
109
124
  };
110
125
  }
111
- const agent = await resolveOpsxAgent(options.projectRoot, options.agent);
112
- const { cmd, args } = buildOpsxWorkflowSetupCommand(options.projectRoot, agent);
126
+ const agent = await resolveOpsxAgent(installRoot, options.agent);
127
+ const { cmd, args } = buildOpsxWorkflowSetupCommand(installRoot, agent);
113
128
  plannedActions.push(`missing opsx apply stack → run: ${cmd} ${args.join(" ")}`);
114
129
  if (options.dryRun) {
115
130
  return {
@@ -121,18 +136,21 @@ async function ensureOpsxWorkflow(options) {
121
136
  };
122
137
  }
123
138
  const ret = (0, child_process_1.spawnSync)(cmd, args, {
124
- cwd: options.projectRoot,
139
+ cwd: installRoot,
125
140
  stdio: "inherit",
126
141
  env: process.env,
127
142
  });
128
143
  if (ret.status !== 0) {
129
- 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}`);
130
148
  }
131
- const repairedScripts = await repairOpsxDeliveryScripts(options.projectRoot);
149
+ const repairedScripts = await repairOpsxDeliveryScripts(installRoot);
132
150
  if (repairedScripts) {
133
151
  plannedActions.push("repaired package.json opsx:delivery-pipeline scripts");
134
152
  }
135
- checks = await collectDeliverySetupChecks(options.projectRoot);
153
+ checks = await collectDeliverySetupChecks(installRoot);
136
154
  return {
137
155
  ready: checks.every((c) => c.ok),
138
156
  workflowInstalled: true,
@@ -141,45 +159,54 @@ async function ensureOpsxWorkflow(options) {
141
159
  plannedActions,
142
160
  };
143
161
  }
144
- async function collectDeliverySetupChecks(projectRoot) {
162
+ async function collectDeliverySetupChecks(installRoot) {
145
163
  var _a, _b, _c, _d, _e, _f;
146
164
  const checks = [];
165
+ const root = (0, projectRoots_1.resolveInstallRoot)(installRoot);
166
+ const openspecRoot = await (0, projectRoots_1.resolveOpenspecRoot)(root);
167
+ checks.push({
168
+ name: "install-root",
169
+ ok: true,
170
+ detail: `安装根=${root}`,
171
+ });
147
172
  checks.push({
148
173
  name: "openspec-dir",
149
- ok: await pathExists(path_1.default.join(projectRoot, "openspec")),
150
- detail: "openspec/ 目录",
174
+ ok: await pathExists(path_1.default.join(openspecRoot, "openspec")),
175
+ detail: openspecRoot === root ? "openspec/ 目录(安装根)" : `openspec/ 目录(上级 ${openspecRoot})`,
151
176
  });
177
+ const openspecOk = hasCommand("openspec") ||
178
+ (0, child_process_1.spawnSync)("npx", ["openspec", "--version"], { stdio: "ignore", cwd: root }).status === 0;
152
179
  checks.push({
153
180
  name: "openspec-cli",
154
- ok: hasCommand("openspec"),
155
- detail: "openspec CLI(npx openspec 或全局安装)",
181
+ ok: openspecOk,
182
+ detail: openspecOk ? "openspec CLI 可用" : "请安装 openspec CLI 或执行 opsx-workflow setup",
156
183
  });
157
- const skillPath = path_1.default.join(projectRoot, ".cursor", "skills", "opsx-dev-delivery-pipeline", "SKILL.md");
184
+ const skillPath = path_1.default.join(root, ".cursor", "skills", "opsx-dev-delivery-pipeline", "SKILL.md");
158
185
  checks.push({
159
186
  name: "opsx-dev-delivery-pipeline-skill",
160
187
  ok: await pathExists(skillPath),
161
188
  detail: skillPath,
162
189
  });
163
- const tddPath = path_1.default.join(projectRoot, ".cursor", "skills", "tdd-script", "SKILL.md");
190
+ const tddPath = path_1.default.join(root, ".cursor", "skills", "tdd-script", "SKILL.md");
164
191
  checks.push({
165
192
  name: "tdd-script-skill",
166
193
  ok: await pathExists(tddPath),
167
194
  detail: tddPath,
168
195
  });
169
- const applyPath = path_1.default.join(projectRoot, ".cursor", "skills", "openspec-apply-change", "SKILL.md");
196
+ const applyPath = path_1.default.join(root, ".cursor", "skills", "openspec-apply-change", "SKILL.md");
170
197
  checks.push({
171
198
  name: "openspec-apply-change-skill",
172
199
  ok: await pathExists(applyPath),
173
200
  detail: applyPath,
174
201
  });
175
- const pipelineScript = path_1.default.join(projectRoot, DELIVERY_SCRIPT_REL);
202
+ const pipelineScript = path_1.default.join(root, DELIVERY_SCRIPT_REL);
176
203
  checks.push({
177
204
  name: "delivery-pipeline-script",
178
205
  ok: await pathExists(pipelineScript),
179
206
  detail: pipelineScript,
180
207
  });
181
208
  try {
182
- const pkgText = await promises_1.default.readFile(path_1.default.join(projectRoot, "package.json"), "utf8");
209
+ const pkgText = await promises_1.default.readFile(path_1.default.join(root, "package.json"), "utf8");
183
210
  const pkg = JSON.parse(pkgText);
184
211
  const cmd = (_b = (_a = pkg.scripts) === null || _a === void 0 ? void 0 : _a["opsx:delivery-pipeline"]) !== null && _b !== void 0 ? _b : "";
185
212
  const ok = cmd.includes("opsx-dev-delivery-pipeline") &&
@@ -199,10 +226,10 @@ async function collectDeliverySetupChecks(projectRoot) {
199
226
  });
200
227
  }
201
228
  try {
202
- const cfgText = await promises_1.default.readFile(path_1.default.join(projectRoot, ".opsx", "config.json"), "utf8");
229
+ const cfgText = await promises_1.default.readFile(path_1.default.join(root, ".opsx", "config.json"), "utf8");
203
230
  const cfg = JSON.parse(cfgText);
204
231
  const skillsDir = (_c = cfg.skillsDir) !== null && _c !== void 0 ? _c : "";
205
- const hasCursorDelivery = await pathExists(path_1.default.join(projectRoot, ".cursor", "skills", "opsx-dev-delivery-pipeline", "SKILL.md"));
232
+ const hasCursorDelivery = await pathExists(path_1.default.join(root, ".cursor", "skills", "opsx-dev-delivery-pipeline", "SKILL.md"));
206
233
  const nameOk = ((_d = cfg.deliveryPipeline) === null || _d === void 0 ? void 0 : _d.skillName) === "opsx-dev-delivery-pipeline";
207
234
  const dirOk = (hasCursorDelivery && skillsDir.includes(".cursor")) ||
208
235
  (!hasCursorDelivery && (skillsDir.includes(".cursor") || skillsDir.includes(".claude")));
@@ -222,7 +249,7 @@ async function collectDeliverySetupChecks(projectRoot) {
222
249
  });
223
250
  }
224
251
  try {
225
- const pkgText = await promises_1.default.readFile(path_1.default.join(projectRoot, "package.json"), "utf8");
252
+ const pkgText = await promises_1.default.readFile(path_1.default.join(root, "package.json"), "utf8");
226
253
  const pkg = JSON.parse(pkgText);
227
254
  const deps = { ...pkg.dependencies, ...pkg.devDependencies };
228
255
  const hasPw = Boolean(deps["@playwright/test"] || deps.playwright);
@@ -239,7 +266,7 @@ async function collectDeliverySetupChecks(projectRoot) {
239
266
  detail: "无法读取 package.json 检查 Playwright",
240
267
  });
241
268
  }
242
- const agentsMd = path_1.default.join(projectRoot, "AGENTS.md");
269
+ const agentsMd = path_1.default.join(root, "AGENTS.md");
243
270
  checks.push({
244
271
  name: "agents-md",
245
272
  ok: await pathExists(agentsMd),
@@ -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
+ }
@@ -0,0 +1,94 @@
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.resolveInstallRoot = resolveInstallRoot;
7
+ exports.isLegacyMonorepoMode = isLegacyMonorepoMode;
8
+ exports.resolveOpenspecRoot = resolveOpenspecRoot;
9
+ exports.resolvePackageRoot = resolvePackageRoot;
10
+ exports.listOpenspecPrdRoots = listOpenspecPrdRoots;
11
+ const promises_1 = __importDefault(require("fs/promises"));
12
+ const path_1 = __importDefault(require("path"));
13
+ async function pathExists(filePath) {
14
+ try {
15
+ await promises_1.default.access(filePath);
16
+ return true;
17
+ }
18
+ catch {
19
+ return false;
20
+ }
21
+ }
22
+ /**
23
+ * 安装根:严格等于 CLI 的 projectRoot(默认 process.cwd())。
24
+ * 可通过 SDD_FLOW_KIT_INSTALL_ROOT 覆盖(绝对路径)。
25
+ * 原则:cd 到哪,package.json / .cursor/skills / opsx-workflow 就装到哪。
26
+ */
27
+ function resolveInstallRoot(projectRoot) {
28
+ var _a;
29
+ const fromEnv = (_a = process.env.SDD_FLOW_KIT_INSTALL_ROOT) === null || _a === void 0 ? void 0 : _a.trim();
30
+ if (fromEnv)
31
+ return path_1.default.resolve(fromEnv);
32
+ return path_1.default.resolve(projectRoot);
33
+ }
34
+ /** 是否启用旧版 monorepo 行为(在仓库根执行时自动落到 frontend/) */
35
+ function isLegacyMonorepoMode() {
36
+ return process.env.SDD_FLOW_KIT_LEGACY_MONOREPO === "1" || process.env.SDD_FLOW_KIT_LEGACY_MONOREPO === "true";
37
+ }
38
+ /**
39
+ * 从 installRoot 向上查找含 openspec/ 的目录(用于解析 PRD run、openspec CLI)。
40
+ * 找不到则退回 installRoot(将在其下新建 openspec/PRD)。
41
+ */
42
+ async function resolveOpenspecRoot(installRoot) {
43
+ let cur = path_1.default.resolve(installRoot);
44
+ for (let i = 0; i < 12; i += 1) {
45
+ if (await pathExists(path_1.default.join(cur, "openspec"))) {
46
+ return cur;
47
+ }
48
+ const parent = path_1.default.dirname(cur);
49
+ if (parent === cur)
50
+ break;
51
+ cur = parent;
52
+ }
53
+ return path_1.default.resolve(installRoot);
54
+ }
55
+ /**
56
+ * 交付/门禁用的包根:默认与 installRoot 相同。
57
+ * 仅当 SDD_FLOW_KIT_LEGACY_MONOREPO=1 且 installRoot 无 package.json 但存在 frontend/package.json 时回落到 frontend/。
58
+ */
59
+ async function resolvePackageRoot(installRoot) {
60
+ const root = resolveInstallRoot(installRoot);
61
+ if (!isLegacyMonorepoMode()) {
62
+ return root;
63
+ }
64
+ const pkgAtRoot = path_1.default.join(root, "package.json");
65
+ if (await pathExists(pkgAtRoot)) {
66
+ return root;
67
+ }
68
+ const fePkg = path_1.default.join(root, "frontend", "package.json");
69
+ if (await pathExists(fePkg)) {
70
+ return path_1.default.join(root, "frontend");
71
+ }
72
+ return root;
73
+ }
74
+ /**
75
+ * 收集自 installRoot 向上所有存在的 openspec/PRD 目录(用于 resolveRun)。
76
+ */
77
+ async function listOpenspecPrdRoots(startDir) {
78
+ const seen = new Set();
79
+ const out = [];
80
+ let cur = path_1.default.resolve(startDir);
81
+ for (let i = 0; i < 12; i += 1) {
82
+ const prdRoot = path_1.default.join(cur, "openspec", "PRD");
83
+ const key = prdRoot.toLowerCase();
84
+ if (!seen.has(key) && (await pathExists(prdRoot))) {
85
+ seen.add(key);
86
+ out.push(prdRoot);
87
+ }
88
+ const parent = path_1.default.dirname(cur);
89
+ if (parent === cur)
90
+ break;
91
+ cur = parent;
92
+ }
93
+ return out;
94
+ }