sdd-flow-kit 1.1.0 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +35 -0
- package/dist/cli.js +36 -15
- package/dist/core/bootstrap.js +58 -0
- package/dist/core/opsxDelivery.js +63 -36
- package/dist/core/opsxWorkflowPackage.js +23 -0
- package/dist/core/packageJsonPatch.js +89 -0
- package/dist/core/projectRoots.js +94 -0
- package/dist/postinstall.js +22 -18
- package/dist/steps/doctor.js +19 -0
- package/dist/steps/invokeFlow.js +18 -11
- package/dist/steps/resolveRun.js +4 -5
- package/dist/steps/runDeliver.js +4 -9
- package/dist/steps/runFlow.js +2 -0
- 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 +32 -20
- package/dist/steps/startFlow.js +3 -1
- package/package.json +1 -1
- package/src/templates/artifacts/06-agent-skill.template.md +31 -52
- package/src/templates/rules/sdd-no-implement-until-apply.mdc.template +7 -0
package/dist/postinstall.js
CHANGED
|
@@ -40,6 +40,7 @@ const path_1 = __importDefault(require("path"));
|
|
|
40
40
|
const process_1 = __importDefault(require("process"));
|
|
41
41
|
const inferProject_1 = require("./core/inferProject");
|
|
42
42
|
const opsxDelivery_1 = require("./core/opsxDelivery");
|
|
43
|
+
const projectRoots_1 = require("./core/projectRoots");
|
|
43
44
|
const setupProject_1 = require("./steps/setupProject");
|
|
44
45
|
async function inferAgent(projectRoot) {
|
|
45
46
|
var _a;
|
|
@@ -68,50 +69,53 @@ async function inferAgent(projectRoot) {
|
|
|
68
69
|
return "cursor";
|
|
69
70
|
}
|
|
70
71
|
async function main() {
|
|
72
|
+
var _a;
|
|
71
73
|
if (process_1.default.env.SDD_FLOW_KIT_SKIP_POSTINSTALL === "1") {
|
|
72
74
|
return;
|
|
73
75
|
}
|
|
74
|
-
const initCwd = process_1.default.env.INIT_CWD ?
|
|
76
|
+
const initCwd = (_a = process_1.default.env.INIT_CWD) === null || _a === void 0 ? void 0 : _a.trim();
|
|
75
77
|
const pkgDir = path_1.default.resolve(__dirname, "..");
|
|
76
|
-
|
|
78
|
+
if (!initCwd) {
|
|
79
|
+
// eslint-disable-next-line no-console
|
|
80
|
+
console.warn("[sdd-flow-kit] postinstall: 缺少 INIT_CWD,已跳过。请执行: npx sdd-flow-kit install --project ADI -y");
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
const projectRoot = (0, projectRoots_1.resolveInstallRoot)(initCwd);
|
|
77
84
|
if (projectRoot === pkgDir)
|
|
78
85
|
return;
|
|
79
86
|
const projectKind = await (0, inferProject_1.inferProjectKind)(projectRoot);
|
|
80
87
|
const agent = await inferAgent(projectRoot);
|
|
88
|
+
const light = process_1.default.env.SDD_FLOW_KIT_POSTINSTALL_LIGHT === "1";
|
|
81
89
|
// eslint-disable-next-line no-console
|
|
82
|
-
console.log(`[sdd-flow-kit] postinstall (light) at: ${projectRoot}`);
|
|
90
|
+
console.log(`[sdd-flow-kit] postinstall (${light ? "light" : "full"}) at: ${projectRoot}`);
|
|
83
91
|
// eslint-disable-next-line no-console
|
|
84
92
|
console.log(`[sdd-flow-kit] agent=${agent}, project=${projectKind}`);
|
|
85
|
-
await (0, setupProject_1.setupProject)({
|
|
93
|
+
const result = await (0, setupProject_1.setupProject)({
|
|
86
94
|
projectRoot,
|
|
87
95
|
agent,
|
|
88
96
|
yes: true,
|
|
89
|
-
light
|
|
97
|
+
light,
|
|
98
|
+
skipGit: true,
|
|
90
99
|
dryRun: false,
|
|
91
100
|
projectKind,
|
|
92
101
|
});
|
|
93
|
-
const repaired = await (0, opsxDelivery_1.repairOpsxDeliveryScripts)(projectRoot);
|
|
94
|
-
if (repaired) {
|
|
95
|
-
// eslint-disable-next-line no-console
|
|
96
|
-
console.log("[sdd-flow-kit] repaired package.json opsx:delivery-pipeline scripts");
|
|
97
|
-
}
|
|
98
102
|
const ready = await (0, opsxDelivery_1.isOpsxApplyReady)(projectRoot);
|
|
99
103
|
if (!ready) {
|
|
100
104
|
// eslint-disable-next-line no-console
|
|
101
|
-
console.warn("[sdd-flow-kit] OPSX
|
|
105
|
+
console.warn("[sdd-flow-kit] OPSX 未完全就绪。请执行: npx sdd-flow-kit install --project " +
|
|
106
|
+
`${projectKind} -y` +
|
|
107
|
+
(process_1.default.env.SDD_OPSX_WORKFLOW_PACKAGE ? "" : "(或设 SDD_OPSX_WORKFLOW_PACKAGE 指向本地 opsx-workflow)"));
|
|
102
108
|
}
|
|
103
109
|
else {
|
|
104
110
|
// eslint-disable-next-line no-console
|
|
105
|
-
console.log("[sdd-flow-kit]
|
|
111
|
+
console.log("[sdd-flow-kit] 安装完成:SDD + OPSX + Superpowers 已就绪");
|
|
106
112
|
}
|
|
107
113
|
// eslint-disable-next-line no-console
|
|
108
|
-
console.log(
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
console.log("[sdd-flow-kit] Cursor skill: .cursor/skills/sdd-flow-kit/SKILL.md");
|
|
112
|
-
}
|
|
114
|
+
console.log(`[sdd-flow-kit] 开发需求: npx sdd-flow-kit invoke "开发 ${projectKind} x.x.x 需求" -y`);
|
|
115
|
+
// eslint-disable-next-line no-console
|
|
116
|
+
console.log(`[sdd-flow-kit] environmentReady=${result.environmentReady}, setupExecuted=${result.setupExecuted}`);
|
|
113
117
|
}
|
|
114
118
|
main().catch((err) => {
|
|
115
119
|
// eslint-disable-next-line no-console
|
|
116
|
-
console.warn("[sdd-flow-kit] postinstall
|
|
120
|
+
console.warn("[sdd-flow-kit] postinstall skipped:", err instanceof Error ? err.message : err);
|
|
117
121
|
});
|
package/dist/steps/doctor.js
CHANGED
|
@@ -9,6 +9,7 @@ const promises_1 = __importDefault(require("fs/promises"));
|
|
|
9
9
|
const path_1 = __importDefault(require("path"));
|
|
10
10
|
const process_1 = __importDefault(require("process"));
|
|
11
11
|
const nodeCompat_1 = require("../core/nodeCompat");
|
|
12
|
+
const bootstrap_1 = require("../core/bootstrap");
|
|
12
13
|
const opsxDelivery_1 = require("../core/opsxDelivery");
|
|
13
14
|
function hasCommand(cmd) {
|
|
14
15
|
const out = (0, child_process_1.spawnSync)("which", [cmd], { stdio: "ignore" });
|
|
@@ -83,6 +84,24 @@ async function runDoctor(options) {
|
|
|
83
84
|
checks.push(c);
|
|
84
85
|
}
|
|
85
86
|
let ok = checks.every((c) => c.ok);
|
|
87
|
+
const opsxStackFailed = checks.some((c) => !c.ok &&
|
|
88
|
+
[
|
|
89
|
+
"opsx-dev-delivery-pipeline-skill",
|
|
90
|
+
"opsx-config",
|
|
91
|
+
"delivery-pipeline-script",
|
|
92
|
+
"package-json-delivery-script",
|
|
93
|
+
].includes(c.name));
|
|
94
|
+
if (!ok && opsxStackFailed && !options.fix && !options.dryRun) {
|
|
95
|
+
await (0, bootstrap_1.bootstrapProjectIfNeeded)({
|
|
96
|
+
projectRoot: options.projectRoot,
|
|
97
|
+
agent: options.agent,
|
|
98
|
+
});
|
|
99
|
+
checks.length = 0;
|
|
100
|
+
for (const c of await (0, opsxDelivery_1.collectDeliverySetupChecks)(options.projectRoot)) {
|
|
101
|
+
checks.push(c);
|
|
102
|
+
}
|
|
103
|
+
ok = checks.every((c) => c.ok);
|
|
104
|
+
}
|
|
86
105
|
if (!ok && options.fix) {
|
|
87
106
|
const ensured = await (0, opsxDelivery_1.ensureOpsxWorkflow)({
|
|
88
107
|
projectRoot: options.projectRoot,
|
package/dist/steps/invokeFlow.js
CHANGED
|
@@ -4,17 +4,24 @@ exports.parseInvokeText = parseInvokeText;
|
|
|
4
4
|
exports.toRunOptions = toRunOptions;
|
|
5
5
|
function parseInvokeText(text) {
|
|
6
6
|
const normalized = text.trim();
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
const
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
7
|
+
const patterns = [
|
|
8
|
+
/(?:开发|进行)\s*(.+?)(?:[-_\s]*)V?([0-9]+(?:\.[0-9]+){1,3})\s*(?:版本)?\s*(?:需求|PRD)?/i,
|
|
9
|
+
/([A-Za-z\u4e00-\u9fa5]+)[_\s-]?V?([0-9]+(?:\.[0-9]+){1,3})\s*版本/i,
|
|
10
|
+
/([A-Za-z]+)[_\s-]([0-9]+(?:\.[0-9]+){2,3})(?:\s|$)/i,
|
|
11
|
+
];
|
|
12
|
+
for (const re of patterns) {
|
|
13
|
+
const match = normalized.match(re);
|
|
14
|
+
if (!match)
|
|
15
|
+
continue;
|
|
16
|
+
let product = match[1].replace(/[-_\s]+$/g, "").trim();
|
|
17
|
+
product = product.replace(/^进行/, "").trim();
|
|
18
|
+
if (/^adi$/i.test(product) || product.toUpperCase() === "ADI")
|
|
19
|
+
product = "ADI";
|
|
20
|
+
if (/^oms$/i.test(product))
|
|
21
|
+
product = "OMS";
|
|
22
|
+
return { product: product.toUpperCase(), version: match[2] };
|
|
23
|
+
}
|
|
24
|
+
return null;
|
|
18
25
|
}
|
|
19
26
|
function toRunOptions(args) {
|
|
20
27
|
var _a, _b;
|
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/runFlow.js
CHANGED
|
@@ -7,6 +7,7 @@ exports.runFlow = runFlow;
|
|
|
7
7
|
const path_1 = __importDefault(require("path"));
|
|
8
8
|
const adapters_1 = require("../adapters");
|
|
9
9
|
const fs_1 = require("../core/fs");
|
|
10
|
+
const inferProject_1 = require("../core/inferProject");
|
|
10
11
|
const startFlow_1 = require("./startFlow");
|
|
11
12
|
const setupProject_1 = require("./setupProject");
|
|
12
13
|
function buildChecklist(args) {
|
|
@@ -37,6 +38,7 @@ async function runFlow(options) {
|
|
|
37
38
|
branch: options.branch,
|
|
38
39
|
yes: options.yes,
|
|
39
40
|
dryRun: options.dryRun,
|
|
41
|
+
projectKind: (0, inferProject_1.productToKind)(options.product),
|
|
40
42
|
});
|
|
41
43
|
const scaffold = options.dryRun
|
|
42
44
|
? {
|
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,8 @@ const fs_1 = require("../core/fs");
|
|
|
11
11
|
const readlinePrompt_1 = require("../core/readlinePrompt");
|
|
12
12
|
const utils_1 = require("../core/utils");
|
|
13
13
|
const opsxDelivery_1 = require("../core/opsxDelivery");
|
|
14
|
+
const packageJsonPatch_1 = require("../core/packageJsonPatch");
|
|
15
|
+
const projectRoots_1 = require("../core/projectRoots");
|
|
14
16
|
const templates_1 = require("../core/templates");
|
|
15
17
|
const EDITOR_CHOICES = ["claude-code", "cursor", "codex", "openclaw"];
|
|
16
18
|
const PROJECT_CHOICES = ["OMS", "ADI", "欢盟", "AD Tools"];
|
|
@@ -124,7 +126,7 @@ function allSkillInstallPaths(projectRoot) {
|
|
|
124
126
|
.map((agent) => skillInstallPath(projectRoot, agent))
|
|
125
127
|
.filter((p) => Boolean(p));
|
|
126
128
|
}
|
|
127
|
-
async function installAgentSkill(projectRoot) {
|
|
129
|
+
async function installAgentSkill(projectRoot, projectKind) {
|
|
128
130
|
const targets = allSkillInstallPaths(projectRoot);
|
|
129
131
|
if (targets.length === 0)
|
|
130
132
|
return;
|
|
@@ -137,7 +139,11 @@ async function installAgentSkill(projectRoot) {
|
|
|
137
139
|
: target.includes("/.codex/")
|
|
138
140
|
? "codex"
|
|
139
141
|
: "openclaw";
|
|
140
|
-
const content = (
|
|
142
|
+
const content = renderVars(template, {
|
|
143
|
+
AGENT_NAME: agentName,
|
|
144
|
+
PROJECT_KIND: projectKind,
|
|
145
|
+
DEFAULT_PRODUCT: projectKind === "AD Tools" ? "AD Tools" : projectKind,
|
|
146
|
+
});
|
|
141
147
|
await (0, fs_1.writeTextFileEnsuredDir)(target, content);
|
|
142
148
|
}
|
|
143
149
|
}
|
|
@@ -219,19 +225,20 @@ async function installOpsxConfig(projectRoot, agent) {
|
|
|
219
225
|
}
|
|
220
226
|
async function setupProject(options) {
|
|
221
227
|
var _a, _b, _c;
|
|
228
|
+
const installRoot = (0, projectRoots_1.resolveInstallRoot)(options.projectRoot);
|
|
222
229
|
const defaultAgent = (_a = options.agent) !== null && _a !== void 0 ? _a : "claude-code";
|
|
223
230
|
const selectedAgent = options.yes ? defaultAgent : await askAgent(defaultAgent);
|
|
224
231
|
const defaultKind = (_b = options.projectKind) !== null && _b !== void 0 ? _b : "OMS";
|
|
225
232
|
const selectedKind = options.yes ? defaultKind : await askProjectKind(defaultKind);
|
|
226
|
-
const defaultBranch = (_c = options.branch) !== null && _c !== void 0 ? _c : pickDefaultBranch(
|
|
233
|
+
const defaultBranch = (_c = options.branch) !== null && _c !== void 0 ? _c : pickDefaultBranch(installRoot);
|
|
227
234
|
const selectedBranch = options.yes ? defaultBranch : await askBranch(defaultBranch);
|
|
228
|
-
const plannedActions = [];
|
|
229
|
-
const branches = listLocalBranches(
|
|
235
|
+
const plannedActions = [`installRoot=${installRoot}`];
|
|
236
|
+
const branches = listLocalBranches(installRoot);
|
|
230
237
|
const branchExists = branches.includes(selectedBranch);
|
|
231
238
|
let branchResult;
|
|
232
|
-
if (options.light) {
|
|
239
|
+
if (options.light || options.skipGit) {
|
|
233
240
|
branchResult = { branch: selectedBranch, created: false };
|
|
234
|
-
plannedActions.push("light mode: skipped git branch checkout");
|
|
241
|
+
plannedActions.push(options.skipGit ? "skipGit: skipped git branch checkout" : "light mode: skipped git branch checkout");
|
|
235
242
|
}
|
|
236
243
|
else if (options.dryRun) {
|
|
237
244
|
branchResult = { branch: selectedBranch, created: !branchExists };
|
|
@@ -240,9 +247,9 @@ async function setupProject(options) {
|
|
|
240
247
|
: `would create and checkout branch: ${selectedBranch}`);
|
|
241
248
|
}
|
|
242
249
|
else {
|
|
243
|
-
branchResult = ensureBranch(
|
|
250
|
+
branchResult = ensureBranch(installRoot, selectedBranch);
|
|
244
251
|
}
|
|
245
|
-
let environmentReady = await (0, opsxDelivery_1.isOpsxApplyReady)(
|
|
252
|
+
let environmentReady = await (0, opsxDelivery_1.isOpsxApplyReady)(installRoot);
|
|
246
253
|
let setupExecuted = false;
|
|
247
254
|
let skillInstalled = false;
|
|
248
255
|
let docsSkillInstalled = false;
|
|
@@ -255,7 +262,7 @@ async function setupProject(options) {
|
|
|
255
262
|
}
|
|
256
263
|
else if (!environmentReady) {
|
|
257
264
|
const ensured = await (0, opsxDelivery_1.ensureOpsxWorkflow)({
|
|
258
|
-
projectRoot:
|
|
265
|
+
projectRoot: installRoot,
|
|
259
266
|
agent: selectedAgent,
|
|
260
267
|
dryRun: options.dryRun,
|
|
261
268
|
});
|
|
@@ -267,23 +274,28 @@ async function setupProject(options) {
|
|
|
267
274
|
plannedActions.push("opsx apply stack already ready");
|
|
268
275
|
}
|
|
269
276
|
if (options.dryRun) {
|
|
270
|
-
for (const skillPath of allSkillInstallPaths(
|
|
277
|
+
for (const skillPath of allSkillInstallPaths(installRoot)) {
|
|
271
278
|
plannedActions.push(`would install skill: ${skillPath}`);
|
|
272
279
|
}
|
|
273
280
|
const { dir } = docSkillDirName(selectedKind);
|
|
274
|
-
plannedActions.push(`would install docs skill: ${path_1.default.join(
|
|
275
|
-
plannedActions.push(`would install opsx config: ${path_1.default.join(
|
|
276
|
-
plannedActions.push(`would install cursor rule: ${path_1.default.join(
|
|
281
|
+
plannedActions.push(`would install docs skill: ${path_1.default.join(installRoot, "docs", dir)}`);
|
|
282
|
+
plannedActions.push(`would install opsx config: ${path_1.default.join(installRoot, ".opsx", "config.json")}`);
|
|
283
|
+
plannedActions.push(`would install cursor rule: ${path_1.default.join(installRoot, ".cursor", "rules", "sdd-flow-kit-gates.mdc")}`);
|
|
277
284
|
}
|
|
278
285
|
else {
|
|
279
|
-
await
|
|
280
|
-
await
|
|
286
|
+
const pkgRoot = await (0, projectRoots_1.resolvePackageRoot)(installRoot);
|
|
287
|
+
const pnpmPatched = await (0, packageJsonPatch_1.ensurePnpmAllowSddFlowKitBuild)(path_1.default.join(pkgRoot, "package.json"));
|
|
288
|
+
if (pnpmPatched) {
|
|
289
|
+
plannedActions.push("added pnpm.onlyBuiltDependencies: sdd-flow-kit");
|
|
290
|
+
}
|
|
291
|
+
await installAgentSkill(installRoot, selectedKind);
|
|
292
|
+
await installSddCursorRule(installRoot);
|
|
281
293
|
skillInstalled = true;
|
|
282
|
-
await installDocsSkill(
|
|
294
|
+
await installDocsSkill(installRoot, selectedKind);
|
|
283
295
|
docsSkillInstalled = true;
|
|
284
|
-
await installOpsxConfig(
|
|
296
|
+
await installOpsxConfig(installRoot, selectedAgent);
|
|
285
297
|
opsxConfigInstalled = true;
|
|
286
|
-
const repaired = await (0, opsxDelivery_1.repairOpsxDeliveryScripts)(
|
|
298
|
+
const repaired = await (0, opsxDelivery_1.repairOpsxDeliveryScripts)(pkgRoot);
|
|
287
299
|
if (repaired) {
|
|
288
300
|
plannedActions.push("repaired package.json opsx:delivery-pipeline scripts (poquan → opsx)");
|
|
289
301
|
}
|
|
@@ -307,7 +319,7 @@ async function setupProject(options) {
|
|
|
307
319
|
...plannedActions.map((x) => `- ${x}`),
|
|
308
320
|
"",
|
|
309
321
|
].join("\n");
|
|
310
|
-
await (0, fs_1.writeTextFileEnsuredDir)(path_1.default.join(
|
|
322
|
+
await (0, fs_1.writeTextFileEnsuredDir)(path_1.default.join(installRoot, ".sdd-flow-kit", "setup-summary.md"), summary);
|
|
311
323
|
return {
|
|
312
324
|
agent: selectedAgent,
|
|
313
325
|
projectKind: selectedKind,
|
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)
|