sdd-flow-kit 1.1.0 → 1.1.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -0
- package/dist/cli.js +8 -1
- package/dist/core/opsxDelivery.js +29 -19
- package/dist/core/projectRoots.js +94 -0
- package/dist/steps/resolveRun.js +4 -5
- package/dist/steps/runDeliver.js +4 -9
- package/dist/steps/runGate.js +80 -20
- package/dist/steps/runPhaseAdvance.js +5 -6
- package/dist/steps/runPropose.js +3 -9
- package/dist/steps/setupProject.js +19 -16
- package/dist/steps/startFlow.js +3 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -73,6 +73,25 @@ npx sdd-flow-kit doctor --agent cursor
|
|
|
73
73
|
|
|
74
74
|
#### 使用说明
|
|
75
75
|
|
|
76
|
+
##### 安装根:cd 到哪,装到哪(v1.2+)
|
|
77
|
+
|
|
78
|
+
默认 **`installRoot = 你执行命令时的当前目录`**(`process.cwd()`),也可用 `--project-root` / `SDD_FLOW_KIT_INSTALL_ROOT` 指定。
|
|
79
|
+
|
|
80
|
+
| 你 cd 到 | 会安装到 |
|
|
81
|
+
|----------|----------|
|
|
82
|
+
| `adInsight-web/frontend` | `frontend/package.json`、`.cursor/skills`、`.opsx`、`pnpm run opsx:delivery-pipeline` |
|
|
83
|
+
| `adInsight-web`(仓库根) | 仓库根(需根目录也有 `package.json`,否则会失败) |
|
|
84
|
+
|
|
85
|
+
```bash
|
|
86
|
+
cd /path/to/adInsight-web/frontend
|
|
87
|
+
npx sdd-flow-kit install --project ADI -y
|
|
88
|
+
npx sdd-flow-kit run --product ADI --version 2.3.2 -y
|
|
89
|
+
```
|
|
90
|
+
|
|
91
|
+
**PRD / gate 的 `--project-root`**:与安装根相同;若 `openspec/` 在上级目录,工具会**向上查找**已有 `openspec/PRD`(不把安装写到上级)。
|
|
92
|
+
|
|
93
|
+
旧行为(在仓库根执行时自动落到 `frontend/`):设置 `SDD_FLOW_KIT_LEGACY_MONOREPO=1`。
|
|
94
|
+
|
|
76
95
|
##### 脚本门禁(v1.1+,**不依赖自然语言**)
|
|
77
96
|
|
|
78
97
|
`run` 会在 `openspec/PRD/<runId>/NEXT.md` 写入**必须按顺序执行的命令**。AI 与人都以 **exit code** 为准:失败则不得进入下一阶段、不得改 `src/`。
|
package/dist/cli.js
CHANGED
|
@@ -16,6 +16,7 @@ const runGate_1 = require("./steps/runGate");
|
|
|
16
16
|
const runPhaseAdvance_1 = require("./steps/runPhaseAdvance");
|
|
17
17
|
const runPropose_1 = require("./steps/runPropose");
|
|
18
18
|
const runDeliver_1 = require("./steps/runDeliver");
|
|
19
|
+
const projectRoots_1 = require("./core/projectRoots");
|
|
19
20
|
const ALLOWED_AGENTS = ["cursor", "claude-code", "codex", "openclaw", "custom"];
|
|
20
21
|
function getArgValue(flag) {
|
|
21
22
|
const idx = process_1.default.argv.indexOf(flag);
|
|
@@ -63,7 +64,9 @@ function printHelp() {
|
|
|
63
64
|
" npx sdd-flow-kit deliver --project-root . --run-id <runId> --change <name>",
|
|
64
65
|
"",
|
|
65
66
|
"可选参数:",
|
|
66
|
-
" --projectRoot <path>
|
|
67
|
+
" --projectRoot <path> 目标项目根目录(默认当前目录 = 安装根,cd 到哪装到哪)",
|
|
68
|
+
" 环境变量 SDD_FLOW_KIT_INSTALL_ROOT 可覆盖安装根(绝对路径)",
|
|
69
|
+
" 环境变量 SDD_FLOW_KIT_LEGACY_MONOREPO=1 恢复「仓库根无 package.json 时落到 frontend/」",
|
|
67
70
|
" --agent <name> cursor | claude-code | codex | openclaw | custom(默认 claude-code)",
|
|
68
71
|
" --branch <name> 开发分支;不存在会自动创建",
|
|
69
72
|
" --project <name> OMS | ADI | 欢盟 | AD Tools(默认 OMS,用于生成 docs/*-doc-skill)",
|
|
@@ -256,6 +259,8 @@ async function main() {
|
|
|
256
259
|
return;
|
|
257
260
|
}
|
|
258
261
|
if (cmd === "setup" || cmd === "install") {
|
|
262
|
+
// eslint-disable-next-line no-console
|
|
263
|
+
console.log(`[sdd-flow-kit] 安装根 installRoot=${(0, projectRoots_1.resolveInstallRoot)(projectRoot)} (cwd=${process_1.default.cwd()})`);
|
|
259
264
|
const result = await (0, setupProject_1.setupProject)({
|
|
260
265
|
projectRoot,
|
|
261
266
|
agent,
|
|
@@ -326,6 +331,8 @@ async function main() {
|
|
|
326
331
|
console.log(`已生成 SDD 流程产物骨架:openspec/PRD/${scaffoldRunId}/`);
|
|
327
332
|
return;
|
|
328
333
|
}
|
|
334
|
+
// eslint-disable-next-line no-console
|
|
335
|
+
console.log(`[sdd-flow-kit] 安装根 installRoot=${(0, projectRoots_1.resolveInstallRoot)(projectRoot)} (cwd=${process_1.default.cwd()})`);
|
|
329
336
|
const result = await (0, runFlow_1.runFlow)({
|
|
330
337
|
product,
|
|
331
338
|
version,
|
|
@@ -15,6 +15,7 @@ const child_process_1 = require("child_process");
|
|
|
15
15
|
const promises_1 = __importDefault(require("fs/promises"));
|
|
16
16
|
const path_1 = __importDefault(require("path"));
|
|
17
17
|
const fs_1 = require("./fs");
|
|
18
|
+
const projectRoots_1 = require("./projectRoots");
|
|
18
19
|
const DELIVERY_SCRIPT_REL = ".cursor/skills/opsx-dev-delivery-pipeline/scripts/opsx-delivery-pipeline.sh";
|
|
19
20
|
function deliveryPipelineNpmScripts() {
|
|
20
21
|
const full = `bash ${DELIVERY_SCRIPT_REL}`;
|
|
@@ -92,13 +93,15 @@ function buildOpsxWorkflowSetupCommand(projectRoot, agent) {
|
|
|
92
93
|
return { cmd: "npx", args: [...baseArgs, "--editor", "codex", "-y"] };
|
|
93
94
|
}
|
|
94
95
|
async function isOpsxApplyReady(projectRoot) {
|
|
95
|
-
const
|
|
96
|
+
const installRoot = await (0, projectRoots_1.resolvePackageRoot)((0, projectRoots_1.resolveInstallRoot)(projectRoot));
|
|
97
|
+
const checks = await collectDeliverySetupChecks(installRoot);
|
|
96
98
|
return checks.every((c) => c.ok);
|
|
97
99
|
}
|
|
98
100
|
/** 缺 openspec/TDD/Playwright 工作流时安装 @lixin5257xxx/opsx-workflow,并修复 delivery 脚本 */
|
|
99
101
|
async function ensureOpsxWorkflow(options) {
|
|
102
|
+
const installRoot = await (0, projectRoots_1.resolvePackageRoot)((0, projectRoots_1.resolveInstallRoot)(options.projectRoot));
|
|
100
103
|
const plannedActions = [];
|
|
101
|
-
let checks = await collectDeliverySetupChecks(
|
|
104
|
+
let checks = await collectDeliverySetupChecks(installRoot);
|
|
102
105
|
if (checks.every((c) => c.ok)) {
|
|
103
106
|
return {
|
|
104
107
|
ready: true,
|
|
@@ -108,8 +111,8 @@ async function ensureOpsxWorkflow(options) {
|
|
|
108
111
|
plannedActions,
|
|
109
112
|
};
|
|
110
113
|
}
|
|
111
|
-
const agent = await resolveOpsxAgent(
|
|
112
|
-
const { cmd, args } = buildOpsxWorkflowSetupCommand(
|
|
114
|
+
const agent = await resolveOpsxAgent(installRoot, options.agent);
|
|
115
|
+
const { cmd, args } = buildOpsxWorkflowSetupCommand(installRoot, agent);
|
|
113
116
|
plannedActions.push(`missing opsx apply stack → run: ${cmd} ${args.join(" ")}`);
|
|
114
117
|
if (options.dryRun) {
|
|
115
118
|
return {
|
|
@@ -121,18 +124,18 @@ async function ensureOpsxWorkflow(options) {
|
|
|
121
124
|
};
|
|
122
125
|
}
|
|
123
126
|
const ret = (0, child_process_1.spawnSync)(cmd, args, {
|
|
124
|
-
cwd:
|
|
127
|
+
cwd: installRoot,
|
|
125
128
|
stdio: "inherit",
|
|
126
129
|
env: process.env,
|
|
127
130
|
});
|
|
128
131
|
if (ret.status !== 0) {
|
|
129
132
|
throw new Error(`opsx-workflow 安装失败:${cmd} ${args.join(" ")}`);
|
|
130
133
|
}
|
|
131
|
-
const repairedScripts = await repairOpsxDeliveryScripts(
|
|
134
|
+
const repairedScripts = await repairOpsxDeliveryScripts(installRoot);
|
|
132
135
|
if (repairedScripts) {
|
|
133
136
|
plannedActions.push("repaired package.json opsx:delivery-pipeline scripts");
|
|
134
137
|
}
|
|
135
|
-
checks = await collectDeliverySetupChecks(
|
|
138
|
+
checks = await collectDeliverySetupChecks(installRoot);
|
|
136
139
|
return {
|
|
137
140
|
ready: checks.every((c) => c.ok),
|
|
138
141
|
workflowInstalled: true,
|
|
@@ -141,45 +144,52 @@ async function ensureOpsxWorkflow(options) {
|
|
|
141
144
|
plannedActions,
|
|
142
145
|
};
|
|
143
146
|
}
|
|
144
|
-
async function collectDeliverySetupChecks(
|
|
147
|
+
async function collectDeliverySetupChecks(installRoot) {
|
|
145
148
|
var _a, _b, _c, _d, _e, _f;
|
|
146
149
|
const checks = [];
|
|
150
|
+
const root = (0, projectRoots_1.resolveInstallRoot)(installRoot);
|
|
151
|
+
const openspecRoot = await (0, projectRoots_1.resolveOpenspecRoot)(root);
|
|
152
|
+
checks.push({
|
|
153
|
+
name: "install-root",
|
|
154
|
+
ok: true,
|
|
155
|
+
detail: `安装根=${root}`,
|
|
156
|
+
});
|
|
147
157
|
checks.push({
|
|
148
158
|
name: "openspec-dir",
|
|
149
|
-
ok: await pathExists(path_1.default.join(
|
|
150
|
-
detail: "openspec/
|
|
159
|
+
ok: await pathExists(path_1.default.join(openspecRoot, "openspec")),
|
|
160
|
+
detail: openspecRoot === root ? "openspec/ 目录(安装根)" : `openspec/ 目录(上级 ${openspecRoot})`,
|
|
151
161
|
});
|
|
152
162
|
checks.push({
|
|
153
163
|
name: "openspec-cli",
|
|
154
164
|
ok: hasCommand("openspec"),
|
|
155
165
|
detail: "openspec CLI(npx openspec 或全局安装)",
|
|
156
166
|
});
|
|
157
|
-
const skillPath = path_1.default.join(
|
|
167
|
+
const skillPath = path_1.default.join(root, ".cursor", "skills", "opsx-dev-delivery-pipeline", "SKILL.md");
|
|
158
168
|
checks.push({
|
|
159
169
|
name: "opsx-dev-delivery-pipeline-skill",
|
|
160
170
|
ok: await pathExists(skillPath),
|
|
161
171
|
detail: skillPath,
|
|
162
172
|
});
|
|
163
|
-
const tddPath = path_1.default.join(
|
|
173
|
+
const tddPath = path_1.default.join(root, ".cursor", "skills", "tdd-script", "SKILL.md");
|
|
164
174
|
checks.push({
|
|
165
175
|
name: "tdd-script-skill",
|
|
166
176
|
ok: await pathExists(tddPath),
|
|
167
177
|
detail: tddPath,
|
|
168
178
|
});
|
|
169
|
-
const applyPath = path_1.default.join(
|
|
179
|
+
const applyPath = path_1.default.join(root, ".cursor", "skills", "openspec-apply-change", "SKILL.md");
|
|
170
180
|
checks.push({
|
|
171
181
|
name: "openspec-apply-change-skill",
|
|
172
182
|
ok: await pathExists(applyPath),
|
|
173
183
|
detail: applyPath,
|
|
174
184
|
});
|
|
175
|
-
const pipelineScript = path_1.default.join(
|
|
185
|
+
const pipelineScript = path_1.default.join(root, DELIVERY_SCRIPT_REL);
|
|
176
186
|
checks.push({
|
|
177
187
|
name: "delivery-pipeline-script",
|
|
178
188
|
ok: await pathExists(pipelineScript),
|
|
179
189
|
detail: pipelineScript,
|
|
180
190
|
});
|
|
181
191
|
try {
|
|
182
|
-
const pkgText = await promises_1.default.readFile(path_1.default.join(
|
|
192
|
+
const pkgText = await promises_1.default.readFile(path_1.default.join(root, "package.json"), "utf8");
|
|
183
193
|
const pkg = JSON.parse(pkgText);
|
|
184
194
|
const cmd = (_b = (_a = pkg.scripts) === null || _a === void 0 ? void 0 : _a["opsx:delivery-pipeline"]) !== null && _b !== void 0 ? _b : "";
|
|
185
195
|
const ok = cmd.includes("opsx-dev-delivery-pipeline") &&
|
|
@@ -199,10 +209,10 @@ async function collectDeliverySetupChecks(projectRoot) {
|
|
|
199
209
|
});
|
|
200
210
|
}
|
|
201
211
|
try {
|
|
202
|
-
const cfgText = await promises_1.default.readFile(path_1.default.join(
|
|
212
|
+
const cfgText = await promises_1.default.readFile(path_1.default.join(root, ".opsx", "config.json"), "utf8");
|
|
203
213
|
const cfg = JSON.parse(cfgText);
|
|
204
214
|
const skillsDir = (_c = cfg.skillsDir) !== null && _c !== void 0 ? _c : "";
|
|
205
|
-
const hasCursorDelivery = await pathExists(path_1.default.join(
|
|
215
|
+
const hasCursorDelivery = await pathExists(path_1.default.join(root, ".cursor", "skills", "opsx-dev-delivery-pipeline", "SKILL.md"));
|
|
206
216
|
const nameOk = ((_d = cfg.deliveryPipeline) === null || _d === void 0 ? void 0 : _d.skillName) === "opsx-dev-delivery-pipeline";
|
|
207
217
|
const dirOk = (hasCursorDelivery && skillsDir.includes(".cursor")) ||
|
|
208
218
|
(!hasCursorDelivery && (skillsDir.includes(".cursor") || skillsDir.includes(".claude")));
|
|
@@ -222,7 +232,7 @@ async function collectDeliverySetupChecks(projectRoot) {
|
|
|
222
232
|
});
|
|
223
233
|
}
|
|
224
234
|
try {
|
|
225
|
-
const pkgText = await promises_1.default.readFile(path_1.default.join(
|
|
235
|
+
const pkgText = await promises_1.default.readFile(path_1.default.join(root, "package.json"), "utf8");
|
|
226
236
|
const pkg = JSON.parse(pkgText);
|
|
227
237
|
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
228
238
|
const hasPw = Boolean(deps["@playwright/test"] || deps.playwright);
|
|
@@ -239,7 +249,7 @@ async function collectDeliverySetupChecks(projectRoot) {
|
|
|
239
249
|
detail: "无法读取 package.json 检查 Playwright",
|
|
240
250
|
});
|
|
241
251
|
}
|
|
242
|
-
const agentsMd = path_1.default.join(
|
|
252
|
+
const agentsMd = path_1.default.join(root, "AGENTS.md");
|
|
243
253
|
checks.push({
|
|
244
254
|
name: "agents-md",
|
|
245
255
|
ok: await pathExists(agentsMd),
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.resolveInstallRoot = resolveInstallRoot;
|
|
7
|
+
exports.isLegacyMonorepoMode = isLegacyMonorepoMode;
|
|
8
|
+
exports.resolveOpenspecRoot = resolveOpenspecRoot;
|
|
9
|
+
exports.resolvePackageRoot = resolvePackageRoot;
|
|
10
|
+
exports.listOpenspecPrdRoots = listOpenspecPrdRoots;
|
|
11
|
+
const promises_1 = __importDefault(require("fs/promises"));
|
|
12
|
+
const path_1 = __importDefault(require("path"));
|
|
13
|
+
async function pathExists(filePath) {
|
|
14
|
+
try {
|
|
15
|
+
await promises_1.default.access(filePath);
|
|
16
|
+
return true;
|
|
17
|
+
}
|
|
18
|
+
catch {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* 安装根:严格等于 CLI 的 projectRoot(默认 process.cwd())。
|
|
24
|
+
* 可通过 SDD_FLOW_KIT_INSTALL_ROOT 覆盖(绝对路径)。
|
|
25
|
+
* 原则:cd 到哪,package.json / .cursor/skills / opsx-workflow 就装到哪。
|
|
26
|
+
*/
|
|
27
|
+
function resolveInstallRoot(projectRoot) {
|
|
28
|
+
var _a;
|
|
29
|
+
const fromEnv = (_a = process.env.SDD_FLOW_KIT_INSTALL_ROOT) === null || _a === void 0 ? void 0 : _a.trim();
|
|
30
|
+
if (fromEnv)
|
|
31
|
+
return path_1.default.resolve(fromEnv);
|
|
32
|
+
return path_1.default.resolve(projectRoot);
|
|
33
|
+
}
|
|
34
|
+
/** 是否启用旧版 monorepo 行为(在仓库根执行时自动落到 frontend/) */
|
|
35
|
+
function isLegacyMonorepoMode() {
|
|
36
|
+
return process.env.SDD_FLOW_KIT_LEGACY_MONOREPO === "1" || process.env.SDD_FLOW_KIT_LEGACY_MONOREPO === "true";
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* 从 installRoot 向上查找含 openspec/ 的目录(用于解析 PRD run、openspec CLI)。
|
|
40
|
+
* 找不到则退回 installRoot(将在其下新建 openspec/PRD)。
|
|
41
|
+
*/
|
|
42
|
+
async function resolveOpenspecRoot(installRoot) {
|
|
43
|
+
let cur = path_1.default.resolve(installRoot);
|
|
44
|
+
for (let i = 0; i < 12; i += 1) {
|
|
45
|
+
if (await pathExists(path_1.default.join(cur, "openspec"))) {
|
|
46
|
+
return cur;
|
|
47
|
+
}
|
|
48
|
+
const parent = path_1.default.dirname(cur);
|
|
49
|
+
if (parent === cur)
|
|
50
|
+
break;
|
|
51
|
+
cur = parent;
|
|
52
|
+
}
|
|
53
|
+
return path_1.default.resolve(installRoot);
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* 交付/门禁用的包根:默认与 installRoot 相同。
|
|
57
|
+
* 仅当 SDD_FLOW_KIT_LEGACY_MONOREPO=1 且 installRoot 无 package.json 但存在 frontend/package.json 时回落到 frontend/。
|
|
58
|
+
*/
|
|
59
|
+
async function resolvePackageRoot(installRoot) {
|
|
60
|
+
const root = resolveInstallRoot(installRoot);
|
|
61
|
+
if (!isLegacyMonorepoMode()) {
|
|
62
|
+
return root;
|
|
63
|
+
}
|
|
64
|
+
const pkgAtRoot = path_1.default.join(root, "package.json");
|
|
65
|
+
if (await pathExists(pkgAtRoot)) {
|
|
66
|
+
return root;
|
|
67
|
+
}
|
|
68
|
+
const fePkg = path_1.default.join(root, "frontend", "package.json");
|
|
69
|
+
if (await pathExists(fePkg)) {
|
|
70
|
+
return path_1.default.join(root, "frontend");
|
|
71
|
+
}
|
|
72
|
+
return root;
|
|
73
|
+
}
|
|
74
|
+
/**
|
|
75
|
+
* 收集自 installRoot 向上所有存在的 openspec/PRD 目录(用于 resolveRun)。
|
|
76
|
+
*/
|
|
77
|
+
async function listOpenspecPrdRoots(startDir) {
|
|
78
|
+
const seen = new Set();
|
|
79
|
+
const out = [];
|
|
80
|
+
let cur = path_1.default.resolve(startDir);
|
|
81
|
+
for (let i = 0; i < 12; i += 1) {
|
|
82
|
+
const prdRoot = path_1.default.join(cur, "openspec", "PRD");
|
|
83
|
+
const key = prdRoot.toLowerCase();
|
|
84
|
+
if (!seen.has(key) && (await pathExists(prdRoot))) {
|
|
85
|
+
seen.add(key);
|
|
86
|
+
out.push(prdRoot);
|
|
87
|
+
}
|
|
88
|
+
const parent = path_1.default.dirname(cur);
|
|
89
|
+
if (parent === cur)
|
|
90
|
+
break;
|
|
91
|
+
cur = parent;
|
|
92
|
+
}
|
|
93
|
+
return out;
|
|
94
|
+
}
|
package/dist/steps/resolveRun.js
CHANGED
|
@@ -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
|
|
14
|
+
* 解析 openspec/PRD 目录:从 installRoot(cwd)向上查找;优先 --run-id,否则取最近修改的 run 目录。
|
|
14
15
|
*/
|
|
15
16
|
async function resolveRunDir(projectRoot, runId) {
|
|
16
|
-
const
|
|
17
|
-
|
|
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 });
|
package/dist/steps/runDeliver.js
CHANGED
|
@@ -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
|
|
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,
|
package/dist/steps/runGate.js
CHANGED
|
@@ -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
|
|
26
|
-
|
|
27
|
-
|
|
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
|
|
41
|
-
const
|
|
42
|
-
if (
|
|
43
|
-
|
|
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
|
|
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
|
|
121
|
-
|
|
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
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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
|
-
|
|
116
|
-
|
|
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
|
|
143
|
-
|
|
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,
|
package/dist/steps/runPropose.js
CHANGED
|
@@ -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
|
|
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
|
|
31
|
-
|
|
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,7 @@ 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 projectRoots_1 = require("../core/projectRoots");
|
|
14
15
|
const templates_1 = require("../core/templates");
|
|
15
16
|
const EDITOR_CHOICES = ["claude-code", "cursor", "codex", "openclaw"];
|
|
16
17
|
const PROJECT_CHOICES = ["OMS", "ADI", "欢盟", "AD Tools"];
|
|
@@ -219,14 +220,15 @@ async function installOpsxConfig(projectRoot, agent) {
|
|
|
219
220
|
}
|
|
220
221
|
async function setupProject(options) {
|
|
221
222
|
var _a, _b, _c;
|
|
223
|
+
const installRoot = (0, projectRoots_1.resolveInstallRoot)(options.projectRoot);
|
|
222
224
|
const defaultAgent = (_a = options.agent) !== null && _a !== void 0 ? _a : "claude-code";
|
|
223
225
|
const selectedAgent = options.yes ? defaultAgent : await askAgent(defaultAgent);
|
|
224
226
|
const defaultKind = (_b = options.projectKind) !== null && _b !== void 0 ? _b : "OMS";
|
|
225
227
|
const selectedKind = options.yes ? defaultKind : await askProjectKind(defaultKind);
|
|
226
|
-
const defaultBranch = (_c = options.branch) !== null && _c !== void 0 ? _c : pickDefaultBranch(
|
|
228
|
+
const defaultBranch = (_c = options.branch) !== null && _c !== void 0 ? _c : pickDefaultBranch(installRoot);
|
|
227
229
|
const selectedBranch = options.yes ? defaultBranch : await askBranch(defaultBranch);
|
|
228
|
-
const plannedActions = [];
|
|
229
|
-
const branches = listLocalBranches(
|
|
230
|
+
const plannedActions = [`installRoot=${installRoot}`];
|
|
231
|
+
const branches = listLocalBranches(installRoot);
|
|
230
232
|
const branchExists = branches.includes(selectedBranch);
|
|
231
233
|
let branchResult;
|
|
232
234
|
if (options.light) {
|
|
@@ -240,9 +242,9 @@ async function setupProject(options) {
|
|
|
240
242
|
: `would create and checkout branch: ${selectedBranch}`);
|
|
241
243
|
}
|
|
242
244
|
else {
|
|
243
|
-
branchResult = ensureBranch(
|
|
245
|
+
branchResult = ensureBranch(installRoot, selectedBranch);
|
|
244
246
|
}
|
|
245
|
-
let environmentReady = await (0, opsxDelivery_1.isOpsxApplyReady)(
|
|
247
|
+
let environmentReady = await (0, opsxDelivery_1.isOpsxApplyReady)(installRoot);
|
|
246
248
|
let setupExecuted = false;
|
|
247
249
|
let skillInstalled = false;
|
|
248
250
|
let docsSkillInstalled = false;
|
|
@@ -255,7 +257,7 @@ async function setupProject(options) {
|
|
|
255
257
|
}
|
|
256
258
|
else if (!environmentReady) {
|
|
257
259
|
const ensured = await (0, opsxDelivery_1.ensureOpsxWorkflow)({
|
|
258
|
-
projectRoot:
|
|
260
|
+
projectRoot: installRoot,
|
|
259
261
|
agent: selectedAgent,
|
|
260
262
|
dryRun: options.dryRun,
|
|
261
263
|
});
|
|
@@ -267,23 +269,24 @@ async function setupProject(options) {
|
|
|
267
269
|
plannedActions.push("opsx apply stack already ready");
|
|
268
270
|
}
|
|
269
271
|
if (options.dryRun) {
|
|
270
|
-
for (const skillPath of allSkillInstallPaths(
|
|
272
|
+
for (const skillPath of allSkillInstallPaths(installRoot)) {
|
|
271
273
|
plannedActions.push(`would install skill: ${skillPath}`);
|
|
272
274
|
}
|
|
273
275
|
const { dir } = docSkillDirName(selectedKind);
|
|
274
|
-
plannedActions.push(`would install docs skill: ${path_1.default.join(
|
|
275
|
-
plannedActions.push(`would install opsx config: ${path_1.default.join(
|
|
276
|
-
plannedActions.push(`would install cursor rule: ${path_1.default.join(
|
|
276
|
+
plannedActions.push(`would install docs skill: ${path_1.default.join(installRoot, "docs", dir)}`);
|
|
277
|
+
plannedActions.push(`would install opsx config: ${path_1.default.join(installRoot, ".opsx", "config.json")}`);
|
|
278
|
+
plannedActions.push(`would install cursor rule: ${path_1.default.join(installRoot, ".cursor", "rules", "sdd-flow-kit-gates.mdc")}`);
|
|
277
279
|
}
|
|
278
280
|
else {
|
|
279
|
-
await installAgentSkill(
|
|
280
|
-
await installSddCursorRule(
|
|
281
|
+
await installAgentSkill(installRoot);
|
|
282
|
+
await installSddCursorRule(installRoot);
|
|
281
283
|
skillInstalled = true;
|
|
282
|
-
await installDocsSkill(
|
|
284
|
+
await installDocsSkill(installRoot, selectedKind);
|
|
283
285
|
docsSkillInstalled = true;
|
|
284
|
-
await installOpsxConfig(
|
|
286
|
+
await installOpsxConfig(installRoot, selectedAgent);
|
|
285
287
|
opsxConfigInstalled = true;
|
|
286
|
-
const
|
|
288
|
+
const pkgRoot = await (0, projectRoots_1.resolvePackageRoot)(installRoot);
|
|
289
|
+
const repaired = await (0, opsxDelivery_1.repairOpsxDeliveryScripts)(pkgRoot);
|
|
287
290
|
if (repaired) {
|
|
288
291
|
plannedActions.push("repaired package.json opsx:delivery-pipeline scripts (poquan → opsx)");
|
|
289
292
|
}
|
|
@@ -307,7 +310,7 @@ async function setupProject(options) {
|
|
|
307
310
|
...plannedActions.map((x) => `- ${x}`),
|
|
308
311
|
"",
|
|
309
312
|
].join("\n");
|
|
310
|
-
await (0, fs_1.writeTextFileEnsuredDir)(path_1.default.join(
|
|
313
|
+
await (0, fs_1.writeTextFileEnsuredDir)(path_1.default.join(installRoot, ".sdd-flow-kit", "setup-summary.md"), summary);
|
|
311
314
|
return {
|
|
312
315
|
agent: selectedAgent,
|
|
313
316
|
projectKind: selectedKind,
|
package/dist/steps/startFlow.js
CHANGED
|
@@ -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
|
|
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)
|