sdd-flow-kit 1.0.25 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -11,6 +11,23 @@
11
11
  软件架构说明
12
12
 
13
13
 
14
+ #### Node 版本(14.16 / 18 / 20 均可)
15
+
16
+ | 组件 | 要求 |
17
+ |------|------|
18
+ | **sdd-flow-kit** CLI(`install` / `run` / `doctor` / `postinstall`) | **Node >= 14.16.0**(`engines` 已声明;兼容老 CI/内网机) |
19
+ | **openspec** CLI、`opsx-workflow` | 以各自包文档为准;建议 **18+** |
20
+ | 目标业务仓(如 adInsight `frontend`) | 按该仓 `package.json` engines,与 kit 独立 |
21
+
22
+ 安装后建议执行:
23
+
24
+ ```bash
25
+ node -v # 应 >= v14.16.0
26
+ npx sdd-flow-kit doctor --agent cursor
27
+ ```
28
+
29
+ 若 `doctor` 报 `node-version` 失败,请升级到 14.16+,或在该仓使用 `.nvmrc` 锁定版本。
30
+
14
31
  #### 安装教程
15
32
 
16
33
  1. 进入你要开发的项目目录
@@ -56,14 +73,36 @@
56
73
 
57
74
  #### 使用说明
58
75
 
59
- ##### 需求澄清:用户无需背提示词
76
+ ##### 脚本门禁(v1.1+,**不依赖自然语言**)
77
+
78
+ `run` 会在 `openspec/PRD/<runId>/NEXT.md` 写入**必须按顺序执行的命令**。AI 与人都以 **exit code** 为准:失败则不得进入下一阶段、不得改 `src/`。
60
79
 
61
- `install` 会写入 `.cursor/rules/sdd-flow-kit-gates.mdc` 与触发 Skill。
62
- 澄清阶段你**直接粘贴确认点**(编号列表即可),AI 先进入文档模式更新 `openspec/PRD/**`。
63
- **默认**:03 闭环并生成 01/02/04 后,**同一回合自动** `/opsx-propose`(01+02+UI)→ `/opsx-apply`,无需再说「继续」。
64
- 说「只提案」或「先别写代码」可停在提案阶段;设 `autoChainOpsx: false` 可恢复人工分步。
80
+ ```bash
81
+ # 门禁(失败 exit 1)
82
+ npx sdd-flow-kit gate --project-root . --run-id <runId> --expect prd-fetched
83
+ npx sdd-flow-kit gate --project-root . --run-id <runId> --expect docs-closed # 检测 git 是否误改 src
84
+ npx sdd-flow-kit gate --project-root . --run-id <runId> --expect propose-ready --change <name>
85
+ npx sdd-flow-kit gate --project-root . --run-id <runId> --expect impl-allowed --change <name>
86
+ npx sdd-flow-kit gate --project-root . --run-id <runId> --expect shipped --change <name>
87
+
88
+ # 阶段推进(更新 .session-state.json + NEXT.md)
89
+ npx sdd-flow-kit phase advance --project-root . --run-id <runId> --to after-prd
90
+ npx sdd-flow-kit phase advance --project-root . --run-id <runId> --to docs-done
91
+ npx sdd-flow-kit phase advance --project-root . --run-id <runId> --to propose-done --change <name>
92
+ npx sdd-flow-kit phase advance --project-root . --run-id <runId> --to impl --change <name>
93
+ npx sdd-flow-kit phase advance --project-root . --run-id <runId> --to shipped --change <name>
94
+
95
+ # 提案 / 交付
96
+ npx sdd-flow-kit propose --project-root . --run-id <runId> --change <name>
97
+ npx sdd-flow-kit deliver --project-root . --run-id <runId> --change <name>
98
+ ```
99
+
100
+ `docs-closed` 会在 git 仓库中扫描 `src/`、`frontend/src/` 等路径;文档阶段若已有实现层改动,**gate 失败**。
101
+
102
+ ##### 需求澄清:用户无需背提示词
65
103
 
66
- 阶段口语(可选,用于断点续跑):`继续` / `做提案` / `开始 apply`。
104
+ `install` 会写入 `.cursor/rules/sdd-flow-kit-gates.mdc` 与触发 Skill(已改为**脚本优先**)。
105
+ 用户粘贴确认点后:先更新 01/02/03/04,再执行 `gate docs-closed` → `phase advance --to docs-done`;**禁止**跳过 gate 直接改代码。
67
106
 
