sdd-flow-kit 1.0.14 → 1.0.18
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 +24 -3
- package/dist/cli.js +24 -4
- package/dist/core/opsxDelivery.js +248 -0
- package/dist/postinstall.js +49 -18
- package/dist/steps/doctor.js +24 -1
- package/dist/steps/setupProject.js +38 -72
- package/dist/steps/startFlow.js +4 -4
- package/package.json +1 -1
- package/src/templates/artifacts/06-agent-skill.template.md +26 -7
package/README.md
CHANGED
|
@@ -18,8 +18,9 @@
|
|
|
18
18
|
检查是否安装 - `ls .cursor/skills/sdd-flow-kit/SKILL.md`
|
|
19
19
|
- `ls .claude/skills/sdd-flow-kit/SKILL.md`
|
|
20
20
|
- `ls docs/*-doc-skill/SKILL.md`
|
|
21
|
-
3.
|
|
22
|
-
- `node ./node_modules/sdd-flow-kit/dist/postinstall.js
|
|
21
|
+
3. 若 skill 未生成,执行手动兜底:
|
|
22
|
+
- `node ./node_modules/sdd-flow-kit/dist/postinstall.js`(轻量,秒级)
|
|
23
|
+
- 或 `npx sdd-flow-kit install -y`(完整环境)
|
|
23
24
|
|
|
24
25
|
4. (可选)预演安装:`npx sdd-flow-kit install --dry-run --plan-json`
|
|
25
26
|
5. (可选)环境健检:`npx sdd-flow-kit doctor --agent claude-code`
|
|
@@ -44,7 +45,11 @@
|
|
|
44
45
|
|
|
45
46
|
团队推荐安装命令(稳妥):
|
|
46
47
|
|
|
47
|
-
`pnpm add -D sdd-flow-kit
|
|
48
|
+
`pnpm add -D sdd-flow-kit`
|
|
49
|
+
|
|
50
|
+
> `postinstall` 仅做**轻量**初始化(写入 skill / Cursor 规则 / docs-skill / `.opsx/config.json`),**不会**交互提问,也**不会**跑 `npx opsx-workflow`(避免 `pnpm i` 卡住)。
|
|
51
|
+
> 完整环境(分支切换 + opsx-workflow)请另执行:`npx sdd-flow-kit install -y`
|
|
52
|
+
> 跳过 postinstall:`SDD_FLOW_KIT_SKIP_POSTINSTALL=1 pnpm add -D sdd-flow-kit`
|
|
48
53
|
|
|
49
54
|
#### 使用说明
|
|
50
55
|
|
|
@@ -56,6 +61,22 @@
|
|
|
56
61
|
|
|
57
62
|
阶段口语(可选,非必须):`继续` / `做提案` → propose;`开始 apply` → 实现。
|
|
58
63
|
|
|
64
|
+
##### `/opsx-apply` 与 TDD + Playwright 检查机制
|
|
65
|
+
|
|
66
|
+
| 环节 | 实现 | 说明 |
|
|
67
|
+
|------|------|------|
|
|
68
|
+
| 安装时 | `postinstall`(light) | 只写 sdd skill/rule;**不**自动跑 opsx-workflow(避免 `pnpm i` 卡住) |
|
|
69
|
+
| 完整安装 | `npx sdd-flow-kit install -y` | 缺什么跑什么 → `ensureOpsxWorkflow` → `npx @lixin5257xxx/opsx-workflow setup` |
|
|
70
|
+
| Apply 前 | `npx sdd-flow-kit ensure-opsx -y` | **阶段 D 第 0 步**;doctor 加 `--fix` 等价修复 |
|
|
71
|
+
| Apply 时 | Cursor 读 skill 链 + `pnpm run opsx:delivery-pipeline` | 由 `AGENTS.md` / `06-agent-skill` 约束 AI,非 Node 自动执行 |
|
|
72
|
+
|
|
73
|
+
```bash
|
|
74
|
+
npx sdd-flow-kit ensure-opsx --agent cursor -y # /opsx-apply 前
|
|
75
|
+
npx sdd-flow-kit doctor --agent cursor --fix # 检查并尝试修复
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
常见遗漏:只 `pnpm add sdd-flow-kit` 未跑 `ensure-opsx`;或 AI 跳过 skill 直接改代码。
|
|
79
|
+
|
|
59
80
|
1. `install`(等价 `setup`):提示选择开发环境(cursor/claude-code/codex/openclaw,默认 claude-code)与开发分支(无则自动创建),并检测/安装 openspec+superpower 基础环境,同时写入项目级 Skill 与 Cursor 门禁规则
|
|
60
81
|
2. 安装完成后,本工具主要负责“流程环境与模板”落地;业务需求由你在目标 AI 工具中自然语言驱动
|
|
61
82
|
3. `start`/`run`/`invoke` 属于进阶能力(脚本化或一键编排时使用)
|
package/dist/cli.js
CHANGED
|
@@ -41,7 +41,11 @@ function printHelp() {
|
|
|
41
41
|
" npx sdd-flow-kit invoke \"开发 XXX 1.2.3 需求\"",
|
|
42
42
|
"",
|
|
43
43
|
"5) 环境健检:",
|
|
44
|
-
" npx sdd-flow-kit doctor --agent
|
|
44
|
+
" npx sdd-flow-kit doctor --agent cursor",
|
|
45
|
+
"",
|
|
46
|
+
"6) Apply 前确保 OPSX+TDD+Playwright(/opsx-apply 前必须):",
|
|
47
|
+
" npx sdd-flow-kit ensure-opsx --agent cursor -y",
|
|
48
|
+
" (缺省时自动 npx @lixin5257xxx/opsx-workflow setup)",
|
|
45
49
|
"",
|
|
46
50
|
"可选参数:",
|
|
47
51
|
" --projectRoot <path> 目标项目根目录(默认当前目录)",
|
|
@@ -67,6 +71,7 @@ async function main() {
|
|
|
67
71
|
cmd !== "setup" &&
|
|
68
72
|
cmd !== "install" &&
|
|
69
73
|
cmd !== "doctor" &&
|
|
74
|
+
cmd !== "ensure-opsx" &&
|
|
70
75
|
cmd !== "invoke") {
|
|
71
76
|
printHelp();
|
|
72
77
|
node_process_1.default.exitCode = 1;
|
|
@@ -96,10 +101,16 @@ async function main() {
|
|
|
96
101
|
node_process_1.default.exitCode = 1;
|
|
97
102
|
return;
|
|
98
103
|
}
|
|
99
|
-
if (cmd === "doctor") {
|
|
100
|
-
const
|
|
104
|
+
if (cmd === "doctor" || cmd === "ensure-opsx") {
|
|
105
|
+
const fix = cmd === "ensure-opsx" || node_process_1.default.argv.includes("--fix");
|
|
106
|
+
const doctor = await (0, doctor_1.runDoctor)({
|
|
107
|
+
projectRoot: node_path_1.default.resolve(projectRoot),
|
|
108
|
+
agent,
|
|
109
|
+
fix,
|
|
110
|
+
dryRun,
|
|
111
|
+
});
|
|
101
112
|
// eslint-disable-next-line no-console
|
|
102
|
-
console.log(
|
|
113
|
+
console.log(`${cmd}=${doctor.ok ? "ok" : "failed"}, agent=${agent}${fix ? ", fix=on" : ""}`);
|
|
103
114
|
for (const check of doctor.checks) {
|
|
104
115
|
// eslint-disable-next-line no-console
|
|
105
116
|
console.log(`[${check.ok ? "OK" : "FAIL"}] ${check.name}: ${check.detail}`);
|
|
@@ -108,6 +119,15 @@ async function main() {
|
|
|
108
119
|
// eslint-disable-next-line no-console
|
|
109
120
|
console.log(JSON.stringify({ command: "doctor", projectRoot: node_path_1.default.resolve(projectRoot), result: doctor }, null, 2));
|
|
110
121
|
}
|
|
122
|
+
if (cmd === "ensure-opsx" && !dryRun) {
|
|
123
|
+
await (0, setupProject_1.setupProject)({
|
|
124
|
+
projectRoot: node_path_1.default.resolve(projectRoot),
|
|
125
|
+
agent,
|
|
126
|
+
yes: true,
|
|
127
|
+
light: true,
|
|
128
|
+
projectKind: projectKind,
|
|
129
|
+
});
|
|
130
|
+
}
|
|
111
131
|
if (!doctor.ok)
|
|
112
132
|
node_process_1.default.exitCode = 1;
|
|
113
133
|
return;
|
|
@@ -0,0 +1,248 @@
|
|
|
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.deliveryPipelineNpmScripts = deliveryPipelineNpmScripts;
|
|
7
|
+
exports.pathExists = pathExists;
|
|
8
|
+
exports.resolveOpsxAgent = resolveOpsxAgent;
|
|
9
|
+
exports.repairOpsxDeliveryScripts = repairOpsxDeliveryScripts;
|
|
10
|
+
exports.buildOpsxWorkflowSetupCommand = buildOpsxWorkflowSetupCommand;
|
|
11
|
+
exports.isOpsxApplyReady = isOpsxApplyReady;
|
|
12
|
+
exports.ensureOpsxWorkflow = ensureOpsxWorkflow;
|
|
13
|
+
exports.collectDeliverySetupChecks = collectDeliverySetupChecks;
|
|
14
|
+
const node_child_process_1 = require("node:child_process");
|
|
15
|
+
const promises_1 = __importDefault(require("node:fs/promises"));
|
|
16
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
17
|
+
const fs_1 = require("./fs");
|
|
18
|
+
const DELIVERY_SCRIPT_REL = ".cursor/skills/opsx-dev-delivery-pipeline/scripts/opsx-delivery-pipeline.sh";
|
|
19
|
+
function deliveryPipelineNpmScripts() {
|
|
20
|
+
const full = `bash ${DELIVERY_SCRIPT_REL}`;
|
|
21
|
+
const short = `OPSX_PIPELINE_MODE=short ${full}`;
|
|
22
|
+
return { full, short };
|
|
23
|
+
}
|
|
24
|
+
async function pathExists(filePath) {
|
|
25
|
+
try {
|
|
26
|
+
await promises_1.default.access(filePath);
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
catch {
|
|
30
|
+
return false;
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
/** 优先使用已安装 OPSX skills 的 editor 目录 */
|
|
34
|
+
async function resolveOpsxAgent(projectRoot, preferred) {
|
|
35
|
+
const deliverySkill = node_path_1.default.join(projectRoot, ".cursor", "skills", "opsx-dev-delivery-pipeline", "SKILL.md");
|
|
36
|
+
if (await pathExists(deliverySkill))
|
|
37
|
+
return "cursor";
|
|
38
|
+
const claudeDelivery = node_path_1.default.join(projectRoot, ".claude", "skills", "opsx-dev-delivery-pipeline", "SKILL.md");
|
|
39
|
+
if (await pathExists(claudeDelivery))
|
|
40
|
+
return "claude-code";
|
|
41
|
+
if (await pathExists(node_path_1.default.join(projectRoot, ".cursor")))
|
|
42
|
+
return "cursor";
|
|
43
|
+
if (await pathExists(node_path_1.default.join(projectRoot, ".claude")))
|
|
44
|
+
return "claude-code";
|
|
45
|
+
return preferred;
|
|
46
|
+
}
|
|
47
|
+
/** 将 package.json 中过期的 poquan-delivery-pipeline 脚本修正为 opsx-dev-delivery-pipeline */
|
|
48
|
+
async function repairOpsxDeliveryScripts(projectRoot) {
|
|
49
|
+
const pkgPath = node_path_1.default.join(projectRoot, "package.json");
|
|
50
|
+
if (!(await pathExists(pkgPath)))
|
|
51
|
+
return false;
|
|
52
|
+
const scriptOnDisk = node_path_1.default.join(projectRoot, DELIVERY_SCRIPT_REL);
|
|
53
|
+
if (!(await pathExists(scriptOnDisk)))
|
|
54
|
+
return false;
|
|
55
|
+
const { full, short } = deliveryPipelineNpmScripts();
|
|
56
|
+
const text = await promises_1.default.readFile(pkgPath, "utf8");
|
|
57
|
+
const pkg = JSON.parse(text);
|
|
58
|
+
if (!pkg.scripts)
|
|
59
|
+
pkg.scripts = {};
|
|
60
|
+
let changed = false;
|
|
61
|
+
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
|
+
}
|
|
66
|
+
if (needsFix(pkg.scripts["opsx:delivery-pipeline:short"])) {
|
|
67
|
+
pkg.scripts["opsx:delivery-pipeline:short"] = short;
|
|
68
|
+
changed = true;
|
|
69
|
+
}
|
|
70
|
+
if (!changed)
|
|
71
|
+
return false;
|
|
72
|
+
await (0, fs_1.writeTextFileEnsuredDir)(pkgPath, `${JSON.stringify(pkg, null, 2)}\n`);
|
|
73
|
+
return true;
|
|
74
|
+
}
|
|
75
|
+
function hasCommand(cmd) {
|
|
76
|
+
return (0, node_child_process_1.spawnSync)("which", [cmd], { stdio: "ignore" }).status === 0;
|
|
77
|
+
}
|
|
78
|
+
function buildOpsxWorkflowSetupCommand(projectRoot, agent) {
|
|
79
|
+
const baseArgs = ["@lixin5257xxx/opsx-workflow", "setup"];
|
|
80
|
+
if (agent === "cursor") {
|
|
81
|
+
return {
|
|
82
|
+
cmd: "npx",
|
|
83
|
+
args: [...baseArgs, "--editor", "cursor", "--project", projectRoot, "-y"],
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
if (agent === "claude-code") {
|
|
87
|
+
return { cmd: "npx", args: [...baseArgs, "--editor", "claude-code", "-y"] };
|
|
88
|
+
}
|
|
89
|
+
if (agent === "openclaw") {
|
|
90
|
+
return { cmd: "npx", args: [...baseArgs, "--editor", "openclaw", "-y"] };
|
|
91
|
+
}
|
|
92
|
+
return { cmd: "npx", args: [...baseArgs, "--editor", "codex", "-y"] };
|
|
93
|
+
}
|
|
94
|
+
async function isOpsxApplyReady(projectRoot) {
|
|
95
|
+
const checks = await collectDeliverySetupChecks(projectRoot);
|
|
96
|
+
return checks.every((c) => c.ok);
|
|
97
|
+
}
|
|
98
|
+
/** 缺 openspec/TDD/Playwright 工作流时安装 @lixin5257xxx/opsx-workflow,并修复 delivery 脚本 */
|
|
99
|
+
async function ensureOpsxWorkflow(options) {
|
|
100
|
+
const plannedActions = [];
|
|
101
|
+
let checks = await collectDeliverySetupChecks(options.projectRoot);
|
|
102
|
+
if (checks.every((c) => c.ok)) {
|
|
103
|
+
return {
|
|
104
|
+
ready: true,
|
|
105
|
+
workflowInstalled: false,
|
|
106
|
+
repairedScripts: false,
|
|
107
|
+
checks,
|
|
108
|
+
plannedActions,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
const agent = await resolveOpsxAgent(options.projectRoot, options.agent);
|
|
112
|
+
const { cmd, args } = buildOpsxWorkflowSetupCommand(options.projectRoot, agent);
|
|
113
|
+
plannedActions.push(`missing opsx apply stack → run: ${cmd} ${args.join(" ")}`);
|
|
114
|
+
if (options.dryRun) {
|
|
115
|
+
return {
|
|
116
|
+
ready: false,
|
|
117
|
+
workflowInstalled: false,
|
|
118
|
+
repairedScripts: false,
|
|
119
|
+
checks,
|
|
120
|
+
plannedActions,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
const ret = (0, node_child_process_1.spawnSync)(cmd, args, {
|
|
124
|
+
cwd: options.projectRoot,
|
|
125
|
+
stdio: "inherit",
|
|
126
|
+
env: process.env,
|
|
127
|
+
});
|
|
128
|
+
if (ret.status !== 0) {
|
|
129
|
+
throw new Error(`opsx-workflow 安装失败:${cmd} ${args.join(" ")}`);
|
|
130
|
+
}
|
|
131
|
+
const repairedScripts = await repairOpsxDeliveryScripts(options.projectRoot);
|
|
132
|
+
if (repairedScripts) {
|
|
133
|
+
plannedActions.push("repaired package.json opsx:delivery-pipeline scripts");
|
|
134
|
+
}
|
|
135
|
+
checks = await collectDeliverySetupChecks(options.projectRoot);
|
|
136
|
+
return {
|
|
137
|
+
ready: checks.every((c) => c.ok),
|
|
138
|
+
workflowInstalled: true,
|
|
139
|
+
repairedScripts,
|
|
140
|
+
checks,
|
|
141
|
+
plannedActions,
|
|
142
|
+
};
|
|
143
|
+
}
|
|
144
|
+
async function collectDeliverySetupChecks(projectRoot) {
|
|
145
|
+
const checks = [];
|
|
146
|
+
checks.push({
|
|
147
|
+
name: "openspec-dir",
|
|
148
|
+
ok: await pathExists(node_path_1.default.join(projectRoot, "openspec")),
|
|
149
|
+
detail: "openspec/ 目录",
|
|
150
|
+
});
|
|
151
|
+
checks.push({
|
|
152
|
+
name: "openspec-cli",
|
|
153
|
+
ok: hasCommand("openspec"),
|
|
154
|
+
detail: "openspec CLI(npx openspec 或全局安装)",
|
|
155
|
+
});
|
|
156
|
+
const skillPath = node_path_1.default.join(projectRoot, ".cursor", "skills", "opsx-dev-delivery-pipeline", "SKILL.md");
|
|
157
|
+
checks.push({
|
|
158
|
+
name: "opsx-dev-delivery-pipeline-skill",
|
|
159
|
+
ok: await pathExists(skillPath),
|
|
160
|
+
detail: skillPath,
|
|
161
|
+
});
|
|
162
|
+
const tddPath = node_path_1.default.join(projectRoot, ".cursor", "skills", "tdd-script", "SKILL.md");
|
|
163
|
+
checks.push({
|
|
164
|
+
name: "tdd-script-skill",
|
|
165
|
+
ok: await pathExists(tddPath),
|
|
166
|
+
detail: tddPath,
|
|
167
|
+
});
|
|
168
|
+
const applyPath = node_path_1.default.join(projectRoot, ".cursor", "skills", "openspec-apply-change", "SKILL.md");
|
|
169
|
+
checks.push({
|
|
170
|
+
name: "openspec-apply-change-skill",
|
|
171
|
+
ok: await pathExists(applyPath),
|
|
172
|
+
detail: applyPath,
|
|
173
|
+
});
|
|
174
|
+
const pipelineScript = node_path_1.default.join(projectRoot, DELIVERY_SCRIPT_REL);
|
|
175
|
+
checks.push({
|
|
176
|
+
name: "delivery-pipeline-script",
|
|
177
|
+
ok: await pathExists(pipelineScript),
|
|
178
|
+
detail: pipelineScript,
|
|
179
|
+
});
|
|
180
|
+
try {
|
|
181
|
+
const pkgText = await promises_1.default.readFile(node_path_1.default.join(projectRoot, "package.json"), "utf8");
|
|
182
|
+
const pkg = JSON.parse(pkgText);
|
|
183
|
+
const cmd = pkg.scripts?.["opsx:delivery-pipeline"] ?? "";
|
|
184
|
+
const ok = cmd.includes("opsx-dev-delivery-pipeline") &&
|
|
185
|
+
cmd.includes("opsx-delivery-pipeline.sh") &&
|
|
186
|
+
!cmd.includes("poquan");
|
|
187
|
+
checks.push({
|
|
188
|
+
name: "package-json-delivery-script",
|
|
189
|
+
ok,
|
|
190
|
+
detail: ok ? cmd : `错误或缺失: ${cmd || "(empty)"}`,
|
|
191
|
+
});
|
|
192
|
+
}
|
|
193
|
+
catch {
|
|
194
|
+
checks.push({
|
|
195
|
+
name: "package-json-delivery-script",
|
|
196
|
+
ok: false,
|
|
197
|
+
detail: "无法读取 package.json",
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
try {
|
|
201
|
+
const cfgText = await promises_1.default.readFile(node_path_1.default.join(projectRoot, ".opsx", "config.json"), "utf8");
|
|
202
|
+
const cfg = JSON.parse(cfgText);
|
|
203
|
+
const skillsDir = cfg.skillsDir ?? "";
|
|
204
|
+
const hasCursorDelivery = await pathExists(node_path_1.default.join(projectRoot, ".cursor", "skills", "opsx-dev-delivery-pipeline", "SKILL.md"));
|
|
205
|
+
const nameOk = cfg.deliveryPipeline?.skillName === "opsx-dev-delivery-pipeline";
|
|
206
|
+
const dirOk = (hasCursorDelivery && skillsDir.includes(".cursor")) ||
|
|
207
|
+
(!hasCursorDelivery && (skillsDir.includes(".cursor") || skillsDir.includes(".claude")));
|
|
208
|
+
checks.push({
|
|
209
|
+
name: "opsx-config-skills-dir",
|
|
210
|
+
ok: nameOk && dirOk,
|
|
211
|
+
detail: hasCursorDelivery && !skillsDir.includes(".cursor")
|
|
212
|
+
? `skills 在 .cursor 但 config 指向 ${skillsDir},请 npx sdd-flow-kit install -y`
|
|
213
|
+
: `skillsDir=${skillsDir}, skillName=${cfg.deliveryPipeline?.skillName ?? ""}`,
|
|
214
|
+
});
|
|
215
|
+
}
|
|
216
|
+
catch {
|
|
217
|
+
checks.push({
|
|
218
|
+
name: "opsx-config",
|
|
219
|
+
ok: false,
|
|
220
|
+
detail: ".opsx/config.json 缺失",
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
try {
|
|
224
|
+
const pkgText = await promises_1.default.readFile(node_path_1.default.join(projectRoot, "package.json"), "utf8");
|
|
225
|
+
const pkg = JSON.parse(pkgText);
|
|
226
|
+
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
227
|
+
const hasPw = Boolean(deps["@playwright/test"] || deps.playwright);
|
|
228
|
+
checks.push({
|
|
229
|
+
name: "playwright-dependency",
|
|
230
|
+
ok: hasPw,
|
|
231
|
+
detail: hasPw ? "@playwright/test 或 playwright 已声明" : "package.json 缺少 Playwright 依赖",
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
catch {
|
|
235
|
+
checks.push({
|
|
236
|
+
name: "playwright-dependency",
|
|
237
|
+
ok: false,
|
|
238
|
+
detail: "无法读取 package.json 检查 Playwright",
|
|
239
|
+
});
|
|
240
|
+
}
|
|
241
|
+
const agentsMd = node_path_1.default.join(projectRoot, "AGENTS.md");
|
|
242
|
+
checks.push({
|
|
243
|
+
name: "agents-md",
|
|
244
|
+
ok: await pathExists(agentsMd),
|
|
245
|
+
detail: "AGENTS.md(Apply/TDD/pipeline 编排 SSOT)",
|
|
246
|
+
});
|
|
247
|
+
return checks;
|
|
248
|
+
}
|
package/dist/postinstall.js
CHANGED
|
@@ -6,15 +6,9 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
6
6
|
const node_path_1 = __importDefault(require("node:path"));
|
|
7
7
|
const promises_1 = __importDefault(require("node:fs/promises"));
|
|
8
8
|
const node_process_1 = __importDefault(require("node:process"));
|
|
9
|
+
const opsxDelivery_1 = require("./core/opsxDelivery");
|
|
9
10
|
const setupProject_1 = require("./steps/setupProject");
|
|
10
11
|
const ALLOWED_PROJECTS = ["OMS", "ADI", "欢盟", "AD Tools"];
|
|
11
|
-
function resolveProjectKindFromEnv() {
|
|
12
|
-
const raw = (node_process_1.default.env.SDD_FLOW_KIT_PROJECT ?? "").trim();
|
|
13
|
-
if (ALLOWED_PROJECTS.includes(raw)) {
|
|
14
|
-
return raw;
|
|
15
|
-
}
|
|
16
|
-
return "OMS";
|
|
17
|
-
}
|
|
18
12
|
async function inferProjectKind(projectRoot) {
|
|
19
13
|
const fromEnv = (node_process_1.default.env.SDD_FLOW_KIT_PROJECT ?? "").trim();
|
|
20
14
|
if (ALLOWED_PROJECTS.includes(fromEnv)) {
|
|
@@ -30,7 +24,6 @@ async function inferProjectKind(projectRoot) {
|
|
|
30
24
|
return "欢盟";
|
|
31
25
|
if (hit("ad-tools") || hit("adtools") || hit("tools"))
|
|
32
26
|
return "AD Tools";
|
|
33
|
-
// 尝试读取项目 package.json 的 name 做二次推断
|
|
34
27
|
try {
|
|
35
28
|
const pkgText = await promises_1.default.readFile(node_path_1.default.join(projectRoot, "package.json"), "utf8");
|
|
36
29
|
const pkg = JSON.parse(pkgText);
|
|
@@ -50,33 +43,71 @@ async function inferProjectKind(projectRoot) {
|
|
|
50
43
|
}
|
|
51
44
|
return "OMS";
|
|
52
45
|
}
|
|
46
|
+
async function inferAgent(projectRoot) {
|
|
47
|
+
const fromEnv = (node_process_1.default.env.SDD_FLOW_KIT_AGENT ?? "").trim();
|
|
48
|
+
const allowed = ["cursor", "claude-code", "codex", "openclaw"];
|
|
49
|
+
if (allowed.includes(fromEnv))
|
|
50
|
+
return fromEnv;
|
|
51
|
+
const exists = async (rel) => {
|
|
52
|
+
try {
|
|
53
|
+
await promises_1.default.access(node_path_1.default.join(projectRoot, rel));
|
|
54
|
+
return true;
|
|
55
|
+
}
|
|
56
|
+
catch {
|
|
57
|
+
return false;
|
|
58
|
+
}
|
|
59
|
+
};
|
|
60
|
+
if (await exists(".cursor"))
|
|
61
|
+
return "cursor";
|
|
62
|
+
if (await exists(".claude"))
|
|
63
|
+
return "claude-code";
|
|
64
|
+
if (await exists(".codex"))
|
|
65
|
+
return "codex";
|
|
66
|
+
if (await exists(".openclaw"))
|
|
67
|
+
return "openclaw";
|
|
68
|
+
return "cursor";
|
|
69
|
+
}
|
|
53
70
|
async function main() {
|
|
54
|
-
|
|
71
|
+
if (node_process_1.default.env.SDD_FLOW_KIT_SKIP_POSTINSTALL === "1") {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
55
74
|
const initCwd = node_process_1.default.env.INIT_CWD ? node_path_1.default.resolve(node_process_1.default.env.INIT_CWD) : null;
|
|
56
75
|
const pkgDir = node_path_1.default.resolve(__dirname, "..");
|
|
57
76
|
const projectRoot = initCwd ?? node_process_1.default.cwd();
|
|
58
|
-
// 避免在本包自身仓库开发安装依赖时重复触发自动初始化。
|
|
59
77
|
if (projectRoot === pkgDir)
|
|
60
78
|
return;
|
|
61
|
-
const isInteractive = Boolean(node_process_1.default.stdin.isTTY && node_process_1.default.stdout.isTTY);
|
|
62
79
|
const projectKind = await inferProjectKind(projectRoot);
|
|
80
|
+
const agent = await inferAgent(projectRoot);
|
|
63
81
|
// eslint-disable-next-line no-console
|
|
64
|
-
console.log(`[sdd-flow-kit] postinstall
|
|
82
|
+
console.log(`[sdd-flow-kit] postinstall (light) at: ${projectRoot}`);
|
|
65
83
|
// eslint-disable-next-line no-console
|
|
66
|
-
console.log(`[sdd-flow-kit]
|
|
84
|
+
console.log(`[sdd-flow-kit] agent=${agent}, project=${projectKind}`);
|
|
67
85
|
await (0, setupProject_1.setupProject)({
|
|
68
86
|
projectRoot,
|
|
69
|
-
agent
|
|
70
|
-
|
|
71
|
-
|
|
87
|
+
agent,
|
|
88
|
+
yes: true,
|
|
89
|
+
light: true,
|
|
72
90
|
dryRun: false,
|
|
73
91
|
projectKind,
|
|
74
92
|
});
|
|
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
|
+
const ready = await (0, opsxDelivery_1.isOpsxApplyReady)(projectRoot);
|
|
99
|
+
if (!ready) {
|
|
100
|
+
// eslint-disable-next-line no-console
|
|
101
|
+
console.warn("[sdd-flow-kit] OPSX/TDD/Playwright 工作流未就绪。/opsx-apply 前请执行: npx sdd-flow-kit ensure-opsx -y");
|
|
102
|
+
}
|
|
103
|
+
else {
|
|
104
|
+
// eslint-disable-next-line no-console
|
|
105
|
+
console.log("[sdd-flow-kit] opsx apply stack ready");
|
|
106
|
+
}
|
|
75
107
|
// eslint-disable-next-line no-console
|
|
76
|
-
console.log("[sdd-flow-kit] setup
|
|
108
|
+
console.log("[sdd-flow-kit] light setup done (skills/rules). Full install: npx sdd-flow-kit install -y");
|
|
77
109
|
}
|
|
78
110
|
main().catch((err) => {
|
|
79
|
-
// 不阻断依赖安装,仅告警
|
|
80
111
|
// eslint-disable-next-line no-console
|
|
81
112
|
console.warn("[sdd-flow-kit] postinstall setup skipped:", err instanceof Error ? err.message : err);
|
|
82
113
|
});
|
package/dist/steps/doctor.js
CHANGED
|
@@ -7,6 +7,7 @@ exports.runDoctor = runDoctor;
|
|
|
7
7
|
const node_child_process_1 = require("node:child_process");
|
|
8
8
|
const promises_1 = __importDefault(require("node:fs/promises"));
|
|
9
9
|
const node_path_1 = __importDefault(require("node:path"));
|
|
10
|
+
const opsxDelivery_1 = require("../core/opsxDelivery");
|
|
10
11
|
function hasCommand(cmd) {
|
|
11
12
|
const out = (0, node_child_process_1.spawnSync)("which", [cmd], { stdio: "ignore" });
|
|
12
13
|
return out.status === 0;
|
|
@@ -71,6 +72,28 @@ async function runDoctor(options) {
|
|
|
71
72
|
detail: `${expectedDir} 目录存在性`,
|
|
72
73
|
});
|
|
73
74
|
}
|
|
74
|
-
const
|
|
75
|
+
for (const c of await (0, opsxDelivery_1.collectDeliverySetupChecks)(options.projectRoot)) {
|
|
76
|
+
checks.push(c);
|
|
77
|
+
}
|
|
78
|
+
let ok = checks.every((c) => c.ok);
|
|
79
|
+
if (!ok && options.fix) {
|
|
80
|
+
const ensured = await (0, opsxDelivery_1.ensureOpsxWorkflow)({
|
|
81
|
+
projectRoot: options.projectRoot,
|
|
82
|
+
agent: options.agent,
|
|
83
|
+
dryRun: options.dryRun,
|
|
84
|
+
});
|
|
85
|
+
checks.length = 0;
|
|
86
|
+
for (const c of ensured.checks) {
|
|
87
|
+
checks.push(c);
|
|
88
|
+
}
|
|
89
|
+
if (ensured.plannedActions.length > 0) {
|
|
90
|
+
checks.push({
|
|
91
|
+
name: "ensure-opsx-actions",
|
|
92
|
+
ok: ensured.ready,
|
|
93
|
+
detail: ensured.plannedActions.join(" | "),
|
|
94
|
+
});
|
|
95
|
+
}
|
|
96
|
+
ok = checks.every((c) => c.ok);
|
|
97
|
+
}
|
|
75
98
|
return { ok, checks };
|
|
76
99
|
}
|
|
@@ -5,11 +5,11 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.setupProject = setupProject;
|
|
7
7
|
const node_child_process_1 = require("node:child_process");
|
|
8
|
-
const promises_1 = __importDefault(require("node:fs/promises"));
|
|
9
8
|
const node_path_1 = __importDefault(require("node:path"));
|
|
10
|
-
const
|
|
9
|
+
const promises_1 = require("node:readline/promises");
|
|
11
10
|
const node_process_1 = __importDefault(require("node:process"));
|
|
12
11
|
const fs_1 = require("../core/fs");
|
|
12
|
+
const opsxDelivery_1 = require("../core/opsxDelivery");
|
|
13
13
|
const templates_1 = require("../core/templates");
|
|
14
14
|
const EDITOR_CHOICES = ["claude-code", "cursor", "codex", "openclaw"];
|
|
15
15
|
const PROJECT_CHOICES = ["OMS", "ADI", "欢盟", "AD Tools"];
|
|
@@ -47,7 +47,7 @@ function listLocalBranches(projectRoot) {
|
|
|
47
47
|
async function askAgent(defaultAgent) {
|
|
48
48
|
if (!node_process_1.default.stdin.isTTY)
|
|
49
49
|
return defaultAgent;
|
|
50
|
-
const rl = (0,
|
|
50
|
+
const rl = (0, promises_1.createInterface)({ input: node_process_1.default.stdin, output: node_process_1.default.stdout });
|
|
51
51
|
const prompt = [
|
|
52
52
|
"请选择开发环境:",
|
|
53
53
|
"1) claude-code (default)",
|
|
@@ -68,7 +68,7 @@ async function askAgent(defaultAgent) {
|
|
|
68
68
|
async function askBranch(defaultBranch) {
|
|
69
69
|
if (!node_process_1.default.stdin.isTTY)
|
|
70
70
|
return defaultBranch;
|
|
71
|
-
const rl = (0,
|
|
71
|
+
const rl = (0, promises_1.createInterface)({ input: node_process_1.default.stdin, output: node_process_1.default.stdout });
|
|
72
72
|
const answer = (await rl.question(`请输入开发分支(默认 ${defaultBranch}):\n`)).trim();
|
|
73
73
|
rl.close();
|
|
74
74
|
return answer || defaultBranch;
|
|
@@ -76,7 +76,7 @@ async function askBranch(defaultBranch) {
|
|
|
76
76
|
async function askProjectKind(defaultKind) {
|
|
77
77
|
if (!node_process_1.default.stdin.isTTY)
|
|
78
78
|
return defaultKind;
|
|
79
|
-
const rl = (0,
|
|
79
|
+
const rl = (0, promises_1.createInterface)({ input: node_process_1.default.stdin, output: node_process_1.default.stdout });
|
|
80
80
|
const prompt = [
|
|
81
81
|
"请选择当前项目类型(用于生成 PRD 下载 skill):",
|
|
82
82
|
"1) OMS (default)",
|
|
@@ -94,50 +94,6 @@ async function askProjectKind(defaultKind) {
|
|
|
94
94
|
return defaultKind;
|
|
95
95
|
return PROJECT_CHOICES[idx - 1];
|
|
96
96
|
}
|
|
97
|
-
function isEditorEnvironmentReady(projectRoot, agent) {
|
|
98
|
-
const checks = {
|
|
99
|
-
cursor: ["openspec", ".cursor"],
|
|
100
|
-
"claude-code": ["openspec", ".claude"],
|
|
101
|
-
codex: ["openspec", ".codex"],
|
|
102
|
-
openclaw: ["openspec", ".openclaw"],
|
|
103
|
-
custom: ["openspec"],
|
|
104
|
-
};
|
|
105
|
-
const targetPaths = checks[agent] ?? ["openspec"];
|
|
106
|
-
return Promise.all(targetPaths.map(async (p) => {
|
|
107
|
-
try {
|
|
108
|
-
await promises_1.default.access(node_path_1.default.join(projectRoot, p));
|
|
109
|
-
return true;
|
|
110
|
-
}
|
|
111
|
-
catch {
|
|
112
|
-
return false;
|
|
113
|
-
}
|
|
114
|
-
})).then((arr) => arr.every(Boolean));
|
|
115
|
-
}
|
|
116
|
-
function buildSetupCommand(projectRoot, agent) {
|
|
117
|
-
const baseArgs = ["@lixin5257xxx/opsx-workflow", "setup"];
|
|
118
|
-
if (agent === "cursor") {
|
|
119
|
-
return {
|
|
120
|
-
cmd: "npx",
|
|
121
|
-
args: [...baseArgs, "--editor", "cursor", "--project", projectRoot, "-y"],
|
|
122
|
-
};
|
|
123
|
-
}
|
|
124
|
-
if (agent === "claude-code") {
|
|
125
|
-
return {
|
|
126
|
-
cmd: "npx",
|
|
127
|
-
args: [...baseArgs, "--editor", "claude-code", "-y"],
|
|
128
|
-
};
|
|
129
|
-
}
|
|
130
|
-
if (agent === "openclaw") {
|
|
131
|
-
return {
|
|
132
|
-
cmd: "npx",
|
|
133
|
-
args: [...baseArgs, "--editor", "openclaw", "-y"],
|
|
134
|
-
};
|
|
135
|
-
}
|
|
136
|
-
return {
|
|
137
|
-
cmd: "npx",
|
|
138
|
-
args: [...baseArgs, "--editor", "codex", "-y"],
|
|
139
|
-
};
|
|
140
|
-
}
|
|
141
97
|
function ensureBranch(projectRoot, branch) {
|
|
142
98
|
const branches = listLocalBranches(projectRoot);
|
|
143
99
|
if (branches.includes(branch)) {
|
|
@@ -241,7 +197,8 @@ function getOpsxPathsByAgent(agent) {
|
|
|
241
197
|
}
|
|
242
198
|
async function installOpsxConfig(projectRoot, agent) {
|
|
243
199
|
const target = node_path_1.default.join(projectRoot, ".opsx", "config.json");
|
|
244
|
-
const
|
|
200
|
+
const resolvedAgent = await (0, opsxDelivery_1.resolveOpsxAgent)(projectRoot, agent);
|
|
201
|
+
const paths = getOpsxPathsByAgent(resolvedAgent);
|
|
245
202
|
const config = {
|
|
246
203
|
version: 1,
|
|
247
204
|
editor: paths.editor,
|
|
@@ -268,39 +225,43 @@ async function setupProject(options) {
|
|
|
268
225
|
const plannedActions = [];
|
|
269
226
|
const branches = listLocalBranches(options.projectRoot);
|
|
270
227
|
const branchExists = branches.includes(selectedBranch);
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
:
|
|
274
|
-
|
|
228
|
+
let branchResult;
|
|
229
|
+
if (options.light) {
|
|
230
|
+
branchResult = { branch: selectedBranch, created: false };
|
|
231
|
+
plannedActions.push("light mode: skipped git branch checkout");
|
|
232
|
+
}
|
|
233
|
+
else if (options.dryRun) {
|
|
234
|
+
branchResult = { branch: selectedBranch, created: !branchExists };
|
|
275
235
|
plannedActions.push(branchExists
|
|
276
236
|
? `would checkout existing branch: ${selectedBranch}`
|
|
277
237
|
: `would create and checkout branch: ${selectedBranch}`);
|
|
278
238
|
}
|
|
279
|
-
|
|
239
|
+
else {
|
|
240
|
+
branchResult = ensureBranch(options.projectRoot, selectedBranch);
|
|
241
|
+
}
|
|
242
|
+
let environmentReady = await (0, opsxDelivery_1.isOpsxApplyReady)(options.projectRoot);
|
|
280
243
|
let setupExecuted = false;
|
|
281
244
|
let skillInstalled = false;
|
|
282
245
|
let docsSkillInstalled = false;
|
|
283
246
|
let opsxConfigInstalled = false;
|
|
284
|
-
if (
|
|
285
|
-
|
|
286
|
-
if (
|
|
287
|
-
plannedActions.push(
|
|
288
|
-
}
|
|
289
|
-
else {
|
|
290
|
-
const ret = (0, node_child_process_1.spawnSync)(cmd, args, {
|
|
291
|
-
cwd: options.projectRoot,
|
|
292
|
-
stdio: "inherit",
|
|
293
|
-
env: node_process_1.default.env,
|
|
294
|
-
});
|
|
295
|
-
if (ret.status !== 0) {
|
|
296
|
-
throw new Error(`环境安装失败:${cmd} ${args.join(" ")}`);
|
|
297
|
-
}
|
|
298
|
-
setupExecuted = true;
|
|
299
|
-
environmentReady = await isEditorEnvironmentReady(options.projectRoot, selectedAgent);
|
|
247
|
+
if (options.light) {
|
|
248
|
+
plannedActions.push("light mode: skipped npx @lixin5257xxx/opsx-workflow setup");
|
|
249
|
+
if (!environmentReady) {
|
|
250
|
+
plannedActions.push("opsx apply stack incomplete → run: npx sdd-flow-kit ensure-opsx -y (before /opsx-apply)");
|
|
300
251
|
}
|
|
301
252
|
}
|
|
253
|
+
else if (!environmentReady) {
|
|
254
|
+
const ensured = await (0, opsxDelivery_1.ensureOpsxWorkflow)({
|
|
255
|
+
projectRoot: options.projectRoot,
|
|
256
|
+
agent: selectedAgent,
|
|
257
|
+
dryRun: options.dryRun,
|
|
258
|
+
});
|
|
259
|
+
plannedActions.push(...ensured.plannedActions);
|
|
260
|
+
setupExecuted = ensured.workflowInstalled;
|
|
261
|
+
environmentReady = ensured.ready;
|
|
262
|
+
}
|
|
302
263
|
else if (options.dryRun) {
|
|
303
|
-
plannedActions.push("
|
|
264
|
+
plannedActions.push("opsx apply stack already ready");
|
|
304
265
|
}
|
|
305
266
|
if (options.dryRun) {
|
|
306
267
|
for (const skillPath of allSkillInstallPaths(options.projectRoot)) {
|
|
@@ -319,6 +280,10 @@ async function setupProject(options) {
|
|
|
319
280
|
docsSkillInstalled = true;
|
|
320
281
|
await installOpsxConfig(options.projectRoot, selectedAgent);
|
|
321
282
|
opsxConfigInstalled = true;
|
|
283
|
+
const repaired = await (0, opsxDelivery_1.repairOpsxDeliveryScripts)(options.projectRoot);
|
|
284
|
+
if (repaired) {
|
|
285
|
+
plannedActions.push("repaired package.json opsx:delivery-pipeline scripts (poquan → opsx)");
|
|
286
|
+
}
|
|
322
287
|
}
|
|
323
288
|
const summary = [
|
|
324
289
|
"# SETUP-SUMMARY",
|
|
@@ -332,6 +297,7 @@ async function setupProject(options) {
|
|
|
332
297
|
`skillInstalled: ${skillInstalled}`,
|
|
333
298
|
`docsSkillInstalled: ${docsSkillInstalled}`,
|
|
334
299
|
`opsxConfigInstalled: ${opsxConfigInstalled}`,
|
|
300
|
+
`light: ${Boolean(options.light)}`,
|
|
335
301
|
`dryRun: ${Boolean(options.dryRun)}`,
|
|
336
302
|
"",
|
|
337
303
|
"plannedActions:",
|
package/dist/steps/startFlow.js
CHANGED
|
@@ -106,10 +106,10 @@ async function startFlowScaffold(options) {
|
|
|
106
106
|
"产出在 `openspec/changes/<change-name>/`;在 `05-openspec-提案草稿.md` 记录 change 名称。",
|
|
107
107
|
"门禁:仅当 03 已闭环且 01/02/04 完成后执行。",
|
|
108
108
|
"",
|
|
109
|
-
"## Step 6
|
|
110
|
-
"
|
|
111
|
-
"
|
|
112
|
-
"
|
|
109
|
+
"## Step 6:Apply + TDD + Playwright(阶段 D)",
|
|
110
|
+
"依次执行 skill:`openspec-apply-change` → `tdd-script` → `openspec-superpowers-pipeline` → `opsx-dev-delivery-pipeline`。",
|
|
111
|
+
"仓库根执行:`pnpm run opsx:delivery-pipeline -- <change-name>`(须 exit 0)。",
|
|
112
|
+
"禁止跳过 TDD / Playwright 裸写代码收尾。详见 `.cursor/skills/sdd-flow-kit/SKILL.md` 阶段 D。",
|
|
113
113
|
].join("\n"));
|
|
114
114
|
return {
|
|
115
115
|
runId,
|
package/package.json
CHANGED
|
@@ -97,13 +97,32 @@ description: 开发某版本需求、SDD 流程、待确认问题澄清、用户
|
|
|
97
97
|
|
|
98
98
|
---
|
|
99
99
|
|
|
100
|
-
### 阶段 D —
|
|
101
|
-
**触发条件**:阶段 C 的 openspec
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
100
|
+
### 阶段 D — Apply + TDD + Playwright 交付(禁止裸写代码收尾)
|
|
101
|
+
**触发条件**:阶段 C 的 openspec 提案已 apply-ready,且用户同意实现(如「开始 apply」)。
|
|
102
|
+
|
|
103
|
+
**严禁**:跳过 skill 链、不按 `tasks.md` TDD、不跑 delivery-pipeline 就改 `src/` 并宣称完成。
|
|
104
|
+
|
|
105
|
+
**必须按顺序执行(以仓库根 `AGENTS.md` 与 `.cursor/skills/` 为准):**
|
|
106
|
+
|
|
107
|
+
0. **环境门禁(缺则自动安装,不可跳过)** — 在仓库根执行:
|
|
108
|
+
```bash
|
|
109
|
+
npx sdd-flow-kit ensure-opsx --agent cursor -y
|
|
110
|
+
```
|
|
111
|
+
- 检测:openspec、TDD/Playwright skills、`opsx:delivery-pipeline` 脚本、`AGENTS.md` 等。
|
|
112
|
+
- 若 FAIL:上述命令会运行 `npx @lixin5257xxx/opsx-workflow setup ...` 安装工作流;**exit 0 前不得开始改 `src/`**。
|
|
113
|
+
1. 读取 `AGENTS.md`、`docs/agent-workflow.md`、`.opsx/config.json`。
|
|
114
|
+
2. 绑定 change 名:写入 `.opsx-active-change`(一行 kebab-case),或 `export OPSX_ACTIVE_CHANGE=<name>`。
|
|
115
|
+
3. **依次读取并执行**(不可省略):
|
|
116
|
+
- `.cursor/skills/openspec-apply-change/SKILL.md` — `openspec instructions apply --change "<name>" --json`
|
|
117
|
+
- `.cursor/skills/tdd-script/SKILL.md` — **每项 task 先测后实现**,并勾选 `tasks.md` 的 `- [x]`
|
|
118
|
+
- `.cursor/skills/openspec-superpowers-pipeline/SKILL.md` — Delta Spec / 代码质量审查门控
|
|
119
|
+
- `.cursor/skills/opsx-dev-delivery-pipeline/SKILL.md` — 机械交付脚本(TDD 门禁 → validate → **Playwright** → archive)
|
|
120
|
+
4. 在**仓库根**(含 `package.json` 的目录)执行:
|
|
121
|
+
```bash
|
|
122
|
+
pnpm run opsx:delivery-pipeline -- <change-name>
|
|
123
|
+
```
|
|
124
|
+
脚本路径须为:`.cursor/skills/opsx-dev-delivery-pipeline/scripts/opsx-delivery-pipeline.sh`(若 `package.json` 仍指向 `poquan-*`,先运行 `npx sdd-flow-kit install -y` 修复)。
|
|
125
|
+
5. **完成判定**:`tasks.md` 全部 `- [x]` **且** 上一条命令 **exit 0**。否则不得宣称 SDD 流程结束。
|
|
107
126
|
|
|
108
127
|
---
|
|
109
128
|
|