sdd-flow-kit 1.0.25 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +49 -9
- package/dist/adapters/index.js +11 -10
- package/dist/cli.js +159 -36
- package/dist/core/fs.js +3 -3
- package/dist/core/gitDiffGuard.js +83 -0
- package/dist/core/inferProject.js +22 -20
- package/dist/core/nodeCompat.js +35 -0
- package/dist/core/openspecGate.js +83 -0
- package/dist/core/opsxDelivery.js +26 -25
- package/dist/core/prdArtifacts.js +53 -0
- package/dist/core/readlinePrompt.js +51 -0
- package/dist/core/sessionState.js +55 -0
- package/dist/core/templates.js +4 -4
- package/dist/core/utils.js +8 -1
- package/dist/core/writeNext.js +54 -0
- package/dist/index.js +13 -1
- package/dist/postinstall.js +10 -9
- package/dist/steps/doctor.js +13 -6
- package/dist/steps/invokeFlow.js +2 -1
- package/dist/steps/resolveRun.js +61 -0
- package/dist/steps/runDeliver.js +82 -0
- package/dist/steps/runFlow.js +3 -3
- package/dist/steps/runGate.js +220 -0
- package/dist/steps/runPhaseAdvance.js +195 -0
- package/dist/steps/runPropose.js +82 -0
- package/dist/steps/setupProject.js +34 -31
- package/dist/steps/startFlow.js +27 -33
- package/package.json +4 -1
- package/src/templates/artifacts/06-agent-skill.template.md +49 -124
- package/src/templates/rules/sdd-no-implement-until-apply.mdc.template +32 -33
|
@@ -7,8 +7,8 @@ exports.PRODUCT_PREFIX_BY_KIND = exports.DOC_SKILL_BY_KIND = exports.ALLOWED_PRO
|
|
|
7
7
|
exports.productToKind = productToKind;
|
|
8
8
|
exports.resolveDocSkillDir = resolveDocSkillDir;
|
|
9
9
|
exports.inferProjectKind = inferProjectKind;
|
|
10
|
-
const promises_1 = __importDefault(require("
|
|
11
|
-
const
|
|
10
|
+
const promises_1 = __importDefault(require("fs/promises"));
|
|
11
|
+
const path_1 = __importDefault(require("path"));
|
|
12
12
|
exports.ALLOWED_PROJECTS = ["OMS", "ADI", "欢盟", "AD Tools"];
|
|
13
13
|
exports.DOC_SKILL_BY_KIND = {
|
|
14
14
|
ADI: "adi-doc-skill",
|
|
@@ -37,24 +37,24 @@ function productToKind(product) {
|
|
|
37
37
|
/** 从 projectRoot 向上查找 docs/*-doc-skill(适配 frontend 子目录 + 根目录 docs) */
|
|
38
38
|
async function resolveDocSkillDir(projectRoot, kind) {
|
|
39
39
|
const dirName = exports.DOC_SKILL_BY_KIND[kind];
|
|
40
|
-
let cur =
|
|
40
|
+
let cur = path_1.default.resolve(projectRoot);
|
|
41
41
|
for (let i = 0; i < 8; i += 1) {
|
|
42
|
-
const candidate =
|
|
43
|
-
if (await pathExists(
|
|
44
|
-
const rel =
|
|
42
|
+
const candidate = path_1.default.join(cur, "docs", dirName);
|
|
43
|
+
if (await pathExists(path_1.default.join(candidate, "confluence-doc.py"))) {
|
|
44
|
+
const rel = path_1.default.relative(projectRoot, candidate);
|
|
45
45
|
return {
|
|
46
46
|
absPath: candidate,
|
|
47
|
-
relFromRoot: rel.startsWith("..") ? rel :
|
|
47
|
+
relFromRoot: rel.startsWith("..") ? rel : path_1.default.join("docs", dirName),
|
|
48
48
|
};
|
|
49
49
|
}
|
|
50
|
-
const parent =
|
|
50
|
+
const parent = path_1.default.dirname(cur);
|
|
51
51
|
if (parent === cur)
|
|
52
52
|
break;
|
|
53
53
|
cur = parent;
|
|
54
54
|
}
|
|
55
55
|
return {
|
|
56
|
-
absPath:
|
|
57
|
-
relFromRoot:
|
|
56
|
+
absPath: path_1.default.join(projectRoot, "docs", dirName),
|
|
57
|
+
relFromRoot: path_1.default.join("docs", dirName),
|
|
58
58
|
};
|
|
59
59
|
}
|
|
60
60
|
async function pathExists(filePath) {
|
|
@@ -87,12 +87,12 @@ async function inferFromExistingDocSkill(projectRoot) {
|
|
|
87
87
|
let dir = projectRoot;
|
|
88
88
|
for (let i = 0; i < 8; i += 1) {
|
|
89
89
|
for (const kind of exports.ALLOWED_PROJECTS) {
|
|
90
|
-
const rel =
|
|
91
|
-
if (await pathExists(
|
|
90
|
+
const rel = path_1.default.join(dir, "docs", exports.DOC_SKILL_BY_KIND[kind]);
|
|
91
|
+
if (await pathExists(path_1.default.join(rel, "SKILL.md"))) {
|
|
92
92
|
return kind;
|
|
93
93
|
}
|
|
94
94
|
}
|
|
95
|
-
const parent =
|
|
95
|
+
const parent = path_1.default.dirname(dir);
|
|
96
96
|
if (parent === dir)
|
|
97
97
|
break;
|
|
98
98
|
dir = parent;
|
|
@@ -101,10 +101,10 @@ async function inferFromExistingDocSkill(projectRoot) {
|
|
|
101
101
|
}
|
|
102
102
|
/** 从目录路径(含父级)推断产品类型 */
|
|
103
103
|
function inferFromPathSegments(projectRoot) {
|
|
104
|
-
const resolved =
|
|
105
|
-
const parts = resolved.split(
|
|
104
|
+
const resolved = path_1.default.resolve(projectRoot);
|
|
105
|
+
const parts = resolved.split(path_1.default.sep);
|
|
106
106
|
for (let i = parts.length; i > 0; i -= 1) {
|
|
107
|
-
const segment = parts.slice(0, i).join(
|
|
107
|
+
const segment = parts.slice(0, i).join(path_1.default.sep);
|
|
108
108
|
const hit = matchKindInText(segment);
|
|
109
109
|
if (hit)
|
|
110
110
|
return hit;
|
|
@@ -112,10 +112,11 @@ function inferFromPathSegments(projectRoot) {
|
|
|
112
112
|
return null;
|
|
113
113
|
}
|
|
114
114
|
async function inferFromPackageJson(dir) {
|
|
115
|
+
var _a, _b;
|
|
115
116
|
try {
|
|
116
|
-
const pkgText = await promises_1.default.readFile(
|
|
117
|
+
const pkgText = await promises_1.default.readFile(path_1.default.join(dir, "package.json"), "utf8");
|
|
117
118
|
const pkg = JSON.parse(pkgText);
|
|
118
|
-
const blob = `${pkg.name
|
|
119
|
+
const blob = `${(_a = pkg.name) !== null && _a !== void 0 ? _a : ""} ${(_b = pkg.description) !== null && _b !== void 0 ? _b : ""}`;
|
|
119
120
|
return matchKindInText(blob);
|
|
120
121
|
}
|
|
121
122
|
catch {
|
|
@@ -127,7 +128,8 @@ async function inferFromPackageJson(dir) {
|
|
|
127
128
|
* monorepo 子目录(如 adInsight-web/frontend)会向上扫描路径,避免误判为 OMS。
|
|
128
129
|
*/
|
|
129
130
|
async function inferProjectKind(projectRoot) {
|
|
130
|
-
|
|
131
|
+
var _a;
|
|
132
|
+
const fromEnv = ((_a = process.env.SDD_FLOW_KIT_PROJECT) !== null && _a !== void 0 ? _a : "").trim();
|
|
131
133
|
if (exports.ALLOWED_PROJECTS.includes(fromEnv)) {
|
|
132
134
|
return fromEnv;
|
|
133
135
|
}
|
|
@@ -140,7 +142,7 @@ async function inferProjectKind(projectRoot) {
|
|
|
140
142
|
const fromPkg = await inferFromPackageJson(dir);
|
|
141
143
|
if (fromPkg)
|
|
142
144
|
return fromPkg;
|
|
143
|
-
const parent =
|
|
145
|
+
const parent = path_1.default.dirname(dir);
|
|
144
146
|
if (parent === dir)
|
|
145
147
|
break;
|
|
146
148
|
dir = parent;
|
|
@@ -0,0 +1,35 @@
|
|
|
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.MIN_NODE_PATCH = exports.MIN_NODE_MINOR = exports.MIN_NODE_MAJOR = void 0;
|
|
7
|
+
exports.parseNodeSemver = parseNodeSemver;
|
|
8
|
+
exports.isNodeVersionSupported = isNodeVersionSupported;
|
|
9
|
+
exports.nodeVersionRequirementLabel = nodeVersionRequirementLabel;
|
|
10
|
+
const process_1 = __importDefault(require("process"));
|
|
11
|
+
/** 与 package.json engines.node 对齐 */
|
|
12
|
+
exports.MIN_NODE_MAJOR = 14;
|
|
13
|
+
exports.MIN_NODE_MINOR = 16;
|
|
14
|
+
exports.MIN_NODE_PATCH = 0;
|
|
15
|
+
function parseNodeSemver() {
|
|
16
|
+
const m = process_1.default.version.match(/^v(\d+)\.(\d+)\.(\d+)/);
|
|
17
|
+
if (!m)
|
|
18
|
+
return { major: 0, minor: 0, patch: 0 };
|
|
19
|
+
return { major: Number(m[1]), minor: Number(m[2]), patch: Number(m[3]) };
|
|
20
|
+
}
|
|
21
|
+
function isNodeVersionSupported() {
|
|
22
|
+
const v = parseNodeSemver();
|
|
23
|
+
if (v.major > exports.MIN_NODE_MAJOR)
|
|
24
|
+
return true;
|
|
25
|
+
if (v.major < exports.MIN_NODE_MAJOR)
|
|
26
|
+
return false;
|
|
27
|
+
if (v.minor > exports.MIN_NODE_MINOR)
|
|
28
|
+
return true;
|
|
29
|
+
if (v.minor < exports.MIN_NODE_MINOR)
|
|
30
|
+
return false;
|
|
31
|
+
return v.patch >= exports.MIN_NODE_PATCH;
|
|
32
|
+
}
|
|
33
|
+
function nodeVersionRequirementLabel() {
|
|
34
|
+
return `>= ${exports.MIN_NODE_MAJOR}.${exports.MIN_NODE_MINOR}.${exports.MIN_NODE_PATCH}`;
|
|
35
|
+
}
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.checkOpenspecApplyReady = checkOpenspecApplyReady;
|
|
7
|
+
exports.checkTasksAllDone = checkTasksAllDone;
|
|
8
|
+
exports.changeDirExists = changeDirExists;
|
|
9
|
+
const child_process_1 = require("child_process");
|
|
10
|
+
const promises_1 = __importDefault(require("fs/promises"));
|
|
11
|
+
const path_1 = __importDefault(require("path"));
|
|
12
|
+
const fs_1 = require("./fs");
|
|
13
|
+
/**
|
|
14
|
+
* 解析 openspec status --json,判断 change 是否 apply-ready。
|
|
15
|
+
*/
|
|
16
|
+
function checkOpenspecApplyReady(projectRoot, changeName) {
|
|
17
|
+
const ret = (0, child_process_1.spawnSync)("openspec", ["status", "--change", changeName, "--json"], {
|
|
18
|
+
cwd: projectRoot,
|
|
19
|
+
encoding: "utf8",
|
|
20
|
+
});
|
|
21
|
+
if (ret.status !== 0) {
|
|
22
|
+
return {
|
|
23
|
+
ok: false,
|
|
24
|
+
changeName,
|
|
25
|
+
applyReady: false,
|
|
26
|
+
detail: ret.stderr || ret.stdout || "openspec status 失败",
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
try {
|
|
30
|
+
const json = JSON.parse(ret.stdout || "{}");
|
|
31
|
+
const requires = json.applyRequires || [];
|
|
32
|
+
const artifacts = json.artifacts || [];
|
|
33
|
+
const pending = requires.filter((id) => {
|
|
34
|
+
const art = artifacts.find((a) => a.id === id);
|
|
35
|
+
return !art || art.status !== "done";
|
|
36
|
+
});
|
|
37
|
+
const applyReady = pending.length === 0 && requires.length > 0;
|
|
38
|
+
return {
|
|
39
|
+
ok: true,
|
|
40
|
+
changeName,
|
|
41
|
+
applyReady,
|
|
42
|
+
detail: applyReady
|
|
43
|
+
? "apply-ready"
|
|
44
|
+
: `待完成 artifacts: ${pending.join(", ") || requires.join(", ")}`,
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
catch (e) {
|
|
48
|
+
return {
|
|
49
|
+
ok: false,
|
|
50
|
+
changeName,
|
|
51
|
+
applyReady: false,
|
|
52
|
+
detail: `无法解析 openspec status JSON: ${e instanceof Error ? e.message : String(e)}`,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* 检查 tasks.md 是否全部勾选完成。
|
|
58
|
+
*/
|
|
59
|
+
async function checkTasksAllDone(projectRoot, changeName) {
|
|
60
|
+
const tasksPath = path_1.default.join(projectRoot, "openspec", "changes", changeName, "tasks.md");
|
|
61
|
+
const content = await (0, fs_1.readFileIfExists)(tasksPath);
|
|
62
|
+
if (!content) {
|
|
63
|
+
return { ok: false, detail: `缺少 tasks.md: ${tasksPath}` };
|
|
64
|
+
}
|
|
65
|
+
const open = content.match(/^- \[ \]/gm);
|
|
66
|
+
if (open && open.length > 0) {
|
|
67
|
+
return { ok: false, detail: `tasks.md 仍有 ${open.length} 项未完成` };
|
|
68
|
+
}
|
|
69
|
+
const hasTask = /- \[[xX]\]/.test(content);
|
|
70
|
+
if (!hasTask) {
|
|
71
|
+
return { ok: false, detail: "tasks.md 无已勾选任务" };
|
|
72
|
+
}
|
|
73
|
+
return { ok: true, detail: "tasks.md 已全部勾选" };
|
|
74
|
+
}
|
|
75
|
+
async function changeDirExists(projectRoot, changeName) {
|
|
76
|
+
try {
|
|
77
|
+
await promises_1.default.access(path_1.default.join(projectRoot, "openspec", "changes", changeName));
|
|
78
|
+
return true;
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
return false;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
@@ -11,9 +11,9 @@ exports.buildOpsxWorkflowSetupCommand = buildOpsxWorkflowSetupCommand;
|
|
|
11
11
|
exports.isOpsxApplyReady = isOpsxApplyReady;
|
|
12
12
|
exports.ensureOpsxWorkflow = ensureOpsxWorkflow;
|
|
13
13
|
exports.collectDeliverySetupChecks = collectDeliverySetupChecks;
|
|
14
|
-
const
|
|
15
|
-
const promises_1 = __importDefault(require("
|
|
16
|
-
const
|
|
14
|
+
const child_process_1 = require("child_process");
|
|
15
|
+
const promises_1 = __importDefault(require("fs/promises"));
|
|
16
|
+
const path_1 = __importDefault(require("path"));
|
|
17
17
|
const fs_1 = require("./fs");
|
|
18
18
|
const DELIVERY_SCRIPT_REL = ".cursor/skills/opsx-dev-delivery-pipeline/scripts/opsx-delivery-pipeline.sh";
|
|
19
19
|
function deliveryPipelineNpmScripts() {
|
|
@@ -32,24 +32,24 @@ async function pathExists(filePath) {
|
|
|
32
32
|
}
|
|
33
33
|
/** 优先使用已安装 OPSX skills 的 editor 目录 */
|
|
34
34
|
async function resolveOpsxAgent(projectRoot, preferred) {
|
|
35
|
-
const deliverySkill =
|
|
35
|
+
const deliverySkill = path_1.default.join(projectRoot, ".cursor", "skills", "opsx-dev-delivery-pipeline", "SKILL.md");
|
|
36
36
|
if (await pathExists(deliverySkill))
|
|
37
37
|
return "cursor";
|
|
38
|
-
const claudeDelivery =
|
|
38
|
+
const claudeDelivery = path_1.default.join(projectRoot, ".claude", "skills", "opsx-dev-delivery-pipeline", "SKILL.md");
|
|
39
39
|
if (await pathExists(claudeDelivery))
|
|
40
40
|
return "claude-code";
|
|
41
|
-
if (await pathExists(
|
|
41
|
+
if (await pathExists(path_1.default.join(projectRoot, ".cursor")))
|
|
42
42
|
return "cursor";
|
|
43
|
-
if (await pathExists(
|
|
43
|
+
if (await pathExists(path_1.default.join(projectRoot, ".claude")))
|
|
44
44
|
return "claude-code";
|
|
45
45
|
return preferred;
|
|
46
46
|
}
|
|
47
47
|
/** 将 package.json 中过期的 poquan-delivery-pipeline 脚本修正为 opsx-dev-delivery-pipeline */
|
|
48
48
|
async function repairOpsxDeliveryScripts(projectRoot) {
|
|
49
|
-
const pkgPath =
|
|
49
|
+
const pkgPath = path_1.default.join(projectRoot, "package.json");
|
|
50
50
|
if (!(await pathExists(pkgPath)))
|
|
51
51
|
return false;
|
|
52
|
-
const scriptOnDisk =
|
|
52
|
+
const scriptOnDisk = path_1.default.join(projectRoot, DELIVERY_SCRIPT_REL);
|
|
53
53
|
if (!(await pathExists(scriptOnDisk)))
|
|
54
54
|
return false;
|
|
55
55
|
const { full, short } = deliveryPipelineNpmScripts();
|
|
@@ -73,7 +73,7 @@ async function repairOpsxDeliveryScripts(projectRoot) {
|
|
|
73
73
|
return true;
|
|
74
74
|
}
|
|
75
75
|
function hasCommand(cmd) {
|
|
76
|
-
return (0,
|
|
76
|
+
return (0, child_process_1.spawnSync)("which", [cmd], { stdio: "ignore" }).status === 0;
|
|
77
77
|
}
|
|
78
78
|
function buildOpsxWorkflowSetupCommand(projectRoot, agent) {
|
|
79
79
|
const baseArgs = ["@lixin5257xxx/opsx-workflow", "setup"];
|
|
@@ -120,7 +120,7 @@ async function ensureOpsxWorkflow(options) {
|
|
|
120
120
|
plannedActions,
|
|
121
121
|
};
|
|
122
122
|
}
|
|
123
|
-
const ret = (0,
|
|
123
|
+
const ret = (0, child_process_1.spawnSync)(cmd, args, {
|
|
124
124
|
cwd: options.projectRoot,
|
|
125
125
|
stdio: "inherit",
|
|
126
126
|
env: process.env,
|
|
@@ -142,10 +142,11 @@ async function ensureOpsxWorkflow(options) {
|
|
|
142
142
|
};
|
|
143
143
|
}
|
|
144
144
|
async function collectDeliverySetupChecks(projectRoot) {
|
|
145
|
+
var _a, _b, _c, _d, _e, _f;
|
|
145
146
|
const checks = [];
|
|
146
147
|
checks.push({
|
|
147
148
|
name: "openspec-dir",
|
|
148
|
-
ok: await pathExists(
|
|
149
|
+
ok: await pathExists(path_1.default.join(projectRoot, "openspec")),
|
|
149
150
|
detail: "openspec/ 目录",
|
|
150
151
|
});
|
|
151
152
|
checks.push({
|
|
@@ -153,34 +154,34 @@ async function collectDeliverySetupChecks(projectRoot) {
|
|
|
153
154
|
ok: hasCommand("openspec"),
|
|
154
155
|
detail: "openspec CLI(npx openspec 或全局安装)",
|
|
155
156
|
});
|
|
156
|
-
const skillPath =
|
|
157
|
+
const skillPath = path_1.default.join(projectRoot, ".cursor", "skills", "opsx-dev-delivery-pipeline", "SKILL.md");
|
|
157
158
|
checks.push({
|
|
158
159
|
name: "opsx-dev-delivery-pipeline-skill",
|
|
159
160
|
ok: await pathExists(skillPath),
|
|
160
161
|
detail: skillPath,
|
|
161
162
|
});
|
|
162
|
-
const tddPath =
|
|
163
|
+
const tddPath = path_1.default.join(projectRoot, ".cursor", "skills", "tdd-script", "SKILL.md");
|
|
163
164
|
checks.push({
|
|
164
165
|
name: "tdd-script-skill",
|
|
165
166
|
ok: await pathExists(tddPath),
|
|
166
167
|
detail: tddPath,
|
|
167
168
|
});
|
|
168
|
-
const applyPath =
|
|
169
|
+
const applyPath = path_1.default.join(projectRoot, ".cursor", "skills", "openspec-apply-change", "SKILL.md");
|
|
169
170
|
checks.push({
|
|
170
171
|
name: "openspec-apply-change-skill",
|
|
171
172
|
ok: await pathExists(applyPath),
|
|
172
173
|
detail: applyPath,
|
|
173
174
|
});
|
|
174
|
-
const pipelineScript =
|
|
175
|
+
const pipelineScript = path_1.default.join(projectRoot, DELIVERY_SCRIPT_REL);
|
|
175
176
|
checks.push({
|
|
176
177
|
name: "delivery-pipeline-script",
|
|
177
178
|
ok: await pathExists(pipelineScript),
|
|
178
179
|
detail: pipelineScript,
|
|
179
180
|
});
|
|
180
181
|
try {
|
|
181
|
-
const pkgText = await promises_1.default.readFile(
|
|
182
|
+
const pkgText = await promises_1.default.readFile(path_1.default.join(projectRoot, "package.json"), "utf8");
|
|
182
183
|
const pkg = JSON.parse(pkgText);
|
|
183
|
-
const cmd = pkg.scripts
|
|
184
|
+
const cmd = (_b = (_a = pkg.scripts) === null || _a === void 0 ? void 0 : _a["opsx:delivery-pipeline"]) !== null && _b !== void 0 ? _b : "";
|
|
184
185
|
const ok = cmd.includes("opsx-dev-delivery-pipeline") &&
|
|
185
186
|
cmd.includes("opsx-delivery-pipeline.sh") &&
|
|
186
187
|
!cmd.includes("poquan");
|
|
@@ -198,11 +199,11 @@ async function collectDeliverySetupChecks(projectRoot) {
|
|
|
198
199
|
});
|
|
199
200
|
}
|
|
200
201
|
try {
|
|
201
|
-
const cfgText = await promises_1.default.readFile(
|
|
202
|
+
const cfgText = await promises_1.default.readFile(path_1.default.join(projectRoot, ".opsx", "config.json"), "utf8");
|
|
202
203
|
const cfg = JSON.parse(cfgText);
|
|
203
|
-
const skillsDir = cfg.skillsDir
|
|
204
|
-
const hasCursorDelivery = await pathExists(
|
|
205
|
-
const nameOk = cfg.deliveryPipeline
|
|
204
|
+
const skillsDir = (_c = cfg.skillsDir) !== null && _c !== void 0 ? _c : "";
|
|
205
|
+
const hasCursorDelivery = await pathExists(path_1.default.join(projectRoot, ".cursor", "skills", "opsx-dev-delivery-pipeline", "SKILL.md"));
|
|
206
|
+
const nameOk = ((_d = cfg.deliveryPipeline) === null || _d === void 0 ? void 0 : _d.skillName) === "opsx-dev-delivery-pipeline";
|
|
206
207
|
const dirOk = (hasCursorDelivery && skillsDir.includes(".cursor")) ||
|
|
207
208
|
(!hasCursorDelivery && (skillsDir.includes(".cursor") || skillsDir.includes(".claude")));
|
|
208
209
|
checks.push({
|
|
@@ -210,7 +211,7 @@ async function collectDeliverySetupChecks(projectRoot) {
|
|
|
210
211
|
ok: nameOk && dirOk,
|
|
211
212
|
detail: hasCursorDelivery && !skillsDir.includes(".cursor")
|
|
212
213
|
? `skills 在 .cursor 但 config 指向 ${skillsDir},请 npx sdd-flow-kit install -y`
|
|
213
|
-
: `skillsDir=${skillsDir}, skillName=${cfg.deliveryPipeline
|
|
214
|
+
: `skillsDir=${skillsDir}, skillName=${(_f = (_e = cfg.deliveryPipeline) === null || _e === void 0 ? void 0 : _e.skillName) !== null && _f !== void 0 ? _f : ""}`,
|
|
214
215
|
});
|
|
215
216
|
}
|
|
216
217
|
catch {
|
|
@@ -221,7 +222,7 @@ async function collectDeliverySetupChecks(projectRoot) {
|
|
|
221
222
|
});
|
|
222
223
|
}
|
|
223
224
|
try {
|
|
224
|
-
const pkgText = await promises_1.default.readFile(
|
|
225
|
+
const pkgText = await promises_1.default.readFile(path_1.default.join(projectRoot, "package.json"), "utf8");
|
|
225
226
|
const pkg = JSON.parse(pkgText);
|
|
226
227
|
const deps = { ...pkg.dependencies, ...pkg.devDependencies };
|
|
227
228
|
const hasPw = Boolean(deps["@playwright/test"] || deps.playwright);
|
|
@@ -238,7 +239,7 @@ async function collectDeliverySetupChecks(projectRoot) {
|
|
|
238
239
|
detail: "无法读取 package.json 检查 Playwright",
|
|
239
240
|
});
|
|
240
241
|
}
|
|
241
|
-
const agentsMd =
|
|
242
|
+
const agentsMd = path_1.default.join(projectRoot, "AGENTS.md");
|
|
242
243
|
checks.push({
|
|
243
244
|
name: "agents-md",
|
|
244
245
|
ok: await pathExists(agentsMd),
|
|
@@ -0,0 +1,53 @@
|
|
|
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.isPrdFetched = isPrdFetched;
|
|
7
|
+
exports.isQuestionsClosed = isQuestionsClosed;
|
|
8
|
+
exports.areAnalysisDocsReady = areAnalysisDocsReady;
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const fs_1 = require("./fs");
|
|
11
|
+
const PRD_PLACEHOLDER_MARKERS = [
|
|
12
|
+
"请把《PRD》",
|
|
13
|
+
"占位",
|
|
14
|
+
"placeholder",
|
|
15
|
+
];
|
|
16
|
+
async function isPrdFetched(outputRoot) {
|
|
17
|
+
const prdPath = path_1.default.join(outputRoot, "source", "PRD.md");
|
|
18
|
+
const content = await (0, fs_1.readFileIfExists)(prdPath);
|
|
19
|
+
if (!content || content.trim().length < 80) {
|
|
20
|
+
return { ok: false, detail: "source/PRD.md 缺失或过短" };
|
|
21
|
+
}
|
|
22
|
+
if (PRD_PLACEHOLDER_MARKERS.some((m) => content.includes(m))) {
|
|
23
|
+
return { ok: false, detail: "source/PRD.md 仍为占位内容,请先执行 Step1 拉取 PRD" };
|
|
24
|
+
}
|
|
25
|
+
return { ok: true, detail: "PRD 已回填" };
|
|
26
|
+
}
|
|
27
|
+
async function isQuestionsClosed(outputRoot) {
|
|
28
|
+
const p = path_1.default.join(outputRoot, "03-待确认问题清单.md");
|
|
29
|
+
const content = await (0, fs_1.readFileIfExists)(p);
|
|
30
|
+
if (!content) {
|
|
31
|
+
return { ok: false, detail: "缺少 03-待确认问题清单.md" };
|
|
32
|
+
}
|
|
33
|
+
if (/矛盾点池状态[::]\s*[`「]*未闭合/.test(content)) {
|
|
34
|
+
return { ok: false, detail: "矛盾点池状态:未闭合" };
|
|
35
|
+
}
|
|
36
|
+
if (!/矛盾点池状态[::]\s*[`「]*已闭合/.test(content)) {
|
|
37
|
+
return { ok: false, detail: "03 未标注「矛盾点池状态:已闭合」" };
|
|
38
|
+
}
|
|
39
|
+
return { ok: true, detail: "矛盾点池已闭合" };
|
|
40
|
+
}
|
|
41
|
+
async function areAnalysisDocsReady(outputRoot) {
|
|
42
|
+
const files = ["01-需求分析报告.md", "02-改动点清单.md", "04-技术文档草稿.md"];
|
|
43
|
+
for (const name of files) {
|
|
44
|
+
const content = await (0, fs_1.readFileIfExists)(path_1.default.join(outputRoot, name));
|
|
45
|
+
if (!content || content.trim().length < 120) {
|
|
46
|
+
return { ok: false, detail: `${name} 缺失或过短(阶段 B 未就绪)` };
|
|
47
|
+
}
|
|
48
|
+
if (name === "01-需求分析报告.md" && content.includes("(由 AI 工具生成")) {
|
|
49
|
+
return { ok: false, detail: `${name} 仍为模板占位` };
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
return { ok: true, detail: "01/02/04 文档齐全" };
|
|
53
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
36
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
|
+
};
|
|
38
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
+
exports.createPromptInterface = createPromptInterface;
|
|
40
|
+
exports.promptLine = promptLine;
|
|
41
|
+
const readline = __importStar(require("readline"));
|
|
42
|
+
const process_1 = __importDefault(require("process"));
|
|
43
|
+
/** Node 14 兼容:不使用 node:readline/promises(需 Node 17+) */
|
|
44
|
+
function createPromptInterface() {
|
|
45
|
+
return readline.createInterface({ input: process_1.default.stdin, output: process_1.default.stdout });
|
|
46
|
+
}
|
|
47
|
+
function promptLine(rl, query) {
|
|
48
|
+
return new Promise((resolve) => {
|
|
49
|
+
rl.question(query, (answer) => resolve(answer));
|
|
50
|
+
});
|
|
51
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
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.readSession = readSession;
|
|
7
|
+
exports.writeSession = writeSession;
|
|
8
|
+
exports.defaultSession = defaultSession;
|
|
9
|
+
exports.normalizeSession = normalizeSession;
|
|
10
|
+
const path_1 = __importDefault(require("path"));
|
|
11
|
+
const fs_1 = require("./fs");
|
|
12
|
+
const SESSION_FILE = ".session-state.json";
|
|
13
|
+
async function readSession(outputRoot) {
|
|
14
|
+
const raw = await (0, fs_1.readFileIfExists)(path_1.default.join(outputRoot, SESSION_FILE));
|
|
15
|
+
if (!raw)
|
|
16
|
+
return null;
|
|
17
|
+
return JSON.parse(raw);
|
|
18
|
+
}
|
|
19
|
+
async function writeSession(outputRoot, state) {
|
|
20
|
+
await (0, fs_1.writeTextFileEnsuredDir)(path_1.default.join(outputRoot, SESSION_FILE), `${JSON.stringify(state, null, 2)}\n`);
|
|
21
|
+
}
|
|
22
|
+
function defaultSession(args) {
|
|
23
|
+
return {
|
|
24
|
+
product: args.product,
|
|
25
|
+
version: args.version,
|
|
26
|
+
runId: args.runId,
|
|
27
|
+
outputRoot: args.outputRoot,
|
|
28
|
+
phase: "A",
|
|
29
|
+
status: "awaiting_user_confirmation",
|
|
30
|
+
docModeOnly: true,
|
|
31
|
+
autoChainOpsx: true,
|
|
32
|
+
activeChange: null,
|
|
33
|
+
createdAtISO: new Date().toISOString(),
|
|
34
|
+
implGitBaseline: null,
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
/** 将旧版 session(无 phase 字段)迁移为当前结构 */
|
|
38
|
+
function normalizeSession(raw, outputRoot) {
|
|
39
|
+
var _a, _b, _c, _d, _e;
|
|
40
|
+
const phase = raw.phase || "A";
|
|
41
|
+
const status = raw.status || "awaiting_user_confirmation";
|
|
42
|
+
return {
|
|
43
|
+
product: String((_a = raw.product) !== null && _a !== void 0 ? _a : ""),
|
|
44
|
+
version: String((_b = raw.version) !== null && _b !== void 0 ? _b : ""),
|
|
45
|
+
runId: String((_c = raw.runId) !== null && _c !== void 0 ? _c : ""),
|
|
46
|
+
outputRoot: String((_d = raw.outputRoot) !== null && _d !== void 0 ? _d : outputRoot),
|
|
47
|
+
phase,
|
|
48
|
+
status,
|
|
49
|
+
docModeOnly: raw.docModeOnly !== false,
|
|
50
|
+
autoChainOpsx: raw.autoChainOpsx !== false,
|
|
51
|
+
activeChange: raw.activeChange ? String(raw.activeChange) : null,
|
|
52
|
+
createdAtISO: String((_e = raw.createdAtISO) !== null && _e !== void 0 ? _e : new Date().toISOString()),
|
|
53
|
+
implGitBaseline: raw.implGitBaseline ? String(raw.implGitBaseline) : null,
|
|
54
|
+
};
|
|
55
|
+
}
|
package/dist/core/templates.js
CHANGED
|
@@ -4,16 +4,16 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.loadTemplateText = loadTemplateText;
|
|
7
|
-
const
|
|
7
|
+
const path_1 = __importDefault(require("path"));
|
|
8
8
|
const fs_1 = require("./fs");
|
|
9
9
|
function resolveCandidatePaths(relPathFromPackageRoot) {
|
|
10
10
|
// 在 TS 开发态:<pkg>/src/templates/xxx
|
|
11
11
|
// 在编译态:<pkg>/dist/cli.js -> __dirname=<pkg>/dist,所以 src/templates 要向上找两级
|
|
12
12
|
const here = __dirname; // src/core/* 或 dist/*
|
|
13
|
-
const pkgRoot =
|
|
13
|
+
const pkgRoot = path_1.default.resolve(here, "../.."); // 兼容 src/ 与 dist/
|
|
14
14
|
return [
|
|
15
|
-
|
|
16
|
-
|
|
15
|
+
path_1.default.join(pkgRoot, "src", "templates", relPathFromPackageRoot),
|
|
16
|
+
path_1.default.join(pkgRoot, "dist", "templates", relPathFromPackageRoot),
|
|
17
17
|
];
|
|
18
18
|
}
|
|
19
19
|
async function loadTemplateText(relPathFromPackageRoot) {
|
package/dist/core/utils.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.formatYYYYMMDD = formatYYYYMMDD;
|
|
4
|
+
exports.replaceAllStr = replaceAllStr;
|
|
4
5
|
exports.ensurePosixRelativePath = ensurePosixRelativePath;
|
|
5
6
|
/**
|
|
6
7
|
* 生成 YYYYMMDD 字符串,用于 openspec/PRD 目录命名。
|
|
@@ -11,7 +12,13 @@ function formatYYYYMMDD(d) {
|
|
|
11
12
|
const dd = String(d.getDate()).padStart(2, "0");
|
|
12
13
|
return `${yyyy}${mm}${dd}`;
|
|
13
14
|
}
|
|
15
|
+
/** Node 14 无 String.prototype.replaceAll(需 Node 15+) */
|
|
16
|
+
function replaceAllStr(text, search, replacement) {
|
|
17
|
+
if (!search)
|
|
18
|
+
return text;
|
|
19
|
+
return text.split(search).join(replacement);
|
|
20
|
+
}
|
|
14
21
|
function ensurePosixRelativePath(p) {
|
|
15
22
|
// 用于模板占位符中展示,避免反斜杠导致工具误解析。
|
|
16
|
-
return p
|
|
23
|
+
return replaceAllStr(p, "\\", "/");
|
|
17
24
|
}
|
|
@@ -0,0 +1,54 @@
|
|
|
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.writeInitialNext = writeInitialNext;
|
|
7
|
+
const path_1 = __importDefault(require("path"));
|
|
8
|
+
const fs_1 = require("./fs");
|
|
9
|
+
/**
|
|
10
|
+
* run 初始化后写入 NEXT.md,强制 AI 按脚本推进而非自然语言跳步。
|
|
11
|
+
*/
|
|
12
|
+
async function writeInitialNext(outputRoot, projectRoot, runId) {
|
|
13
|
+
const pr = projectRoot.replace(/\\/g, "/");
|
|
14
|
+
const lines = [
|
|
15
|
+
"# NEXT(脚本门禁 — 必须按顺序执行)",
|
|
16
|
+
"",
|
|
17
|
+
"> **禁止**跳过下列命令。失败 (exit≠0) 时不得进入下一阶段或修改 `src/`。",
|
|
18
|
+
"",
|
|
19
|
+
"## 1. Step1 — 拉取 PRD",
|
|
20
|
+
"按 `01-获取需求文档指引.md` 执行 confluence 脚本,然后:",
|
|
21
|
+
"```bash",
|
|
22
|
+
`npx sdd-flow-kit gate --project-root "${pr}" --run-id ${runId} --expect prd-fetched`,
|
|
23
|
+
`npx sdd-flow-kit phase advance --project-root "${pr}" --run-id ${runId} --to after-prd`,
|
|
24
|
+
"```",
|
|
25
|
+
"",
|
|
26
|
+
"## 2. Step2 — 需求分析(提问模式)",
|
|
27
|
+
"执行 `02-需求分析提示词.md`。未闭合时仅改 `03`,并确认:",
|
|
28
|
+
"```bash",
|
|
29
|
+
`npx sdd-flow-kit gate --project-root "${pr}" --run-id ${runId} --expect questions-open`,
|
|
30
|
+
"```",
|
|
31
|
+
"",
|
|
32
|
+
"## 3. 用户确认后 — 阶段 B(仅文档)",
|
|
33
|
+
"生成 01/02/04 后:",
|
|
34
|
+
"```bash",
|
|
35
|
+
`npx sdd-flow-kit gate --project-root "${pr}" --run-id ${runId} --expect docs-closed`,
|
|
36
|
+
`npx sdd-flow-kit phase advance --project-root "${pr}" --run-id ${runId} --to docs-done`,
|
|
37
|
+
"```",
|
|
38
|
+
"",
|
|
39
|
+
"## 4. 阶段 C — propose",
|
|
40
|
+
"```bash",
|
|
41
|
+
`npx sdd-flow-kit propose --project-root "${pr}" --run-id ${runId} --change <kebab-name>`,
|
|
42
|
+
`npx sdd-flow-kit phase advance --project-root "${pr}" --run-id ${runId} --to propose-done --change <kebab-name>`,
|
|
43
|
+
"```",
|
|
44
|
+
"",
|
|
45
|
+
"## 5. 阶段 D — 实现 + 交付",
|
|
46
|
+
"```bash",
|
|
47
|
+
`npx sdd-flow-kit phase advance --project-root "${pr}" --run-id ${runId} --to impl --change <kebab-name>`,
|
|
48
|
+
`npx sdd-flow-kit deliver --project-root "${pr}" --run-id ${runId} --change <kebab-name>`,
|
|
49
|
+
`npx sdd-flow-kit phase advance --project-root "${pr}" --run-id ${runId} --to shipped --change <kebab-name>`,
|
|
50
|
+
"```",
|
|
51
|
+
"",
|
|
52
|
+
];
|
|
53
|
+
await (0, fs_1.writeTextFileEnsuredDir)(path_1.default.join(outputRoot, "NEXT.md"), lines.join("\n"));
|
|
54
|
+
}
|