68
107
  ##### `/opsx-apply` 与 TDD + Playwright 检查机制
69
108
 
@@ -88,9 +127,10 @@ npx sdd-flow-kit doctor --agent cursor --fix # 检查并尝试修复
88
127
 
89
128
  #### 当前自动化边界
90
129
 
91
- - 已支持:跨工具统一目录结构、提示词、报告模板、执行清单
92
- - 已支持:环境检测与按工具自动执行 `npx @lixin5257xxx/opsx-workflow setup ...`
93
- - 自动化增强:`claude-code` adapter 支持 `claude -p` 自动执行并记录 `AUTO-RUN-LOG.md`(失败自动降级人工)
130
+ - 已支持:跨工具统一目录结构、提示词、报告模板、**NEXT.md 脚本清单**
131
+ - 已支持:**gate / phase / propose / deliver** 命令(exit code 机械门禁)
132
+ - 已支持:`ensure-opsx` + `openspec status` 校验 apply-ready
133
+ - 未支持:在 Cursor 内全自动跑完全流程(Cursor adapter 仍为 manual);可选 `SDD_FLOW_KIT_ENABLE_CLAUDE_AUTORUN=1` 给 Claude Code
94
134
 
95
135
  #### npm 发包流程
96
136
 
@@ -4,12 +4,12 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  };
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.getAdapter = getAdapter;
7
- const node_child_process_1 = require("node:child_process");
8
- const node_path_1 = __importDefault(require("node:path"));
9
- const node_process_1 = __importDefault(require("node:process"));
7
+ const child_process_1 = require("child_process");
8
+ const path_1 = __importDefault(require("path"));
9
+ const process_1 = __importDefault(require("process"));
10
10
  const fs_1 = require("../core/fs");
11
11
  function hasCommand(cmd) {
12
- const result = (0, node_child_process_1.spawnSync)("which", [cmd], { stdio: "ignore" });
12
+ const result = (0, child_process_1.spawnSync)("which", [cmd], { stdio: "ignore" });
13
13
  return result.status === 0;
14
14
  }
15
15
  function createManualAdapter(name, displayName, bins) {
@@ -25,15 +25,16 @@ function createManualAdapter(name, displayName, bins) {
25
25
  };
26
26
  }
