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.
@@ -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,16 +7,15 @@ exports.resolveRunDir = resolveRunDir;
7
7
  exports.loadRunSession = loadRunSession;
8
8
  const promises_1 = __importDefault(require("fs/promises"));
9
9
  const path_1 = __importDefault(require("path"));
10
+ const projectRoots_1 = require("../core/projectRoots");
10
11
  const sessionState_1 = require("../core/sessionState");
11
12
  const fs_1 = require("../core/fs");
12
13
  /**
13
- * 解析 openspec/PRD 目录:优先 --run-id,否则取最近修改的 run 目录。
14
+ * 解析 openspec/PRD 目录:从 installRoot(cwd)向上查找;优先 --run-id,否则取最近修改的 run 目录。
14
15
  */
15
16
  async function resolveRunDir(projectRoot, runId) {
16
- const candidates = [
17
- path_1.default.join(projectRoot, "openspec", "PRD"),
18
- path_1.default.join(projectRoot, "frontend", "openspec", "PRD"),
19
- ];
17
+ const installRoot = (0, projectRoots_1.resolveInstallRoot)(projectRoot);
18
+ const candidates = await (0, projectRoots_1.listOpenspecPrdRoots)(installRoot);
20
19
  for (const prdRoot of candidates) {
21
20
  try {
22
21
  const entries = await promises_1.default.readdir(prdRoot, { withFileTypes: true });
@@ -9,14 +9,8 @@ const child_process_1 = require("child_process");
9
9
  const resolveRun_1 = require("./resolveRun");
10
10
  const runGate_1 = require("./runGate");
11
11
  const opsxDelivery_1 = require("../core/opsxDelivery");
12
+ const projectRoots_1 = require("../core/projectRoots");
12
13
  const fs_1 = require("../core/fs");
13
- async function resolvePackageRoot(projectRoot) {
14
- const fe = path_1.default.join(projectRoot, "frontend", "package.json");
15
- if (await (0, opsxDelivery_1.pathExists)(fe)) {
16
- return path_1.default.join(projectRoot, "frontend");
17
- }
18
- return projectRoot;
19
- }
20
14
  /**
21
15
  * 实现阶段交付:先 gate impl-allowed,再 ensure-opsx,再跑 opsx:delivery-pipeline,写入 .delivery-pass.json。
22
16
  */
@@ -36,10 +30,11 @@ async function runDeliver(options) {
36
30
  (0, runGate_1.printGateResult)(implGate);
37
31
  return { ok: false, detail: "impl-allowed gate 未通过" };
38
32
  }
39
- const pkgRoot = await resolvePackageRoot(options.projectRoot);
33
+ const installRoot = (0, projectRoots_1.resolveInstallRoot)(options.projectRoot);
34
+ const pkgRoot = await (0, projectRoots_1.resolvePackageRoot)(installRoot);
40
35
  const agent = options.agent || "cursor";
41
36
  if (options.dryRun) {
42
- return { ok: true, detail: `dry-run: would deliver in ${pkgRoot}` };
37
+ return { ok: true, detail: `dry-run: would deliver in ${pkgRoot} (installRoot=${installRoot})` };
43
38
  }
44
39
  const ensured = await (0, opsxDelivery_1.ensureOpsxWorkflow)({
45
40
  projectRoot: pkgRoot,
@@ -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
  ? {
@@ -8,6 +8,7 @@ exports.printGateResult = printGateResult;
8
8
  const path_1 = __importDefault(require("path"));
9
9
  const prdArtifacts_1 = require("../core/prdArtifacts");
10
10
  const gitDiffGuard_1 = require("../core/gitDiffGuard");
11
+ const projectRoots_1 = require("../core/projectRoots");
11
12
  const openspecGate_1 = require("../core/openspecGate");
12
13
  const resolveRun_1 = require("./resolveRun");
13
14
  const fs_1 = require("../core/fs");
@@ -22,9 +23,28 @@ async function readActiveChange(projectRoot, session, override) {
22
23
  return override;
23
24
  if (session === null || session === void 0 ? void 0 : session.activeChange)
24
25
  return session.activeChange;
25
- const rootCandidates = [projectRoot, path_1.default.join(projectRoot, "frontend")];
26
- for (const root of rootCandidates) {
27
- const text = await (0, fs_1.readFileIfExists)(path_1.default.join(root, ".opsx-active-change"));
26
+ const installRoot = (0, projectRoots_1.resolveInstallRoot)(projectRoot);
27
+ const prdRoots = await (0, projectRoots_1.listOpenspecPrdRoots)(installRoot);
28
+ const dirs = [installRoot, ...prdRoots.map((p) => path_1.default.dirname(path_1.default.dirname(p)))];
29
+ const seen = new Set();
30
+ for (const root of dirs) {
31
+ const key = path_1.default.resolve(root);
32
+ if (seen.has(key))
33
+ continue;
34
+ seen.add(key);
35
+ const text = await (0, fs_1.readFileIfExists)(path_1.default.join(key, ".opsx-active-change"));
36
+ if (text) {
37
+ const line = text
38
+ .split("\n")
39
+ .map((l) => l.trim())
40
+ .find(Boolean);
41
+ if (line)
42
+ return line;
43
+ }
44
+ }
45
+ if ((0, projectRoots_1.isLegacyMonorepoMode)()) {
46
+ const fe = path_1.default.join(installRoot, "frontend", ".opsx-active-change");
47
+ const text = await (0, fs_1.readFileIfExists)(fe);
28
48
  if (text) {
29
49
  const line = text
30
50
  .split("\n")
@@ -36,11 +56,15 @@ async function readActiveChange(projectRoot, session, override) {
36
56
  }
37
57
  return null;
38
58
  }
39
- function resolveImplRoots(projectRoot) {
40
- const roots = [path_1.default.resolve(projectRoot)];
41
- const fe = path_1.default.join(projectRoot, "frontend");
42
- if (fe !== roots[0])
43
- roots.push(path_1.default.resolve(fe));
59
+ async function resolveImplRoots(projectRoot) {
60
+ const installRoot = (0, projectRoots_1.resolveInstallRoot)(projectRoot);
61
+ const roots = [installRoot];
62
+ if ((0, projectRoots_1.isLegacyMonorepoMode)()) {
63
+ const pkgRoot = await (0, projectRoots_1.resolvePackageRoot)(installRoot);
64
+ if (pkgRoot !== installRoot && !roots.includes(pkgRoot)) {
65
+ roots.push(pkgRoot);
66
+ }
67
+ }
44
68
  return roots;
45
69
  }
46
70
  /**
@@ -56,7 +80,7 @@ async function runGate(options) {
56
80
  const { resolved, session } = loaded;
57
81
  const outputRoot = resolved.outputRoot;
58
82
  const runId = resolved.runId;
59
- const implRoots = resolveImplRoots(options.projectRoot);
83
+ const implRoots = await resolveImplRoots(options.projectRoot);
60
84
  if (options.expect === "prd-fetched") {
61
85
  const prd = await (0, prdArtifacts_1.isPrdFetched)(outputRoot);
62
86
  checks.push({ name: "prd-fetched", ...prd });
@@ -115,10 +139,19 @@ async function runGate(options) {
115
139
  checks.push({ name: "active-change", ok: false, detail: "未指定 change(--change 或 .opsx-active-change)" });
116
140
  return fail(checks, options.expect, runId);
117
141
  }
118
- const roots = [options.projectRoot, path_1.default.join(options.projectRoot, "frontend")];
142
+ const installRoot = (0, projectRoots_1.resolveInstallRoot)(options.projectRoot);
143
+ const openspecRoots = [
144
+ installRoot,
145
+ ...(await (0, projectRoots_1.listOpenspecPrdRoots)(installRoot)).map((p) => path_1.default.dirname(path_1.default.dirname(p))),
146
+ ];
147
+ const seen = new Set();
119
148
  let found = false;
120
- for (const root of roots) {
121
- if (await (0, openspecGate_1.changeDirExists)(root, changeName)) {
149
+ for (const root of openspecRoots) {
150
+ const key = path_1.default.resolve(root);
151
+ if (seen.has(key))
152
+ continue;
153
+ seen.add(key);
154
+ if (await (0, openspecGate_1.changeDirExists)(key, changeName)) {
122
155
  found = true;
123
156
  const st = (0, openspecGate_1.checkOpenspecApplyReady)(root, changeName);
124
157
  checks.push({ name: "openspec-apply-ready", ok: st.applyReady, detail: st.detail });
@@ -156,14 +189,32 @@ async function runGate(options) {
156
189
  checks.push({ name: "active-change", ok: false, detail: "未绑定 activeChange" });
157
190
  return fail(checks, options.expect, runId);
158
191
  }
159
- const roots = [options.projectRoot, path_1.default.join(options.projectRoot, "frontend")];
160
- for (const root of roots) {
161
- if (await (0, openspecGate_1.changeDirExists)(root, changeName)) {
162
- const st = (0, openspecGate_1.checkOpenspecApplyReady)(root, changeName);
192
+ const installRoot = (0, projectRoots_1.resolveInstallRoot)(options.projectRoot);
193
+ const openspecRoots = [
194
+ installRoot,
195
+ ...(await (0, projectRoots_1.listOpenspecPrdRoots)(installRoot)).map((p) => path_1.default.dirname(path_1.default.dirname(p))),
196
+ ];
197
+ const seen = new Set();
198
+ let found = false;
199
+ for (const root of openspecRoots) {
200
+ const key = path_1.default.resolve(root);
201
+ if (seen.has(key))
202
+ continue;
203
+ seen.add(key);
204
+ if (await (0, openspecGate_1.changeDirExists)(key, changeName)) {
205
+ found = true;
206
+ const st = (0, openspecGate_1.checkOpenspecApplyReady)(key, changeName);
163
207
  checks.push({ name: "openspec-apply-ready", ok: st.applyReady, detail: st.detail });
164
208
  break;
165
209
  }
166
210
  }
211
+ if (!found) {
212
+ checks.push({
213
+ name: "change-dir",
214
+ ok: false,
215
+ detail: `openspec/changes/${changeName} 不存在`,
216
+ });
217
+ }
167
218
  return pass(checks, options.expect, runId);
168
219
  }
169
220
  if (options.expect === "shipped") {
@@ -172,10 +223,19 @@ async function runGate(options) {
172
223
  checks.push({ name: "active-change", ok: false, detail: "未指定 change" });
173
224
  return fail(checks, options.expect, runId);
174
225
  }
175
- const roots = [options.projectRoot, path_1.default.join(options.projectRoot, "frontend")];
176
- for (const root of roots) {
177
- if (await (0, openspecGate_1.changeDirExists)(root, changeName)) {
178
- const tasks = await (0, openspecGate_1.checkTasksAllDone)(root, changeName);
226
+ const installRoot = (0, projectRoots_1.resolveInstallRoot)(options.projectRoot);
227
+ const openspecRoots = [
228
+ installRoot,
229
+ ...(await (0, projectRoots_1.listOpenspecPrdRoots)(installRoot)).map((p) => path_1.default.dirname(path_1.default.dirname(p))),
230
+ ];
231
+ const seen = new Set();
232
+ for (const root of openspecRoots) {
233
+ const key = path_1.default.resolve(root);
234
+ if (seen.has(key))
235
+ continue;
236
+ seen.add(key);
237
+ if (await (0, openspecGate_1.changeDirExists)(key, changeName)) {
238
+ const tasks = await (0, openspecGate_1.checkTasksAllDone)(key, changeName);
179
239
  checks.push({ name: "tasks-done", ...tasks });
180
240
  const marker = await (0, fs_1.readFileIfExists)(path_1.default.join(outputRoot, ".delivery-pass.json"));
181
241
  if (marker) {
@@ -7,6 +7,7 @@ exports.runPhaseAdvance = runPhaseAdvance;
7
7
  const path_1 = __importDefault(require("path"));
8
8
  const runGate_1 = require("./runGate");
9
9
  const resolveRun_1 = require("./resolveRun");
10
+ const projectRoots_1 = require("../core/projectRoots");
10
11
  const sessionState_1 = require("../core/sessionState");
11
12
  const fs_1 = require("../core/fs");
12
13
  const gitDiffGuard_1 = require("../core/gitDiffGuard");
@@ -112,9 +113,8 @@ async function runPhaseAdvance(options) {
112
113
  };
113
114
  if (!options.dryRun) {
114
115
  await (0, sessionState_1.writeSession)(outputRoot, next);
115
- await (0, fs_1.writeTextFileEnsuredDir)(path_1.default.join(options.projectRoot, ".opsx-active-change"), `${changeName}\n`);
116
- const fe = path_1.default.join(options.projectRoot, "frontend", ".opsx-active-change");
117
- await (0, fs_1.writeTextFileEnsuredDir)(fe, `${changeName}\n`).catch(() => undefined);
116
+ const installRoot = (0, projectRoots_1.resolveInstallRoot)(options.projectRoot);
117
+ await (0, fs_1.writeTextFileEnsuredDir)(path_1.default.join(installRoot, ".opsx-active-change"), `${changeName}\n`);
118
118
  await writeNextMd(outputRoot, [
119
119
  "## 当前:阶段 C 完成 → 进入 D(实现)",
120
120
  "```bash",
@@ -139,9 +139,8 @@ async function runPhaseAdvance(options) {
139
139
  (0, runGate_1.printGateResult)(gate);
140
140
  return { ok: false, runId, message: "propose 未就绪,禁止进入实现" };
141
141
  }
142
- const gitRoot = (0, gitDiffGuard_1.resolveGitRoot)(path_1.default.join(options.projectRoot, "frontend") === path_1.default.join(options.projectRoot)
143
- ? options.projectRoot
144
- : path_1.default.join(options.projectRoot, "frontend"));
142
+ const installRoot = (0, projectRoots_1.resolveInstallRoot)(options.projectRoot);
143
+ const gitRoot = (0, gitDiffGuard_1.resolveGitRoot)(installRoot);
145
144
  const head = (0, gitDiffGuard_1.gitDiffNameOnly)(gitRoot).head;
146
145
  const next = {
147
146
  ...session,
@@ -9,7 +9,7 @@ const child_process_1 = require("child_process");
9
9
  const resolveRun_1 = require("./resolveRun");
10
10
  const runGate_1 = require("./runGate");
11
11
  const fs_1 = require("../core/fs");
12
- const opsxDelivery_1 = require("../core/opsxDelivery");
12
+ const projectRoots_1 = require("../core/projectRoots");
13
13
  /**
14
14
  * 创建/校验 openspec change,并检查 apply-ready(不代替 AI 写 proposal 正文,但强制 gate)。
15
15
  */
@@ -27,14 +27,8 @@ async function runPropose(options) {
27
27
  (0, runGate_1.printGateResult)(docsGate);
28
28
  return { ok: false, detail: "docs-closed gate 未通过,禁止 propose" };
29
29
  }
30
- const roots = [options.projectRoot, path_1.default.join(options.projectRoot, "frontend")];
31
- let cwd = options.projectRoot;
32
- for (const r of roots) {
33
- if (await (0, opsxDelivery_1.pathExists)(path_1.default.join(r, "openspec"))) {
34
- cwd = r;
35
- break;
36
- }
37
- }
30
+ const installRoot = (0, projectRoots_1.resolveInstallRoot)(options.projectRoot);
31
+ const cwd = await (0, projectRoots_1.resolveOpenspecRoot)(installRoot);
38
32
  if (options.dryRun) {
39
33
  return { ok: true, detail: `dry-run: would openspec new change ${options.change}` };
40
34
  }
@@ -11,6 +11,8 @@ 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");
15
+ const projectRoots_1 = require("../core/projectRoots");
14
16
  const templates_1 = require("../core/templates");
15
17
  const EDITOR_CHOICES = ["claude-code", "cursor", "codex", "openclaw"];
16
18
  const PROJECT_CHOICES = ["OMS", "ADI", "欢盟", "AD Tools"];
@@ -124,7 +126,7 @@ function allSkillInstallPaths(projectRoot) {
124
126
  .map((agent) => skillInstallPath(projectRoot, agent))
125
127
  .filter((p) => Boolean(p));
126
128
  }
127
- async function installAgentSkill(projectRoot) {
129
+ async function installAgentSkill(projectRoot, projectKind) {
128
130
  const targets = allSkillInstallPaths(projectRoot);
129
131
  if (targets.length === 0)
130
132
  return;
@@ -137,7 +139,11 @@ async function installAgentSkill(projectRoot) {
137
139
  : target.includes("/.codex/")
138
140
  ? "codex"
139
141
  : "openclaw";
140
- 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
+ });
141
147
  await (0, fs_1.writeTextFileEnsuredDir)(target, content);
142
148
  }
143
149
  }
@@ -219,19 +225,20 @@ async function installOpsxConfig(projectRoot, agent) {
219
225
  }
220
226
  async function setupProject(options) {
221
227
  var _a, _b, _c;
228
+ const installRoot = (0, projectRoots_1.resolveInstallRoot)(options.projectRoot);
222
229
  const defaultAgent = (_a = options.agent) !== null && _a !== void 0 ? _a : "claude-code";
223
230
  const selectedAgent = options.yes ? defaultAgent : await askAgent(defaultAgent);
224
231
  const defaultKind = (_b = options.projectKind) !== null && _b !== void 0 ? _b : "OMS";
225
232
  const selectedKind = options.yes ? defaultKind : await askProjectKind(defaultKind);
226
- const defaultBranch = (_c = options.branch) !== null && _c !== void 0 ? _c : pickDefaultBranch(options.projectRoot);
233
+ const defaultBranch = (_c = options.branch) !== null && _c !== void 0 ? _c : pickDefaultBranch(installRoot);
227
234
  const selectedBranch = options.yes ? defaultBranch : await askBranch(defaultBranch);
228
- const plannedActions = [];
229
- const branches = listLocalBranches(options.projectRoot);
235
+ const plannedActions = [`installRoot=${installRoot}`];
236
+ const branches = listLocalBranches(installRoot);
230
237
  const branchExists = branches.includes(selectedBranch);
231
238
  let branchResult;
232
- if (options.light) {
239
+ if (options.light || options.skipGit) {
233
240
  branchResult = { branch: selectedBranch, created: false };
234
- plannedActions.push("light mode: skipped git branch checkout");
241
+ plannedActions.push(options.skipGit ? "skipGit: skipped git branch checkout" : "light mode: skipped git branch checkout");
235
242
  }
236
243
  else if (options.dryRun) {
237
244
  branchResult = { branch: selectedBranch, created: !branchExists };
@@ -240,9 +247,9 @@ async function setupProject(options) {
240
247
  : `would create and checkout branch: ${selectedBranch}`);
241
248
  }
242
249
  else {
243
- branchResult = ensureBranch(options.projectRoot, selectedBranch);
250
+ branchResult = ensureBranch(installRoot, selectedBranch);
244
251
  }
245
- let environmentReady = await (0, opsxDelivery_1.isOpsxApplyReady)(options.projectRoot);
252
+ let environmentReady = await (0, opsxDelivery_1.isOpsxApplyReady)(installRoot);
246
253
  let setupExecuted = false;
247
254
  let skillInstalled = false;
248
255
  let docsSkillInstalled = false;
@@ -255,7 +262,7 @@ async function setupProject(options) {
255
262
  }
256
263
  else if (!environmentReady) {
257
264
  const ensured = await (0, opsxDelivery_1.ensureOpsxWorkflow)({
258
- projectRoot: options.projectRoot,
265
+ projectRoot: installRoot,
259
266
  agent: selectedAgent,
260
267
  dryRun: options.dryRun,
261
268
  });
@@ -267,23 +274,28 @@ async function setupProject(options) {
267
274
  plannedActions.push("opsx apply stack already ready");
268
275
  }
269
276
  if (options.dryRun) {
270
- for (const skillPath of allSkillInstallPaths(options.projectRoot)) {
277
+ for (const skillPath of allSkillInstallPaths(installRoot)) {
271
278
  plannedActions.push(`would install skill: ${skillPath}`);
272
279
  }
273
280
  const { dir } = docSkillDirName(selectedKind);
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")}`);
281
+ plannedActions.push(`would install docs skill: ${path_1.default.join(installRoot, "docs", dir)}`);
282
+ plannedActions.push(`would install opsx config: ${path_1.default.join(installRoot, ".opsx", "config.json")}`);
283
+ plannedActions.push(`would install cursor rule: ${path_1.default.join(installRoot, ".cursor", "rules", "sdd-flow-kit-gates.mdc")}`);
277
284
  }
278
285
  else {
279
- await installAgentSkill(options.projectRoot);
280
- await installSddCursorRule(options.projectRoot);
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);
292
+ await installSddCursorRule(installRoot);
281
293
  skillInstalled = true;
282
- await installDocsSkill(options.projectRoot, selectedKind);
294
+ await installDocsSkill(installRoot, selectedKind);
283
295
  docsSkillInstalled = true;
284
- await installOpsxConfig(options.projectRoot, selectedAgent);
296
+ await installOpsxConfig(installRoot, selectedAgent);
285
297
  opsxConfigInstalled = true;
286
- const repaired = await (0, opsxDelivery_1.repairOpsxDeliveryScripts)(options.projectRoot);
298
+ const repaired = await (0, opsxDelivery_1.repairOpsxDeliveryScripts)(pkgRoot);
287
299
  if (repaired) {
288
300
  plannedActions.push("repaired package.json opsx:delivery-pipeline scripts (poquan → opsx)");
289
301
  }
@@ -307,7 +319,7 @@ async function setupProject(options) {
307
319
  ...plannedActions.map((x) => `- ${x}`),
308
320
  "",
309
321
  ].join("\n");
310
- await (0, fs_1.writeTextFileEnsuredDir)(path_1.default.join(options.projectRoot, ".sdd-flow-kit", "setup-summary.md"), summary);
322
+ await (0, fs_1.writeTextFileEnsuredDir)(path_1.default.join(installRoot, ".sdd-flow-kit", "setup-summary.md"), summary);
311
323
  return {
312
324
  agent: selectedAgent,
313
325
  projectKind: selectedKind,
@@ -11,6 +11,7 @@ const fs_1 = require("../core/fs");
11
11
  const templates_1 = require("../core/templates");
12
12
  const inferProject_1 = require("../core/inferProject");
13
13
  const sessionState_1 = require("../core/sessionState");
14
+ const projectRoots_1 = require("../core/projectRoots");
14
15
  const writeNext_1 = require("../core/writeNext");
15
16
  function renderTemplate(template, vars) {
16
17
  let out = template;
@@ -22,7 +23,8 @@ function renderTemplate(template, vars) {
22
23
  async function startFlowScaffold(options) {
23
24
  const now = new Date();
24
25
  const runId = `${(0, utils_1.formatYYYYMMDD)(now)}-${options.product}-${options.version}`;
25
- const outputRoot = path_1.default.join(options.projectRoot, "openspec", "PRD", runId);
26
+ const installRoot = (0, projectRoots_1.resolveInstallRoot)(options.projectRoot);
27
+ const outputRoot = path_1.default.join(installRoot, "openspec", "PRD", runId);
26
28
  const sourceDir = path_1.default.join(outputRoot, "source");
27
29
  await promises_1.default.mkdir(sourceDir, { recursive: true });
28
30
  // 1) Step1: doc-skill 指引(按产品解析脚本路径,支持 monorepo frontend)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sdd-flow-kit",
3
- "version": "1.1.0",
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",