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 +49 -9
- package/dist/adapters/index.js +11 -10
- package/dist/cli.js +159 -36
- package/dist/core/fs.js +3 -3
- package/dist/core/gitDiffGuard.js +83 -0
- package/dist/core/inferProject.js +22 -20
- package/dist/core/nodeCompat.js +35 -0
- package/dist/core/openspecGate.js +83 -0
- package/dist/core/opsxDelivery.js +26 -25
- package/dist/core/prdArtifacts.js +53 -0
- package/dist/core/readlinePrompt.js +51 -0
- package/dist/core/sessionState.js +55 -0
- package/dist/core/templates.js +4 -4
- package/dist/core/utils.js +8 -1
- package/dist/core/writeNext.js +54 -0
- package/dist/index.js +13 -1
- package/dist/postinstall.js +10 -9
- package/dist/steps/doctor.js +13 -6
- package/dist/steps/invokeFlow.js +2 -1
- package/dist/steps/resolveRun.js +61 -0
- package/dist/steps/runDeliver.js +82 -0
- package/dist/steps/runFlow.js +3 -3
- package/dist/steps/runGate.js +220 -0
- package/dist/steps/runPhaseAdvance.js +195 -0
- package/dist/steps/runPropose.js +82 -0
- package/dist/steps/setupProject.js +34 -31
- package/dist/steps/startFlow.js +27 -33
- package/package.json +4 -1
- package/src/templates/artifacts/06-agent-skill.template.md +49 -124
- package/src/templates/rules/sdd-no-implement-until-apply.mdc.template +32 -33
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
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
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
|
-
|
|
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
|
-
-
|
|
93
|
-
-
|
|
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
|
|
package/dist/adapters/index.js
CHANGED
|
@@ -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
|
|
8
|
-
const
|
|
9
|
-
const
|
|
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,
|
|
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 (
|
|
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)(
|
|
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,
|
|
53
|
+
const commandResult = (0, child_process_1.spawnSync)("claude", ["-p", prompt], {
|
|
53
54
|
cwd: args.outputRoot,
|
|
54
55
|
encoding: "utf8",
|
|
55
|
-
env:
|
|
56
|
+
env: process_1.default.env,
|
|
56
57
|
});
|
|
57
|
-
await (0, fs_1.writeTextFileEnsuredDir)(
|
|
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
|
|
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
|
|
8
|
-
const
|
|
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 =
|
|
21
|
+
const idx = process_1.default.argv.indexOf(flag);
|
|
18
22
|
if (idx === -1)
|
|
19
23
|
return null;
|
|
20
|
-
const v =
|
|
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
|
-
"
|
|
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
|
-
|
|
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
|
-
|
|
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")
|
|
83
|
-
const
|
|
84
|
-
const
|
|
85
|
-
const
|
|
86
|
-
const
|
|
87
|
-
const
|
|
88
|
-
const
|
|
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
|
-
|
|
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
|
-
|
|
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" ||
|
|
228
|
+
const fix = cmd === "ensure-opsx" || process_1.default.argv.includes("--fix");
|
|
106
229
|
const doctor = await (0, doctor_1.runDoctor)({
|
|
107
|
-
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:
|
|
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
|
|
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
|
-
|
|
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
|
|
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:
|
|
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 =
|
|
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
|
|
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
|
|
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
|
-
|
|
315
|
+
process_1.default.exitCode = 1;
|
|
194
316
|
return;
|
|
195
317
|
}
|
|
196
|
-
const
|
|
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
|
|
323
|
+
projectRoot,
|
|
203
324
|
});
|
|
204
325
|
// eslint-disable-next-line no-console
|
|
205
|
-
console.log(`已生成 SDD 流程产物骨架:openspec/PRD/${
|
|
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
|
|
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
|
|
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
|
-
|
|
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("
|
|
9
|
-
const
|
|
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(
|
|
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
|
+
}
|