27
27
  async function runClaudeFlow(args) {
28
+ var _a;
28
29
  // 默认关闭“二次调用 claude”自动执行,避免在 AI 会话内递归触发导致静默挂起。
29
30
  // 仅在显式开启时执行:SDD_FLOW_KIT_ENABLE_CLAUDE_AUTORUN=1
30
- if (node_process_1.default.env.SDD_FLOW_KIT_ENABLE_CLAUDE_AUTORUN !== "1") {
31
+ if (process_1.default.env.SDD_FLOW_KIT_ENABLE_CLAUDE_AUTORUN !== "1") {
31
32
  return false;
32
33
  }
33
34
  const hasClaude = hasCommand("claude");
34
35
  if (!hasClaude)
35
36
  return false;
36
- const sddPrompt = await (0, fs_1.readFileIfExists)(node_path_1.default.join(args.outputRoot, "02-需求分析提示词.md"));
37
+ const sddPrompt = await (0, fs_1.readFileIfExists)(path_1.default.join(args.outputRoot, "02-需求分析提示词.md"));
37
38
  if (!sddPrompt)
38
39
  return false;
39
40
  const prompt = [
@@ -49,16 +50,16 @@ async function runClaudeFlow(args) {
49
50
  "以下是第2步提示词:",
50
51
  sddPrompt,
51
52
  ].join("\n");
52
- const commandResult = (0, node_child_process_1.spawnSync)("claude", ["-p", prompt], {
53
+ const commandResult = (0, child_process_1.spawnSync)("claude", ["-p", prompt], {
53
54
  cwd: args.outputRoot,
54
55
  encoding: "utf8",
55
- env: node_process_1.default.env,
56
+ env: process_1.default.env,
56
57
  });
57
- await (0, fs_1.writeTextFileEnsuredDir)(node_path_1.default.join(args.outputRoot, "AUTO-RUN-LOG.md"), [
58
+ await (0, fs_1.writeTextFileEnsuredDir)(path_1.default.join(args.outputRoot, "AUTO-RUN-LOG.md"), [
58
59
  "# AUTO-RUN-LOG",
59
60
  "",
60
61
  `agent: claude-code`,
61
- `status: ${commandResult.status ?? "unknown"}`,
62
+ `status: ${(_a = commandResult.status) !== null && _a !== void 0 ? _a : "unknown"}`,
62
63
  "",
63
64
  "## stdout",
64
65
  commandResult.stdout || "",
package/dist/cli.js CHANGED
@@ -4,20 +4,24 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
4
4
  return (mod && mod.__esModule) ? mod : { "default": mod };
5
5
  };
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
- const node_path_1 = __importDefault(require("node:path"));
8
- const node_process_1 = __importDefault(require("node:process"));
7
+ const path_1 = __importDefault(require("path"));
8
+ const process_1 = __importDefault(require("process"));
9
9
  const startFlow_1 = require("./steps/startFlow");
10
10
  const runFlow_1 = require("./steps/runFlow");
11
11
  const utils_1 = require("./core/utils");
12
12
  const setupProject_1 = require("./steps/setupProject");
13
13
  const doctor_1 = require("./steps/doctor");
14
14
  const invokeFlow_1 = require("./steps/invokeFlow");
15
+ const runGate_1 = require("./steps/runGate");
16
+ const runPhaseAdvance_1 = require("./steps/runPhaseAdvance");
17
+ const runPropose_1 = require("./steps/runPropose");
18
+ const runDeliver_1 = require("./steps/runDeliver");
15
19
  const ALLOWED_AGENTS = ["cursor", "claude-code", "codex", "openclaw", "custom"];
16
20
  function getArgValue(flag) {
17
- const idx = node_process_1.default.argv.indexOf(flag);
21
+ const idx = process_1.default.argv.indexOf(flag);
18
22
  if (idx === -1)
19
23
  return null;
20
- const v = node_process_1.default.argv[idx + 1];
24
+ const v = process_1.default.argv[idx + 1];
21
25
  if (!v)
22
26
  return null;
23
27
  return v;
@@ -45,7 +49,18 @@ function printHelp() {
45
49
  "",
46
50
  "6) Apply 前确保 OPSX+TDD+Playwright(/opsx-apply 前必须):",
47
51
  " npx sdd-flow-kit ensure-opsx --agent cursor -y",
48
- " (缺省时自动 npx @lixin5257xxx/opsx-workflow setup)",
52
+ "",
53
+ "7) 脚本门禁(失败 exit 1,不依赖自然语言):",
54
+ " npx sdd-flow-kit gate --project-root . --run-id <runId> --expect prd-fetched",
55
+ " npx sdd-flow-kit gate --project-root . --run-id <runId> --expect docs-closed",
56
+ " npx sdd-flow-kit gate --project-root . --run-id <runId> --expect impl-allowed --change <name>",
57
+ "",
58
+ "8) 阶段推进(更新 session + NEXT.md):",
59
+ " npx sdd-flow-kit phase advance --project-root . --run-id <runId> --to after-prd|docs-done|propose-done|impl|shipped",
60
+ "",
61
+ "9) 提案 / 交付:",
62
+ " npx sdd-flow-kit propose --project-root . --run-id <runId> --change <name>",
63
+ " npx sdd-flow-kit deliver --project-root . --run-id <runId> --change <name>",
49
64
  "",
50
65
  "可选参数:",
51
66
  " --projectRoot <path> 目标项目根目录(默认当前目录)",
@@ -61,36 +76,58 @@ function printHelp() {
61
76
  ].join("\n"));
62
77
  }
63
78
  async function main() {
64
- const cmd = node_process_1.default.argv[2];
79
+ var _a, _b, _c, _d, _e, _f, _g, _h;
80
+ const cmd = process_1.default.argv[2];
65
81
  if (!cmd || cmd === "--help" || cmd === "-h") {
66
82
  printHelp();
67
83
  return;
68
84
  }
85
+ const GATE_EXPECTS = [
86
+ "prd-fetched",
87
+ "questions-open",
88
+ "docs-closed",
89
+ "propose-ready",
90
+ "impl-allowed",
91
+ "shipped",
92
+ ];
93
+ const PHASE_TARGETS = [
94
+ "after-prd",
95
+ "docs-done",
96
+ "propose-done",
97
+ "impl",
98
+ "shipped",
99
+ ];
69
100
  if (cmd !== "start" &&
70
101
  cmd !== "run" &&
71
102
  cmd !== "setup" &&
72
103
  cmd !== "install" &&
73
104
  cmd !== "doctor" &&
74
105
  cmd !== "ensure-opsx" &&
75
- cmd !== "invoke") {
106
+ cmd !== "invoke" &&
107
+ cmd !== "gate" &&
108
+ cmd !== "phase" &&
109
+ cmd !== "propose" &&
110
+ cmd !== "deliver") {
76
111
  printHelp();
77
- node_process_1.default.exitCode = 1;
112
+ process_1.default.exitCode = 1;
78
113
  return;
79
114
  }
80
115
  const product = getArgValue("--product");
81
116
  const version = getArgValue("--version");
82
- const projectRoot = getArgValue("--projectRoot") ?? node_process_1.default.cwd();
83
- const branch = getArgValue("--branch") ?? undefined;
84
- const yes = node_process_1.default.argv.includes("-y");
85
- const dryRun = node_process_1.default.argv.includes("--dry-run");
86
- const planJson = node_process_1.default.argv.includes("--plan-json");
87
- const agentValue = getArgValue("--agent") ?? "claude-code";
88
- const projectKind = (getArgValue("--project") ?? "OMS");
117
+ const projectRoot = path_1.default.resolve((_b = (_a = getArgValue("--project-root")) !== null && _a !== void 0 ? _a : getArgValue("--projectRoot")) !== null && _b !== void 0 ? _b : process_1.default.cwd());
118
+ const runId = (_d = (_c = getArgValue("--run-id")) !== null && _c !== void 0 ? _c : getArgValue("--runId")) !== null && _d !== void 0 ? _d : undefined;
119
+ const changeName = (_e = getArgValue("--change")) !== null && _e !== void 0 ? _e : undefined;
120
+ const branch = (_f = getArgValue("--branch")) !== null && _f !== void 0 ? _f : undefined;
121
+ const yes = process_1.default.argv.includes("-y");
122
+ const dryRun = process_1.default.argv.includes("--dry-run");
123
+ const planJson = process_1.default.argv.includes("--plan-json");
124
+ const agentValue = (_g = getArgValue("--agent")) !== null && _g !== void 0 ? _g : "claude-code";
125
+ const projectKind = ((_h = getArgValue("--project")) !== null && _h !== void 0 ? _h : "OMS");
89
126
  if (!ALLOWED_AGENTS.includes(agentValue)) {
90
127
  // eslint-disable-next-line no-console
91
128
  console.error(`不支持的 --agent: ${agentValue}`);
92
129
  printHelp();
93
- node_process_1.default.exitCode = 1;
130
+ process_1.default.exitCode = 1;
94
131
  return;
95
132
  }
96
133
  const agent = agentValue;
@@ -98,13 +135,99 @@ async function main() {
98
135
  if (!allowedProjects.includes(projectKind)) {
99
136
  // eslint-disable-next-line no-console
100
137
  console.error(`不支持的 --project: ${projectKind}(可选:${allowedProjects.join(" / ")})`);
101
- node_process_1.default.exitCode = 1;
138
+ process_1.default.exitCode = 1;
139
+ return;
140
+ }
141
+ if (cmd === "gate") {
142
+ const expect = getArgValue("--expect");
143
+ if (!expect || !GATE_EXPECTS.includes(expect)) {
144
+ // eslint-disable-next-line no-console
145
+ console.error(`gate 需要 --expect ${GATE_EXPECTS.join("|")}`);
146
+ process_1.default.exitCode = 1;
147
+ return;
148
+ }
149
+ const result = await (0, runGate_1.runGate)({
150
+ projectRoot,
151
+ runId,
152
+ expect,
153
+ change: changeName,
154
+ });
155
+ (0, runGate_1.printGateResult)(result);
156
+ if (!result.ok)
157
+ process_1.default.exitCode = 1;
158
+ return;
159
+ }
160
+ if (cmd === "phase") {
161
+ const sub = process_1.default.argv[3];
162
+ if (sub !== "advance") {
163
+ // eslint-disable-next-line no-console
164
+ console.error("用法: sdd-flow-kit phase advance --to <target> ...");
165
+ process_1.default.exitCode = 1;
166
+ return;
167
+ }
168
+ const to = getArgValue("--to");
169
+ if (!to || !PHASE_TARGETS.includes(to)) {
170
+ // eslint-disable-next-line no-console
171
+ console.error(`需要 --to ${PHASE_TARGETS.join("|")}`);
172
+ process_1.default.exitCode = 1;
173
+ return;
174
+ }
175
+ const result = await (0, runPhaseAdvance_1.runPhaseAdvance)({
176
+ projectRoot,
177
+ runId,
178
+ to,
179
+ change: changeName,
180
+ dryRun,
181
+ });
182
+ // eslint-disable-next-line no-console
183
+ console.log(result.ok ? `✅ ${result.message}` : `❌ ${result.message}`);
184
+ if (!result.ok)
185
+ process_1.default.exitCode = 1;
186
+ return;
187
+ }
188
+ if (cmd === "propose") {
189
+ if (!changeName) {
190
+ // eslint-disable-next-line no-console
191
+ console.error("propose 需要 --change <kebab-name>");
192
+ process_1.default.exitCode = 1;
193
+ return;
194
+ }
195
+ const result = await (0, runPropose_1.runPropose)({
196
+ projectRoot,
197
+ runId,
198
+ change: changeName,
199
+ dryRun,
200
+ });
201
+ // eslint-disable-next-line no-console
202
+ console.log(result.ok ? `✅ ${result.detail}` : `❌ ${result.detail}`);
203
+ if (!result.ok)
204
+ process_1.default.exitCode = 1;
205
+ return;
206
+ }
207
+ if (cmd === "deliver") {
208
+ if (!changeName) {
209
+ // eslint-disable-next-line no-console
210
+ console.error("deliver 需要 --change <kebab-name>");
211
+ process_1.default.exitCode = 1;
212
+ return;
213
+ }
214
+ const result = await (0, runDeliver_1.runDeliver)({
215
+ projectRoot,
216
+ runId,
217
+ change: changeName,
218
+ agent,
219
+ dryRun,
220
+ });
221
+ // eslint-disable-next-line no-console
222
+ console.log(result.ok ? `✅ ${result.detail}` : `❌ ${result.detail}`);
223
+ if (!result.ok)
224
+ process_1.default.exitCode = 1;
102
225
  return;
103
226
  }
104
227
  if (cmd === "doctor" || cmd === "ensure-opsx") {
105
- const fix = cmd === "ensure-opsx" || node_process_1.default.argv.includes("--fix");
228
+ const fix = cmd === "ensure-opsx" || process_1.default.argv.includes("--fix");
106
229
  const doctor = await (0, doctor_1.runDoctor)({
107
- projectRoot: node_path_1.default.resolve(projectRoot),
230
+ projectRoot,
108
231
  agent,
109
232
  fix,
110
233
  dryRun,
@@ -117,11 +240,11 @@ async function main() {
117
240
  }
118
241
  if (planJson) {
119
242
  // eslint-disable-next-line no-console
120
- console.log(JSON.stringify({ command: "doctor", projectRoot: node_path_1.default.resolve(projectRoot), result: doctor }, null, 2));
243
+ console.log(JSON.stringify({ command: "doctor", projectRoot: path_1.default.resolve(projectRoot), result: doctor }, null, 2));
121
244
  }
122
245
  if (cmd === "ensure-opsx" && !dryRun) {
123
246
  await (0, setupProject_1.setupProject)({
124
- projectRoot: node_path_1.default.resolve(projectRoot),
247
+ projectRoot,
125
248
  agent,
126
249
  yes: true,
127
250
  light: true,
@@ -129,12 +252,12 @@ async function main() {
129
252
  });
130
253
  }
131
254
  if (!doctor.ok)
132
- node_process_1.default.exitCode = 1;
255
+ process_1.default.exitCode = 1;
133
256
  return;
134
257
  }
135
258
  if (cmd === "setup" || cmd === "install") {
136
259
  const result = await (0, setupProject_1.setupProject)({
137
- projectRoot: node_path_1.default.resolve(projectRoot),
260
+ projectRoot,
138
261
  agent,
139
262
  branch,
140
263
  yes,
@@ -151,7 +274,7 @@ async function main() {
151
274
  // eslint-disable-next-line no-console
152
275
  console.log(JSON.stringify({
153
276
  command: cmd,
154
- projectRoot: node_path_1.default.resolve(projectRoot),
277
+ projectRoot: path_1.default.resolve(projectRoot),
155
278
  dryRun,
156
279
  result,
157
280
  }, null, 2));
@@ -160,21 +283,20 @@ async function main() {
160
283
  }
161
284
  if (!product || !version) {
162
285
  if (cmd === "invoke") {
163
- const raw = node_process_1.default.argv
286
+ const raw = process_1.default.argv
164
287
  .slice(3)
165
288
  .filter((token) => !token.startsWith("--") && token !== "-y")
166
289
  .join(" ")
167
290
  .trim();
168
291
  const fromFlag = getArgValue("--text");
169
- const text = (fromFlag ?? raw).trim();
292
+ const text = (fromFlag !== null && fromFlag !== void 0 ? fromFlag : raw).trim();
170
293
  const parsed = (0, invokeFlow_1.parseInvokeText)(text);
171
294
  if (!parsed) {
172
295
  throw new Error("invoke 解析失败,请使用:开发 ADI 2.3.2 需求");
173
296
  }
174
- const resolvedRoot = node_path_1.default.resolve(projectRoot);
175
297
  const options = (0, invokeFlow_1.toRunOptions)({
176
298
  parsed,
177
- projectRoot: resolvedRoot,
299
+ projectRoot,
178
300
  branch,
179
301
  yes,
180
302
  dryRun,
@@ -190,25 +312,24 @@ async function main() {
190
312
  return;
191
313
  }
192
314
  printHelp();
193
- node_process_1.default.exitCode = 1;
315
+ process_1.default.exitCode = 1;
194
316
  return;
195
317
  }
196
- const resolvedRoot = node_path_1.default.resolve(projectRoot);
197
- const runId = `${(0, utils_1.formatYYYYMMDD)(new Date())}-${product}-${version}`;
318
+ const scaffoldRunId = `${(0, utils_1.formatYYYYMMDD)(new Date())}-${product}-${version}`;
198
319
  if (cmd === "start") {
199
320
  await (0, startFlow_1.startFlowScaffold)({
200
321
  product,
201
322
  version,
202
- projectRoot: resolvedRoot,
323
+ projectRoot,
203
324
  });
204
325
  // eslint-disable-next-line no-console
205
- console.log(`已生成 SDD 流程产物骨架:openspec/PRD/${runId}/`);
326
+ console.log(`已生成 SDD 流程产物骨架:openspec/PRD/${scaffoldRunId}/`);
206
327
  return;
207
328
  }
208
329
  const result = await (0, runFlow_1.runFlow)({
209
330
  product,
210
331
  version,
211
- projectRoot: resolvedRoot,
332
+ projectRoot,
212
333
  agent,
213
334
  branch,
214
335
  yes,
@@ -221,6 +342,8 @@ async function main() {
221
342
  // eslint-disable-next-line no-console
222
343
  console.log(`流程已初始化:openspec/PRD/${result.runId}/`);
223
344
  // eslint-disable-next-line no-console
345
+ console.log(`请打开 openspec/PRD/${result.runId}/NEXT.md,按脚本顺序执行 gate / phase / propose / deliver。`);
346
+ // eslint-disable-next-line no-console
224
347
  console.log(`agent=${result.adapterName}, cli=${result.adapterAvailable ? "detected" : "missing"}, autoExecute=${result.executed ? "yes" : "manual"}`);
225
348
  // eslint-disable-next-line no-console
226
349
  console.log(`branch=${result.branch}, envSetup=${result.setupExecuted ? "installed" : "already-ready"}`);
@@ -232,7 +355,7 @@ async function main() {
232
355
  // eslint-disable-next-line no-console
233
356
  console.log(JSON.stringify({
234
357
  command: "run",
235
- projectRoot: resolvedRoot,
358
+ projectRoot,
236
359
  dryRun,
237
360
  product,
238
361
  version,
@@ -243,5 +366,5 @@ async function main() {
243
366
  main().catch((e) => {
244
367
  // eslint-disable-next-line no-console
245
368
  console.error(e);
246
- node_process_1.default.exitCode = 1;
369
+ process_1.default.exitCode = 1;
247
370
  });
package/dist/core/fs.js CHANGED
@@ -5,8 +5,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
5
5
  Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.readFileIfExists = readFileIfExists;
7
7
  exports.writeTextFileEnsuredDir = writeTextFileEnsuredDir;
8
- const promises_1 = __importDefault(require("node:fs/promises"));
9
- const node_path_1 = __importDefault(require("node:path"));
8
+ const promises_1 = __importDefault(require("fs/promises"));
9
+ const path_1 = __importDefault(require("path"));
10
10
  async function readFileIfExists(filePath) {
11
11
  try {
12
12
  return await promises_1.default.readFile(filePath, "utf8");
@@ -22,6 +22,6 @@ async function readFileIfExists(filePath) {
22
22
  }
23
23
  }
24
24
  async function writeTextFileEnsuredDir(filePath, content) {
25
- await promises_1.default.mkdir(node_path_1.default.dirname(filePath), { recursive: true });
25
+ await promises_1.default.mkdir(path_1.default.dirname(filePath), { recursive: true });
26
26
  await promises_1.default.writeFile(filePath, content, "utf8");
27
27
  }
@@ -0,0 +1,83 @@
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.gitDiffNameOnly = gitDiffNameOnly;
7
+ exports.isImplPath = isImplPath;
8
+ exports.isAllowedDocPath = isAllowedDocPath;
9
+ exports.findImplChanges = findImplChanges;
10
+ exports.resolveGitRoot = resolveGitRoot;
11
+ const child_process_1 = require("child_process");
12
+ const path_1 = __importDefault(require("path"));
13
+ /**
14
+ * 在 projectRoot 执行 git 命令;非 git 仓库时 isRepo=false(不阻断文档阶段)。
15
+ */
16
+ function gitDiffNameOnly(projectRoot) {
17
+ const rev = (0, child_process_1.spawnSync)("git", ["rev-parse", "HEAD"], {
18
+ cwd: projectRoot,
19
+ encoding: "utf8",
20
+ });
21
+ if (rev.status !== 0) {
22
+ return { ok: true, isRepo: false, changedFiles: [], head: null };
23
+ }
24
+ const head = (rev.stdout || "").trim();
25
+ const diff = (0, child_process_1.spawnSync)("git", ["diff", "--name-only", "HEAD"], {
26
+ cwd: projectRoot,
27
+ encoding: "utf8",
28
+ });
29
+ const untracked = (0, child_process_1.spawnSync)("git", ["ls-files", "--others", "--exclude-standard"], {
30
+ cwd: projectRoot,
31
+ encoding: "utf8",
32
+ });
33
+ const changed = [
34
+ ...(diff.stdout || "").split("\n").filter(Boolean),
35
+ ...(untracked.stdout || "").split("\n").filter(Boolean),
36
+ ];
37
+ const unique = Array.from(new Set(changed));
38
+ return { ok: true, isRepo: true, changedFiles: unique, head };
39
+ }
40
+ /** 业务实现路径:命中则视为「实现层改动」 */
41
+ const IMPL_PATH_PATTERNS = [
42
+ /^src\//,
43
+ /^e2e\//,
44
+ /^frontend\/src\//,
45
+ /^frontend\/e2e\//,
46
+ /^app\//,
47
+ /^pages\//,
48
+ /^components\//,
49
+ ];
50
+ function isImplPath(filePath) {
51
+ const p = filePath.replace(/\\/g, "/");
52
+ return IMPL_PATH_PATTERNS.some((re) => re.test(p));
53
+ }
54
+ /** 仅允许 PRD / openspec 文档路径(阶段 B/C) */
55
+ const DOC_PATH_PATTERNS = [
56
+ /^openspec\/PRD\//,
57
+ /^openspec\/changes\//,
58
+ /^frontend\/openspec\/PRD\//,
59
+ /^frontend\/openspec\/changes\//,
60
+ /^\.opsx-active-change$/,
61
+ /^frontend\/\.opsx-active-change$/,
62
+ ];
63
+ function isAllowedDocPath(filePath) {
64
+ const p = filePath.replace(/\\/g, "/");
65
+ if (DOC_PATH_PATTERNS.some((re) => re.test(p)))
66
+ return true;
67
+ if (p.endsWith("docs/") || /^docs\//.test(p))
68
+ return true;
69
+ return false;
70
+ }
71
+ function findImplChanges(changedFiles) {
72
+ return changedFiles.filter((f) => isImplPath(f) && !isAllowedDocPath(f));
73
+ }
74
+ function resolveGitRoot(projectRoot) {
75
+ const top = (0, child_process_1.spawnSync)("git", ["rev-parse", "--show-toplevel"], {
76
+ cwd: projectRoot,
77
+ encoding: "utf8",
78
+ });
79
+ if (top.status === 0 && top.stdout) {
80
+ return top.stdout.trim();
81
+ }
82
+ return path_1.default.resolve(projectRoot);
83
+ }