sdd-flow-kit 1.0.22 → 1.0.27
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 +21 -3
- package/dist/adapters/index.js +13 -12
- package/dist/cli.js +29 -28
- package/dist/core/fs.js +3 -3
- package/dist/core/inferProject.js +22 -20
- package/dist/core/nodeCompat.js +35 -0
- package/dist/core/opsxDelivery.js +26 -25
- package/dist/core/readlinePrompt.js +51 -0
- package/dist/core/templates.js +4 -4
- package/dist/core/utils.js +8 -1
- package/dist/postinstall.js +10 -9
- package/dist/steps/doctor.js +13 -6
- package/dist/steps/invokeFlow.js +2 -1
- package/dist/steps/runFlow.js +3 -3
- package/dist/steps/setupProject.js +34 -31
- package/dist/steps/startFlow.js +26 -22
- package/package.json +4 -1
- package/src/templates/artifacts/05-openspec-/346/217/220/346/241/210/350/215/211/347/250/277.template.md +2 -1
- package/src/templates/artifacts/06-agent-skill.template.md +25 -21
- package/src/templates/artifacts/06-opsx-auto-chain.template.md +38 -0
- package/src/templates/prompts/02-sdd-loop-prompt.md +4 -2
- package/src/templates/rules/sdd-no-implement-until-apply.mdc.template +9 -4
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
|
}
|
package/dist/postinstall.js
CHANGED
|
@@ -36,20 +36,21 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
36
36
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
37
37
|
};
|
|
38
38
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
39
|
-
const
|
|
40
|
-
const
|
|
39
|
+
const path_1 = __importDefault(require("path"));
|
|
40
|
+
const process_1 = __importDefault(require("process"));
|
|
41
41
|
const inferProject_1 = require("./core/inferProject");
|
|
42
42
|
const opsxDelivery_1 = require("./core/opsxDelivery");
|
|
43
43
|
const setupProject_1 = require("./steps/setupProject");
|
|
44
44
|
async function inferAgent(projectRoot) {
|
|
45
|
-
|
|
45
|
+
var _a;
|
|
46
|
+
const fromEnv = ((_a = process_1.default.env.SDD_FLOW_KIT_AGENT) !== null && _a !== void 0 ? _a : "").trim();
|
|
46
47
|
const allowed = ["cursor", "claude-code", "codex", "openclaw"];
|
|
47
48
|
if (allowed.includes(fromEnv))
|
|
48
49
|
return fromEnv;
|
|
49
|
-
const fs = await Promise.resolve().then(() => __importStar(require("
|
|
50
|
+
const fs = await Promise.resolve().then(() => __importStar(require("fs/promises")));
|
|
50
51
|
const exists = async (rel) => {
|
|
51
52
|
try {
|
|
52
|
-
await fs.access(
|
|
53
|
+
await fs.access(path_1.default.join(projectRoot, rel));
|
|
53
54
|
return true;
|
|
54
55
|
}
|
|
55
56
|
catch {
|
|
@@ -67,12 +68,12 @@ async function inferAgent(projectRoot) {
|
|
|
67
68
|
return "cursor";
|
|
68
69
|
}
|
|
69
70
|
async function main() {
|
|
70
|
-
if (
|
|
71
|
+
if (process_1.default.env.SDD_FLOW_KIT_SKIP_POSTINSTALL === "1") {
|
|
71
72
|
return;
|
|
72
73
|
}
|
|
73
|
-
const initCwd =
|
|
74
|
-
const pkgDir =
|
|
75
|
-
const projectRoot = initCwd
|
|
74
|
+
const initCwd = process_1.default.env.INIT_CWD ? path_1.default.resolve(process_1.default.env.INIT_CWD) : null;
|
|
75
|
+
const pkgDir = path_1.default.resolve(__dirname, "..");
|
|
76
|
+
const projectRoot = initCwd !== null && initCwd !== void 0 ? initCwd : process_1.default.cwd();
|
|
76
77
|
if (projectRoot === pkgDir)
|
|
77
78
|
return;
|
|
78
79
|
const projectKind = await (0, inferProject_1.inferProjectKind)(projectRoot);
|
package/dist/steps/doctor.js
CHANGED
|
@@ -4,12 +4,14 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.runDoctor = runDoctor;
|
|
7
|
-
const
|
|
8
|
-
const promises_1 = __importDefault(require("
|
|
9
|
-
const
|
|
7
|
+
const child_process_1 = require("child_process");
|
|
8
|
+
const promises_1 = __importDefault(require("fs/promises"));
|
|
9
|
+
const path_1 = __importDefault(require("path"));
|
|
10
|
+
const process_1 = __importDefault(require("process"));
|
|
11
|
+
const nodeCompat_1 = require("../core/nodeCompat");
|
|
10
12
|
const opsxDelivery_1 = require("../core/opsxDelivery");
|
|
11
13
|
function hasCommand(cmd) {
|
|
12
|
-
const out = (0,
|
|
14
|
+
const out = (0, child_process_1.spawnSync)("which", [cmd], { stdio: "ignore" });
|
|
13
15
|
return out.status === 0;
|
|
14
16
|
}
|
|
15
17
|
async function hasPath(filePath) {
|
|
@@ -34,6 +36,11 @@ function agentBin(agent) {
|
|
|
34
36
|
}
|
|
35
37
|
async function runDoctor(options) {
|
|
36
38
|
const checks = [];
|
|
39
|
+
checks.push({
|
|
40
|
+
name: "node-version",
|
|
41
|
+
ok: (0, nodeCompat_1.isNodeVersionSupported)(),
|
|
42
|
+
detail: `Node ${process_1.default.version}(要求 ${(0, nodeCompat_1.nodeVersionRequirementLabel)()},推荐 18/20 LTS)`,
|
|
43
|
+
});
|
|
37
44
|
checks.push({
|
|
38
45
|
name: "git",
|
|
39
46
|
ok: hasCommand("git"),
|
|
@@ -54,7 +61,7 @@ async function runDoctor(options) {
|
|
|
54
61
|
}
|
|
55
62
|
checks.push({
|
|
56
63
|
name: "openspec-dir",
|
|
57
|
-
ok: await hasPath(
|
|
64
|
+
ok: await hasPath(path_1.default.join(options.projectRoot, "openspec")),
|
|
58
65
|
detail: "openspec 目录存在性",
|
|
59
66
|
});
|
|
60
67
|
const toolDirMap = {
|
|
@@ -68,7 +75,7 @@ async function runDoctor(options) {
|
|
|
68
75
|
if (expectedDir) {
|
|
69
76
|
checks.push({
|
|
70
77
|
name: `${options.agent}-dir`,
|
|
71
|
-
ok: await hasPath(
|
|
78
|
+
ok: await hasPath(path_1.default.join(options.projectRoot, expectedDir)),
|
|
72
79
|
detail: `${expectedDir} 目录存在性`,
|
|
73
80
|
});
|
|
74
81
|
}
|
package/dist/steps/invokeFlow.js
CHANGED
|
@@ -17,6 +17,7 @@ function parseInvokeText(text) {
|
|
|
17
17
|
return { product, version };
|
|
18
18
|
}
|
|
19
19
|
function toRunOptions(args) {
|
|
20
|
+
var _a, _b;
|
|
20
21
|
return {
|
|
21
22
|
product: args.parsed.product,
|
|
22
23
|
version: args.parsed.version,
|
|
@@ -24,6 +25,6 @@ function toRunOptions(args) {
|
|
|
24
25
|
branch: args.branch,
|
|
25
26
|
yes: args.yes,
|
|
26
27
|
dryRun: args.dryRun,
|
|
27
|
-
agent: args.agent
|
|
28
|
+
agent: (_b = (_a = args.agent) !== null && _a !== void 0 ? _a : args.parsed.agent) !== null && _b !== void 0 ? _b : "claude-code",
|
|
28
29
|
};
|
|
29
30
|
}
|
package/dist/steps/runFlow.js
CHANGED
|
@@ -4,7 +4,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.runFlow = runFlow;
|
|
7
|
-
const
|
|
7
|
+
const path_1 = __importDefault(require("path"));
|
|
8
8
|
const adapters_1 = require("../adapters");
|
|
9
9
|
const fs_1 = require("../core/fs");
|
|
10
10
|
const startFlow_1 = require("./startFlow");
|
|
@@ -41,7 +41,7 @@ async function runFlow(options) {
|
|
|
41
41
|
const scaffold = options.dryRun
|
|
42
42
|
? {
|
|
43
43
|
runId: "dry-run-no-files-generated",
|
|
44
|
-
outputRoot:
|
|
44
|
+
outputRoot: path_1.default.join(options.projectRoot, "openspec", "PRD"),
|
|
45
45
|
}
|
|
46
46
|
: await (0, startFlow_1.startFlowScaffold)(options);
|
|
47
47
|
const adapter = (0, adapters_1.getAdapter)(setup.agent);
|
|
@@ -63,7 +63,7 @@ async function runFlow(options) {
|
|
|
63
63
|
version: options.version,
|
|
64
64
|
});
|
|
65
65
|
if (!options.dryRun) {
|
|
66
|
-
await (0, fs_1.writeTextFileEnsuredDir)(
|
|
66
|
+
await (0, fs_1.writeTextFileEnsuredDir)(path_1.default.join(scaffold.outputRoot, "AUTO-RUN-CHECKLIST.md"), checklist);
|
|
67
67
|
}
|
|
68
68
|
return {
|
|
69
69
|
...scaffold,
|
|
@@ -4,19 +4,21 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.setupProject = setupProject;
|
|
7
|
-
const
|
|
8
|
-
const
|
|
9
|
-
const
|
|
10
|
-
const node_process_1 = __importDefault(require("node:process"));
|
|
7
|
+
const child_process_1 = require("child_process");
|
|
8
|
+
const path_1 = __importDefault(require("path"));
|
|
9
|
+
const process_1 = __importDefault(require("process"));
|
|
11
10
|
const fs_1 = require("../core/fs");
|
|
11
|
+
const readlinePrompt_1 = require("../core/readlinePrompt");
|
|
12
|
+
const utils_1 = require("../core/utils");
|
|
12
13
|
const opsxDelivery_1 = require("../core/opsxDelivery");
|
|
13
14
|
const templates_1 = require("../core/templates");
|
|
14
15
|
const EDITOR_CHOICES = ["claude-code", "cursor", "codex", "openclaw"];
|
|
15
16
|
const PROJECT_CHOICES = ["OMS", "ADI", "欢盟", "AD Tools"];
|
|
16
17
|
function runGit(args, cwd) {
|
|
17
|
-
|
|
18
|
+
var _a, _b;
|
|
19
|
+
const out = (0, child_process_1.spawnSync)("git", args, { cwd, encoding: "utf8" });
|
|
18
20
|
if (out.status !== 0) {
|
|
19
|
-
const err = out.stderr
|
|
21
|
+
const err = ((_a = out.stderr) === null || _a === void 0 ? void 0 : _a.trim()) || ((_b = out.stdout) === null || _b === void 0 ? void 0 : _b.trim()) || `git ${args.join(" ")} failed`;
|
|
20
22
|
throw new Error(err);
|
|
21
23
|
}
|
|
22
24
|
return out.stdout.trim();
|
|
@@ -45,9 +47,9 @@ function listLocalBranches(projectRoot) {
|
|
|
45
47
|
}
|
|
46
48
|
}
|
|
47
49
|
async function askAgent(defaultAgent) {
|
|
48
|
-
if (!
|
|
50
|
+
if (!process_1.default.stdin.isTTY)
|
|
49
51
|
return defaultAgent;
|
|
50
|
-
const rl = (0,
|
|
52
|
+
const rl = (0, readlinePrompt_1.createPromptInterface)();
|
|
51
53
|
const prompt = [
|
|
52
54
|
"请选择开发环境:",
|
|
53
55
|
"1) claude-code (default)",
|
|
@@ -56,7 +58,7 @@ async function askAgent(defaultAgent) {
|
|
|
56
58
|
"4) openclaw",
|
|
57
59
|
"输入编号(回车默认 1):",
|
|
58
60
|
].join("\n");
|
|
59
|
-
const answer = (await
|
|
61
|
+
const answer = (await (0, readlinePrompt_1.promptLine)(rl, `${prompt}\n`)).trim();
|
|
60
62
|
rl.close();
|
|
61
63
|
if (!answer)
|
|
62
64
|
return defaultAgent;
|
|
@@ -66,17 +68,17 @@ async function askAgent(defaultAgent) {
|
|
|
66
68
|
return EDITOR_CHOICES[idx - 1];
|
|
67
69
|
}
|
|
68
70
|
async function askBranch(defaultBranch) {
|
|
69
|
-
if (!
|
|
71
|
+
if (!process_1.default.stdin.isTTY)
|
|
70
72
|
return defaultBranch;
|
|
71
|
-
const rl = (0,
|
|
72
|
-
const answer = (await
|
|
73
|
+
const rl = (0, readlinePrompt_1.createPromptInterface)();
|
|
74
|
+
const answer = (await (0, readlinePrompt_1.promptLine)(rl, `请输入开发分支(默认 ${defaultBranch}):\n`)).trim();
|
|
73
75
|
rl.close();
|
|
74
76
|
return answer || defaultBranch;
|
|
75
77
|
}
|
|
76
78
|
async function askProjectKind(defaultKind) {
|
|
77
|
-
if (!
|
|
79
|
+
if (!process_1.default.stdin.isTTY)
|
|
78
80
|
return defaultKind;
|
|
79
|
-
const rl = (0,
|
|
81
|
+
const rl = (0, readlinePrompt_1.createPromptInterface)();
|
|
80
82
|
const prompt = [
|
|
81
83
|
"请选择当前项目类型(用于生成 PRD 下载 skill):",
|
|
82
84
|
"1) OMS (default)",
|
|
@@ -85,7 +87,7 @@ async function askProjectKind(defaultKind) {
|
|
|
85
87
|
"4) AD Tools",
|
|
86
88
|
"输入编号(回车默认 1):",
|
|
87
89
|
].join("\n");
|
|
88
|
-
const answer = (await
|
|
90
|
+
const answer = (await (0, readlinePrompt_1.promptLine)(rl, `${prompt}\n`)).trim();
|
|
89
91
|
rl.close();
|
|
90
92
|
if (!answer)
|
|
91
93
|
return defaultKind;
|
|
@@ -114,7 +116,7 @@ function skillInstallPath(projectRoot, agent) {
|
|
|
114
116
|
const rel = relMap[agent];
|
|
115
117
|
if (!rel)
|
|
116
118
|
return null;
|
|
117
|
-
return
|
|
119
|
+
return path_1.default.join(projectRoot, rel);
|
|
118
120
|
}
|
|
119
121
|
function allSkillInstallPaths(projectRoot) {
|
|
120
122
|
const agents = ["cursor", "claude-code", "codex", "openclaw"];
|
|
@@ -135,19 +137,19 @@ async function installAgentSkill(projectRoot) {
|
|
|
135
137
|
: target.includes("/.codex/")
|
|
136
138
|
? "codex"
|
|
137
139
|
: "openclaw";
|
|
138
|
-
const content =
|
|
140
|
+
const content = (0, utils_1.replaceAllStr)(template, "{{AGENT_NAME}}", agentName);
|
|
139
141
|
await (0, fs_1.writeTextFileEnsuredDir)(target, content);
|
|
140
142
|
}
|
|
141
143
|
}
|
|
142
144
|
async function installSddCursorRule(projectRoot) {
|
|
143
|
-
const rulePath =
|
|
145
|
+
const rulePath = path_1.default.join(projectRoot, ".cursor", "rules", "sdd-flow-kit-gates.mdc");
|
|
144
146
|
const template = await (0, templates_1.loadTemplateText)("rules/sdd-no-implement-until-apply.mdc.template");
|
|
145
147
|
await (0, fs_1.writeTextFileEnsuredDir)(rulePath, template);
|
|
146
148
|
}
|
|
147
149
|
function renderVars(template, vars) {
|
|
148
150
|
let out = template;
|
|
149
151
|
for (const [k, v] of Object.entries(vars)) {
|
|
150
|
-
out =
|
|
152
|
+
out = (0, utils_1.replaceAllStr)(out, `{{${k}}}`, v);
|
|
151
153
|
}
|
|
152
154
|
return out;
|
|
153
155
|
}
|
|
@@ -166,7 +168,7 @@ function docSkillDirName(kind) {
|
|
|
166
168
|
}
|
|
167
169
|
async function installDocsSkill(projectRoot, kind) {
|
|
168
170
|
const { dir, prefix } = docSkillDirName(kind);
|
|
169
|
-
const docsDir =
|
|
171
|
+
const docsDir = path_1.default.join(projectRoot, "docs", dir);
|
|
170
172
|
const pyTemplate = await (0, templates_1.loadTemplateText)("skills/confluence-doc.py.template");
|
|
171
173
|
const mdTemplate = await (0, templates_1.loadTemplateText)("skills/doc-skill.SKILL.md.template");
|
|
172
174
|
const md = renderVars(mdTemplate, {
|
|
@@ -176,9 +178,9 @@ async function installDocsSkill(projectRoot, kind) {
|
|
|
176
178
|
PRODUCT_PREFIX_LOWER: prefix.toLowerCase(),
|
|
177
179
|
SKILL_DIR: dir,
|
|
178
180
|
});
|
|
179
|
-
const py =
|
|
180
|
-
await (0, fs_1.writeTextFileEnsuredDir)(
|
|
181
|
-
await (0, fs_1.writeTextFileEnsuredDir)(
|
|
181
|
+
const py = (0, utils_1.replaceAllStr)(pyTemplate, "{{DOC_PRODUCT_PREFIX}}", prefix);
|
|
182
|
+
await (0, fs_1.writeTextFileEnsuredDir)(path_1.default.join(docsDir, "SKILL.md"), md);
|
|
183
|
+
await (0, fs_1.writeTextFileEnsuredDir)(path_1.default.join(docsDir, "confluence-doc.py"), py);
|
|
182
184
|
return { dirPath: docsDir };
|
|
183
185
|
}
|
|
184
186
|
function getOpsxPathsByAgent(agent) {
|
|
@@ -196,7 +198,7 @@ function getOpsxPathsByAgent(agent) {
|
|
|
196
198
|
}
|
|
197
199
|
}
|
|
198
200
|
async function installOpsxConfig(projectRoot, agent) {
|
|
199
|
-
const target =
|
|
201
|
+
const target = path_1.default.join(projectRoot, ".opsx", "config.json");
|
|
200
202
|
const resolvedAgent = await (0, opsxDelivery_1.resolveOpsxAgent)(projectRoot, agent);
|
|
201
203
|
const paths = getOpsxPathsByAgent(resolvedAgent);
|
|
202
204
|
const config = {
|
|
@@ -216,11 +218,12 @@ async function installOpsxConfig(projectRoot, agent) {
|
|
|
216
218
|
await (0, fs_1.writeTextFileEnsuredDir)(target, JSON.stringify(config, null, 2));
|
|
217
219
|
}
|
|
218
220
|
async function setupProject(options) {
|
|
219
|
-
|
|
221
|
+
var _a, _b, _c;
|
|
222
|
+
const defaultAgent = (_a = options.agent) !== null && _a !== void 0 ? _a : "claude-code";
|
|
220
223
|
const selectedAgent = options.yes ? defaultAgent : await askAgent(defaultAgent);
|
|
221
|
-
const defaultKind = options.projectKind
|
|
224
|
+
const defaultKind = (_b = options.projectKind) !== null && _b !== void 0 ? _b : "OMS";
|
|
222
225
|
const selectedKind = options.yes ? defaultKind : await askProjectKind(defaultKind);
|
|
223
|
-
const defaultBranch = options.branch
|
|
226
|
+
const defaultBranch = (_c = options.branch) !== null && _c !== void 0 ? _c : pickDefaultBranch(options.projectRoot);
|
|
224
227
|
const selectedBranch = options.yes ? defaultBranch : await askBranch(defaultBranch);
|
|
225
228
|
const plannedActions = [];
|
|
226
229
|
const branches = listLocalBranches(options.projectRoot);
|
|
@@ -268,9 +271,9 @@ async function setupProject(options) {
|
|
|
268
271
|
plannedActions.push(`would install skill: ${skillPath}`);
|
|
269
272
|
}
|
|
270
273
|
const { dir } = docSkillDirName(selectedKind);
|
|
271
|
-
plannedActions.push(`would install docs skill: ${
|
|
272
|
-
plannedActions.push(`would install opsx config: ${
|
|
273
|
-
plannedActions.push(`would install cursor rule: ${
|
|
274
|
+
plannedActions.push(`would install docs skill: ${path_1.default.join(options.projectRoot, "docs", dir)}`);
|
|
275
|
+
plannedActions.push(`would install opsx config: ${path_1.default.join(options.projectRoot, ".opsx", "config.json")}`);
|
|
276
|
+
plannedActions.push(`would install cursor rule: ${path_1.default.join(options.projectRoot, ".cursor", "rules", "sdd-flow-kit-gates.mdc")}`);
|
|
274
277
|
}
|
|
275
278
|
else {
|
|
276
279
|
await installAgentSkill(options.projectRoot);
|
|
@@ -304,7 +307,7 @@ async function setupProject(options) {
|
|
|
304
307
|
...plannedActions.map((x) => `- ${x}`),
|
|
305
308
|
"",
|
|
306
309
|
].join("\n");
|
|
307
|
-
await (0, fs_1.writeTextFileEnsuredDir)(
|
|
310
|
+
await (0, fs_1.writeTextFileEnsuredDir)(path_1.default.join(options.projectRoot, ".sdd-flow-kit", "setup-summary.md"), summary);
|
|
308
311
|
return {
|
|
309
312
|
agent: selectedAgent,
|
|
310
313
|
projectKind: selectedKind,
|
package/dist/steps/startFlow.js
CHANGED
|
@@ -4,8 +4,8 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
6
|
exports.startFlowScaffold = startFlowScaffold;
|
|
7
|
-
const
|
|
8
|
-
const promises_1 = __importDefault(require("
|
|
7
|
+
const path_1 = __importDefault(require("path"));
|
|
8
|
+
const promises_1 = __importDefault(require("fs/promises"));
|
|
9
9
|
const utils_1 = require("../core/utils");
|
|
10
10
|
const fs_1 = require("../core/fs");
|
|
11
11
|
const templates_1 = require("../core/templates");
|
|
@@ -13,15 +13,15 @@ const inferProject_1 = require("../core/inferProject");
|
|
|
13
13
|
function renderTemplate(template, vars) {
|
|
14
14
|
let out = template;
|
|
15
15
|
for (const [k, v] of Object.entries(vars)) {
|
|
16
|
-
out =
|
|
16
|
+
out = (0, utils_1.replaceAllStr)(out, `{{${k}}}`, v);
|
|
17
17
|
}
|
|
18
18
|
return out;
|
|
19
19
|
}
|
|
20
20
|
async function startFlowScaffold(options) {
|
|
21
21
|
const now = new Date();
|
|
22
22
|
const runId = `${(0, utils_1.formatYYYYMMDD)(now)}-${options.product}-${options.version}`;
|
|
23
|
-
const outputRoot =
|
|
24
|
-
const sourceDir =
|
|
23
|
+
const outputRoot = path_1.default.join(options.projectRoot, "openspec", "PRD", runId);
|
|
24
|
+
const sourceDir = path_1.default.join(outputRoot, "source");
|
|
25
25
|
await promises_1.default.mkdir(sourceDir, { recursive: true });
|
|
26
26
|
// 1) Step1: doc-skill 指引(按产品解析脚本路径,支持 monorepo frontend)
|
|
27
27
|
const kind = (0, inferProject_1.productToKind)(options.product);
|
|
@@ -33,23 +33,23 @@ async function startFlowScaffold(options) {
|
|
|
33
33
|
DOC_SKILL_REL: docSkill.relFromRoot.replace(/\\/g, "/"),
|
|
34
34
|
VERSION_EXAMPLE: options.version,
|
|
35
35
|
});
|
|
36
|
-
await (0, fs_1.writeTextFileEnsuredDir)(
|
|
36
|
+
await (0, fs_1.writeTextFileEnsuredDir)(path_1.default.join(outputRoot, "01-获取需求文档指引.md"), step1);
|
|
37
37
|
// source 占位:让第 2 步提示词在任何工具里都能直接读
|
|
38
|
-
await (0, fs_1.writeTextFileEnsuredDir)(
|
|
38
|
+
await (0, fs_1.writeTextFileEnsuredDir)(path_1.default.join(sourceDir, "PRD.md"), [
|
|
39
39
|
"# source/PRD.md",
|
|
40
40
|
"",
|
|
41
41
|
"请把《PRD》markdown 内容粘贴到这里(由 `adi-doc-skill` 下载并回填)。",
|
|
42
42
|
"",
|
|
43
43
|
"- 建议:保持原有标题层级与编号。",
|
|
44
44
|
].join("\n"));
|
|
45
|
-
await (0, fs_1.writeTextFileEnsuredDir)(
|
|
45
|
+
await (0, fs_1.writeTextFileEnsuredDir)(path_1.default.join(sourceDir, "接口文档.md"), [
|
|
46
46
|
"# source/接口文档.md",
|
|
47
47
|
"",
|
|
48
48
|
"(可选)如果你已经有接口文档,请粘贴到这里;没有也不影响后续 SDD 循环。",
|
|
49
49
|
"",
|
|
50
50
|
"- 注意:接口文档中的字段/示例越完整,分析报告越贴近落地。",
|
|
51
51
|
].join("\n"));
|
|
52
|
-
await (0, fs_1.writeTextFileEnsuredDir)(
|
|
52
|
+
await (0, fs_1.writeTextFileEnsuredDir)(path_1.default.join(sourceDir, "openspec-knowledge-base-summary.md"), [
|
|
53
53
|
"# source/openspec-knowledge-base-summary.md",
|
|
54
54
|
"",
|
|
55
55
|
"(可选)现有知识库摘要/快速索引,建议由你在执行第 2 步前手动整理。",
|
|
@@ -60,32 +60,35 @@ async function startFlowScaffold(options) {
|
|
|
60
60
|
const sddLoopPrompt = await (0, templates_1.loadTemplateText)("prompts/02-sdd-loop-prompt.md");
|
|
61
61
|
const step2PromptTemplate = await (0, templates_1.loadTemplateText)("artifacts/02-需求分析提示词.template.md");
|
|
62
62
|
const step2Prompt = renderTemplate(step2PromptTemplate, { SDD_LOOP_PROMPT: sddLoopPrompt });
|
|
63
|
-
await (0, fs_1.writeTextFileEnsuredDir)(
|
|
63
|
+
await (0, fs_1.writeTextFileEnsuredDir)(path_1.default.join(outputRoot, "02-需求分析提示词.md"), step2Prompt);
|
|
64
64
|
// 3) Step2 输出文件占位(由 AI 工具生成并覆盖)
|
|
65
65
|
const file01Template = await (0, templates_1.loadTemplateText)("artifacts/01-需求分析报告.template.md");
|
|
66
66
|
const file02Template = await (0, templates_1.loadTemplateText)("artifacts/02-改动点清单.template.md");
|
|
67
67
|
const file03Template = await (0, templates_1.loadTemplateText)("artifacts/03-待确认问题清单.template.md");
|
|
68
|
-
await (0, fs_1.writeTextFileEnsuredDir)(
|
|
69
|
-
await (0, fs_1.writeTextFileEnsuredDir)(
|
|
70
|
-
await (0, fs_1.writeTextFileEnsuredDir)(
|
|
68
|
+
await (0, fs_1.writeTextFileEnsuredDir)(path_1.default.join(outputRoot, "01-需求分析报告.md"), file01Template);
|
|
69
|
+
await (0, fs_1.writeTextFileEnsuredDir)(path_1.default.join(outputRoot, "02-改动点清单.md"), file02Template);
|
|
70
|
+
await (0, fs_1.writeTextFileEnsuredDir)(path_1.default.join(outputRoot, "03-待确认问题清单.md"), file03Template);
|
|
71
71
|
// 4) Step4 技术文档入口(草稿)
|
|
72
72
|
const techDocTemplate = await (0, templates_1.loadTemplateText)("artifacts/04-技术文档.template.md");
|
|
73
|
-
await (0, fs_1.writeTextFileEnsuredDir)(
|
|
73
|
+
await (0, fs_1.writeTextFileEnsuredDir)(path_1.default.join(outputRoot, "04-技术文档草稿.md"), techDocTemplate);
|
|
74
74
|
// 5) Step5 openspec 提案入口(草稿)
|
|
75
75
|
const openspecEntryTemplate = await (0, templates_1.loadTemplateText)("artifacts/05-openspec-提案草稿.template.md");
|
|
76
|
-
await (0, fs_1.writeTextFileEnsuredDir)(
|
|
77
|
-
await (0,
|
|
76
|
+
await (0, fs_1.writeTextFileEnsuredDir)(path_1.default.join(outputRoot, "05-openspec-提案草稿.md"), openspecEntryTemplate);
|
|
77
|
+
const opsxAutoChainTemplate = await (0, templates_1.loadTemplateText)("artifacts/06-opsx-auto-chain.template.md");
|
|
78
|
+
await (0, fs_1.writeTextFileEnsuredDir)(path_1.default.join(outputRoot, "06-opsx-自动串联指引.md"), opsxAutoChainTemplate);
|
|
79
|
+
await (0, fs_1.writeTextFileEnsuredDir)(path_1.default.join(outputRoot, ".session-state.json"), JSON.stringify({
|
|
78
80
|
product: options.product,
|
|
79
81
|
version: options.version,
|
|
80
82
|
runId,
|
|
81
83
|
outputRoot,
|
|
82
84
|
status: "awaiting_user_confirmation",
|
|
83
85
|
docModeOnly: true,
|
|
86
|
+
autoChainOpsx: true,
|
|
84
87
|
phase: "A_or_B",
|
|
85
88
|
createdAtISO: new Date().toISOString(),
|
|
86
89
|
}, null, 2));
|
|
87
90
|
// runDir 自述
|
|
88
|
-
await (0, fs_1.writeTextFileEnsuredDir)(
|
|
91
|
+
await (0, fs_1.writeTextFileEnsuredDir)(path_1.default.join(outputRoot, "RUNME.md"), [
|
|
89
92
|
"# RUNME(SDD 流程执行说明)",
|
|
90
93
|
"",
|
|
91
94
|
`本次 runId: \`${runId}\``,
|
|
@@ -110,12 +113,13 @@ async function startFlowScaffold(options) {
|
|
|
110
113
|
"- `02-改动点清单.md`",
|
|
111
114
|
"- `04-技术文档草稿.md`",
|
|
112
115
|
"",
|
|
113
|
-
"## Step 5
|
|
114
|
-
"
|
|
115
|
-
"
|
|
116
|
-
"
|
|
116
|
+
"## Step 5–6:自动串联 OPSX(默认)",
|
|
117
|
+
"03 闭环且 01/02/04 完成后,**同一 AI 回合**读 `06-opsx-自动串联指引.md`:",
|
|
118
|
+
"1. `/opsx-propose`(输入 01、02、UI;UI 默认读 `source/PRD.md` 原型目录)",
|
|
119
|
+
"2. 提案 apply-ready 后立即 `/opsx-apply` + `pnpm run opsx:delivery-pipeline`(exit 0)",
|
|
120
|
+
"勿停下来问「是否继续」。仅当用户说「只提案」或 `.session-state.json` 中 `autoChainOpsx: false` 时暂停。",
|
|
117
121
|
"",
|
|
118
|
-
"## Step 6
|
|
122
|
+
"## Step 6 明细:Apply + TDD + Playwright(阶段 D)",
|
|
119
123
|
"依次执行 skill:`openspec-apply-change` → `tdd-script` → `openspec-superpowers-pipeline` → `opsx-dev-delivery-pipeline`。",
|
|
120
124
|
"仓库根执行:`pnpm run opsx:delivery-pipeline -- <change-name>`(须 exit 0)。",
|
|
121
125
|
"禁止跳过 TDD / Playwright 裸写代码收尾。详见 `.cursor/skills/sdd-flow-kit/SKILL.md` 阶段 D。",
|
package/package.json
CHANGED
|
@@ -1,9 +1,12 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "sdd-flow-kit",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.27",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Cross-agent SDD automated development workflow kit (ADI/ADI-like).",
|
|
6
6
|
"license": "MIT",
|
|
7
|
+
"engines": {
|
|
8
|
+
"node": ">=14.16.0"
|
|
9
|
+
},
|
|
7
10
|
"type": "commonjs",
|
|
8
11
|
"main": "dist/index.js",
|
|
9
12
|
"files": [
|
|
@@ -6,7 +6,8 @@
|
|
|
6
6
|
## 前置条件(必须全部满足)
|
|
7
7
|
- [ ] `03-待确认问题清单.md` 已标注 **矛盾点池状态:已闭合**
|
|
8
8
|
- [ ] `01-需求分析报告.md`、`02-改动点清单.md`、`04-技术文档草稿.md` 已生成
|
|
9
|
-
- [ ]
|
|
9
|
+
- [ ] 用户已确认可进入提案阶段(阶段 B 闭环)
|
|
10
|
+
- [ ] `autoChainOpsx` 为 true(默认)→ 与用户确认后**同一回合**自动 propose,无需再等「继续」
|
|
10
11
|
|
|
11
12
|
## /opsx-propose 输入清单
|
|
12
13
|
- 需求分析:`01-需求分析报告.md`
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: sdd-flow-kit-trigger
|
|
3
|
-
description: 开发某版本需求、SDD
|
|
3
|
+
description: 开发某版本需求、SDD 流程、待确认问题澄清、用户回复确认点/矛盾点结论时触发。阶段 A/B 仅文档;阶段 B 闭环后**自动** /opsx-propose → /opsx-apply(除非用户说只提案或 autoChainOpsx=false)。
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# SDD Flow Trigger Skill
|
|
@@ -20,16 +20,18 @@ description: 开发某版本需求、SDD 流程、待确认问题澄清、用户
|
|
|
20
20
|
| 模式 | 何时 | 允许改动的路径 | 禁止 |
|
|
21
21
|
|------|------|----------------|------|
|
|
22
22
|
| **文档模式** | 阶段 A/B/C,含「用户发来确认点」 | 仅 `openspec/PRD/**`(及阶段 C 的 `openspec/changes/**`) | 任何 `src/`、`pages/`、`components/` 等业务代码 |
|
|
23
|
-
| **实现模式** |
|
|
23
|
+
| **实现模式** | 阶段 D(提案 apply-ready 后**自动进入**,无需用户再说「开始 apply」) | 按 openspec change 与 pipeline 要求 | 跳过 propose / 跳过 pipeline |
|
|
24
24
|
|
|
25
25
|
**常见错误**:用户粘贴确认点后,你一边更新 `03` 一边改 `.tsx` —— **绝对禁止**。确认回复 = 只做文档,同一回合不得 touch 业务代码。
|
|
26
26
|
|
|
27
27
|
### 用户侧:零口令(不要要求加前缀)
|
|
28
28
|
- 用户**直接粘贴**确认点、编号结论、「按 1/2/3 继续」即可;**禁止**要求用户输入【仅文档模式】等固定话术。
|
|
29
29
|
- 你须在收到后**自动**判定为阶段 B 的文档模式;先读 `openspec/PRD/<runId>/.session-state.json`(`docModeOnly: true` 时严禁写业务代码)与 `03-待确认问题清单.md`。
|
|
30
|
-
-
|
|
31
|
-
-
|
|
32
|
-
-
|
|
30
|
+
- **默认自动串联**(`openspec/PRD/<runId>/.session-state.json` 中 `autoChainOpsx !== false`):
|
|
31
|
+
- 阶段 B 产出 01/02/闭环03/04 后,**同一回合**继续阶段 C→D,**不要**停下来问「是否继续」。
|
|
32
|
+
- 读 `06-opsx-自动串联指引.md` 与 `.cursor/skills/opsx-superpowers-workflow/SKILL.md`。
|
|
33
|
+
- **仅当用户说**「只提案」「先别写代码」时:阶段 C 结束后停止,等待 `/opsx-apply`。
|
|
34
|
+
- 口语仍可用:`继续` / `做提案` / `开始 apply`(用于断点续跑)。
|
|
33
35
|
|
|
34
36
|
## 触发后执行规则(分阶段,禁止跳步)
|
|
35
37
|
|
|
@@ -64,7 +66,9 @@ description: 开发某版本需求、SDD 流程、待确认问题澄清、用户
|
|
|
64
66
|
2. 将状态改为 **矛盾点池状态:已闭合**。
|
|
65
67
|
3. 再生成 `01`、`02`、`04`(路径均在 `openspec/PRD/<runId>/`)。
|
|
66
68
|
4. **本回合禁止**:`StrReplace`/`Write` 任何业务源码;禁止「顺手实现一版」。
|
|
67
|
-
5.
|
|
69
|
+
5. 更新 `.session-state.json`:`status: ready_for_propose`,`phase: B_done`。
|
|
70
|
+
6. **若 `autoChainOpsx !== false` 且用户未说「只提案」**:**不要结束回合**——立即打开 `06-opsx-自动串联指引.md` 执行阶段 C→D(见下文)。
|
|
71
|
+
7. 若 `autoChainOpsx === false`:简短说明 01/02/03/04 已就绪,等待用户说「继续」。
|
|
68
72
|
|
|
69
73
|
**本阶段只允许产出:**
|
|
70
74
|
1. 更新 `03-待确认问题清单.md`(标注 **矛盾点池状态:已闭合**,写入用户确认结论)
|
|
@@ -74,34 +78,34 @@ description: 开发某版本需求、SDD 流程、待确认问题澄清、用户
|
|
|
74
78
|
|
|
75
79
|
**本阶段禁止:**
|
|
76
80
|
- 修改 `src/`、`pages/`、`components/`、`api/` 等实现代码(`02-改动点清单` 只是**计划**,不是实施许可)
|
|
77
|
-
-
|
|
78
|
-
-
|
|
79
|
-
|
|
80
|
-
完成后告知用户:“01/02/03/04 已就绪。发 UI 参考路径,或回复「继续」我将执行 /opsx-propose。”
|
|
81
|
+
- 在 **未闭合 03** 时执行 `/opsx-propose` 或 `/opsx-apply`
|
|
82
|
+
- 在 `autoChainOpsx === false` 时未经用户同意就进入阶段 C/D
|
|
81
83
|
|
|
82
84
|
---
|
|
83
85
|
|
|
84
|
-
### 阶段 C — `/opsx-propose
|
|
85
|
-
|
|
86
|
+
### 阶段 C — `/opsx-propose`(生成提案,对应 Step5)
|
|
87
|
+
**触发条件**:
|
|
88
|
+
- 阶段 B 已完成(03 已闭合,01/02/04 已生成),**且**
|
|
89
|
+
- `autoChainOpsx !== false`(默认)→ **与用户确认同一回合内自动执行**;或用户说「继续/做提案」;或 `status` 为 `ready_for_propose` 且用户未说「只提案」。
|
|
86
90
|
|
|
87
91
|
**必须执行:**
|
|
88
92
|
1. 先读当前仓库 `.opsx/config.json` 与本地 OPSX skills(禁止跨项目 fallback)
|
|
89
|
-
2. 执行 **`/opsx-propose
|
|
90
|
-
- `01-需求分析报告.md`
|
|
91
|
-
- `02-改动点清单.md`
|
|
92
|
-
- **UI
|
|
93
|
+
2. 执行 **`/opsx-propose`**(遵循 `.cursor/commands/opsx-propose.md` 与 `openspec-propose` skill),输入至少包含:
|
|
94
|
+
- `openspec/PRD/<runId>/01-需求分析报告.md`
|
|
95
|
+
- `openspec/PRD/<runId>/02-改动点清单.md`
|
|
96
|
+
- **UI 图/交互参考**(按 `06-opsx-自动串联指引.md` 解析;无则注明缺失并继续)
|
|
93
97
|
3. 一次性生成 openspec change(`proposal.md`、`design.md`、`specs/`、`tasks.md` 等)
|
|
98
|
+
4. 更新 `05-openspec-提案草稿.md`;`.session-state.json` → `status: propose_done`
|
|
94
99
|
|
|
95
|
-
**本阶段交付物 = openspec
|
|
96
|
-
|
|
100
|
+
**本阶段交付物 = openspec 提案产物**。
|
|
101
|
+
**本阶段禁止**:写 `src/`、跑 delivery-pipeline。
|
|
97
102
|
|
|
98
|
-
|
|
99
|
-
- 执行 `/opsx-apply`(留给阶段 D)
|
|
103
|
+
**默认**:提案 apply-ready 后 **立即** 进入阶段 D(勿问用户「是否 apply」),除非用户说了「只提案」。
|
|
100
104
|
|
|
101
105
|
---
|
|
102
106
|
|
|
103
107
|
### 阶段 D — Apply + TDD + Playwright 交付(禁止裸写代码收尾)
|
|
104
|
-
**触发条件**:阶段 C 的 openspec 提案已 apply-ready
|
|
108
|
+
**触发条件**:阶段 C 的 openspec 提案已 apply-ready(`autoChainOpsx` 默认下与阶段 C **同一连续执行**)。
|
|
105
109
|
|
|
106
110
|
**严禁**:跳过 skill 链、不按 `tasks.md` TDD、不跑 delivery-pipeline 就改 `src/` 并宣称完成。
|
|
107
111
|
|