sdd-flow-kit 0.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.en.md +36 -0
- package/README.md +54 -0
- package/dist/adapters/index.js +89 -0
- package/dist/adapters/types.js +2 -0
- package/dist/cli.js +211 -0
- package/dist/core/fs.js +27 -0
- package/dist/core/templates.js +27 -0
- package/dist/core/types.js +2 -0
- package/dist/core/utils.js +17 -0
- package/dist/index.js +14 -0
- package/dist/steps/doctor.js +76 -0
- package/dist/steps/invokeFlow.js +24 -0
- package/dist/steps/runFlow.js +78 -0
- package/dist/steps/setupProject.js +189 -0
- package/dist/steps/startFlow.js +101 -0
- package/package.json +27 -0
- package/src/templates/artifacts/01-adi-doc-skill-/346/214/207/345/274/225.template.md +15 -0
- package/src/templates/artifacts/01-/351/234/200/346/261/202/345/210/206/346/236/220/346/212/245/345/221/212.template.md +22 -0
- package/src/templates/artifacts/02-/346/224/271/345/212/250/347/202/271/346/270/205/345/215/225.template.md +8 -0
- package/src/templates/artifacts/02-/351/234/200/346/261/202/345/210/206/346/236/220/346/217/220/347/244/272/350/257/215.template.md +20 -0
- package/src/templates/artifacts/03-/345/276/205/347/241/256/350/256/244/351/227/256/351/242/230/346/270/205/345/215/225.template.md +6 -0
- package/src/templates/artifacts/04-/346/212/200/346/234/257/346/226/207/346/241/243.template.md +21 -0
- package/src/templates/artifacts/05-openspec-/346/217/220/346/241/210/350/215/211/347/250/277.template.md +16 -0
- package/src/templates/prompts/02-sdd-loop-prompt.md +84 -0
package/README.en.md
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# sdd-flow-kit
|
|
2
|
+
|
|
3
|
+
#### Description
|
|
4
|
+
自动化开发流程
|
|
5
|
+
|
|
6
|
+
#### Software Architecture
|
|
7
|
+
Software architecture description
|
|
8
|
+
|
|
9
|
+
#### Installation
|
|
10
|
+
|
|
11
|
+
1. xxxx
|
|
12
|
+
2. xxxx
|
|
13
|
+
3. xxxx
|
|
14
|
+
|
|
15
|
+
#### Instructions
|
|
16
|
+
|
|
17
|
+
1. xxxx
|
|
18
|
+
2. xxxx
|
|
19
|
+
3. xxxx
|
|
20
|
+
|
|
21
|
+
#### Contribution
|
|
22
|
+
|
|
23
|
+
1. Fork the repository
|
|
24
|
+
2. Create Feat_xxx branch
|
|
25
|
+
3. Commit your code
|
|
26
|
+
4. Create Pull Request
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
#### Gitee Feature
|
|
30
|
+
|
|
31
|
+
1. You can use Readme\_XXX.md to support different languages, such as Readme\_en.md, Readme\_zh.md
|
|
32
|
+
2. Gitee blog [blog.gitee.com](https://blog.gitee.com)
|
|
33
|
+
3. Explore open source project [https://gitee.com/explore](https://gitee.com/explore)
|
|
34
|
+
4. The most valuable open source project [GVP](https://gitee.com/gvp)
|
|
35
|
+
5. The manual of Gitee [https://gitee.com/help](https://gitee.com/help)
|
|
36
|
+
6. The most popular members [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/)
|
package/README.md
ADDED
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# sdd-flow-kit
|
|
2
|
+
|
|
3
|
+
#### 介绍
|
|
4
|
+
跨工具(Cursor Claude Code / OpenClaw / Codex 等)的“SDD 需求分析 + 产物编排”自动化工程骨架。
|
|
5
|
+
|
|
6
|
+
它把你的 5 步流程拆成可复用的“流程引擎 + 产物模板 + Adapter 适配层”,并在目标项目下生成固定结构的产物文件。
|
|
7
|
+
|
|
8
|
+
注意:不同 AI 工具在“真正执行 LLM/写入报告”的能力不同;因此当前版本会把第 2 步 SDD 提示词、产物模板与调用指引打通,让你在 Cursor/Claude/Codex/OpenClaw 中直接执行提示词完成内容生成。
|
|
9
|
+
|
|
10
|
+
#### 软件架构
|
|
11
|
+
软件架构说明
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
#### 安装教程
|
|
15
|
+
|
|
16
|
+
1. 进入你要开发的项目目录(例如 `adInsight-web` 或任意工程根目录)
|
|
17
|
+
2. 安装(本地/私有 npm 方式均可):`npm i -D /Users/lixinxin/Desktop/Huanwang/sdd-flow-kit` 或发布后 `npm i -D sdd-flow-kit`
|
|
18
|
+
3. 先初始化环境(默认 `claude-code`):`npx sdd-flow-kit setup`
|
|
19
|
+
4. 启动:`npx sdd-flow-kit run --product ADI --version 2.3.2`
|
|
20
|
+
5. 预演:`npx sdd-flow-kit run --product ADI --version 2.3.2 --dry-run`
|
|
21
|
+
6. 一句话触发:`npx sdd-flow-kit invoke "开发 ADI 2.3.2 需求"`
|
|
22
|
+
7. 环境健检:`npx sdd-flow-kit doctor --agent claude-code`
|
|
23
|
+
|
|
24
|
+
#### 使用说明
|
|
25
|
+
|
|
26
|
+
1. `setup`:提示选择开发环境(cursor/claude-code/codex/openclaw,默认 claude-code)与开发分支(无则自动创建),并检测/安装 openspec+superpower 基础环境
|
|
27
|
+
2. `start`:创建 `openspec/PRD/<YYYYMMDD>-<product>-<version>/` 并生成 1~5 步所需模板
|
|
28
|
+
3. `run`:先执行 `setup` 逻辑,再生成流程骨架与 `AUTO-RUN-CHECKLIST.md`
|
|
29
|
+
4. 第 2 步 SDD 循环提示词与第 4/5 步入口文件可直接在 Cursor/Claude/Codex/OpenClaw 使用
|
|
30
|
+
5. `invoke`:从自然语言解析 product/version,并自动触发 `run`
|
|
31
|
+
6. `doctor`:检查 git/npx/agent CLI/openspec/tool 目录,输出可机读诊断结果
|
|
32
|
+
|
|
33
|
+
#### 当前自动化边界
|
|
34
|
+
|
|
35
|
+
- 已支持:跨工具统一目录结构、提示词、报告模板、执行清单
|
|
36
|
+
- 已支持:环境检测与按工具自动执行 `npx @lixin5257xxx/opsx-workflow setup ...`
|
|
37
|
+
- 自动化增强:`claude-code` adapter 支持 `claude -p` 自动执行并记录 `AUTO-RUN-LOG.md`(失败自动降级人工)
|
|
38
|
+
|
|
39
|
+
#### 参与贡献
|
|
40
|
+
|
|
41
|
+
1. Fork 本仓库
|
|
42
|
+
2. 新建 Feat_xxx 分支
|
|
43
|
+
3. 提交代码
|
|
44
|
+
4. 新建 Pull Request
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
#### 特技
|
|
48
|
+
|
|
49
|
+
1. 使用 Readme\_XXX.md 来支持不同的语言,例如 Readme\_en.md, Readme\_zh.md
|
|
50
|
+
2. Gitee 官方博客 [blog.gitee.com](https://blog.gitee.com)
|
|
51
|
+
3. 你可以 [https://gitee.com/explore](https://gitee.com/explore) 这个地址来了解 Gitee 上的优秀开源项目
|
|
52
|
+
4. [GVP](https://gitee.com/gvp) 全称是 Gitee 最有价值开源项目,是综合评定出的优秀开源项目
|
|
53
|
+
5. Gitee 官方提供的使用手册 [https://gitee.com/help](https://gitee.com/help)
|
|
54
|
+
6. Gitee 封面人物是一档用来展示 Gitee 会员风采的栏目 [https://gitee.com/gitee-stars/](https://gitee.com/gitee-stars/)
|
|
@@ -0,0 +1,89 @@
|
|
|
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.getAdapter = getAdapter;
|
|
7
|
+
const node_child_process_1 = require("node:child_process");
|
|
8
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
9
|
+
const node_process_1 = __importDefault(require("node:process"));
|
|
10
|
+
const fs_1 = require("../core/fs");
|
|
11
|
+
function hasCommand(cmd) {
|
|
12
|
+
const result = (0, node_child_process_1.spawnSync)("which", [cmd], { stdio: "ignore" });
|
|
13
|
+
return result.status === 0;
|
|
14
|
+
}
|
|
15
|
+
function createManualAdapter(name, displayName, bins) {
|
|
16
|
+
return {
|
|
17
|
+
name,
|
|
18
|
+
displayName,
|
|
19
|
+
async checkAvailable() {
|
|
20
|
+
return bins.some((bin) => hasCommand(bin));
|
|
21
|
+
},
|
|
22
|
+
async runFlow() {
|
|
23
|
+
return false;
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
}
|
|
27
|
+
async function runClaudeFlow(args) {
|
|
28
|
+
const hasClaude = hasCommand("claude");
|
|
29
|
+
if (!hasClaude)
|
|
30
|
+
return false;
|
|
31
|
+
const sddPrompt = await (0, fs_1.readFileIfExists)(node_path_1.default.join(args.outputRoot, "02-需求分析提示词.md"));
|
|
32
|
+
if (!sddPrompt)
|
|
33
|
+
return false;
|
|
34
|
+
const prompt = [
|
|
35
|
+
`你在项目目录内执行需求流程,runId=${args.runId}。`,
|
|
36
|
+
"请按以下顺序自动完成:",
|
|
37
|
+
"1) 读取并执行 01-获取需求文档指引.md,完成 source/PRD.md 与 source/接口文档.md 回填(若无法访问外部系统,请保留占位并写明阻塞原因)。",
|
|
38
|
+
"2) 执行 02-需求分析提示词.md,更新 01-需求分析报告.md、02-改动点清单.md、03-待确认问题清单.md。",
|
|
39
|
+
"3) 基于 01/02/03 补全 04-技术文档草稿.md。",
|
|
40
|
+
"4) 更新 05-openspec-提案草稿.md 为可直接用于 openspec+superpowers 的输入。",
|
|
41
|
+
"完成后输出一句:SDD flow completed",
|
|
42
|
+
"",
|
|
43
|
+
"以下是第2步提示词:",
|
|
44
|
+
sddPrompt,
|
|
45
|
+
].join("\n");
|
|
46
|
+
const commandResult = (0, node_child_process_1.spawnSync)("claude", ["-p", prompt], {
|
|
47
|
+
cwd: args.outputRoot,
|
|
48
|
+
encoding: "utf8",
|
|
49
|
+
env: node_process_1.default.env,
|
|
50
|
+
});
|
|
51
|
+
await (0, fs_1.writeTextFileEnsuredDir)(node_path_1.default.join(args.outputRoot, "AUTO-RUN-LOG.md"), [
|
|
52
|
+
"# AUTO-RUN-LOG",
|
|
53
|
+
"",
|
|
54
|
+
`agent: claude-code`,
|
|
55
|
+
`status: ${commandResult.status ?? "unknown"}`,
|
|
56
|
+
"",
|
|
57
|
+
"## stdout",
|
|
58
|
+
commandResult.stdout || "",
|
|
59
|
+
"",
|
|
60
|
+
"## stderr",
|
|
61
|
+
commandResult.stderr || "",
|
|
62
|
+
].join("\n"));
|
|
63
|
+
if (commandResult.status !== 0)
|
|
64
|
+
return false;
|
|
65
|
+
return /SDD flow completed/i.test(commandResult.stdout || "");
|
|
66
|
+
}
|
|
67
|
+
function getAdapter(name) {
|
|
68
|
+
switch (name) {
|
|
69
|
+
case "cursor":
|
|
70
|
+
return createManualAdapter("cursor", "Cursor", ["cursor"]);
|
|
71
|
+
case "claude-code":
|
|
72
|
+
return {
|
|
73
|
+
name: "claude-code",
|
|
74
|
+
displayName: "Claude Code",
|
|
75
|
+
async checkAvailable() {
|
|
76
|
+
return hasCommand("claude");
|
|
77
|
+
},
|
|
78
|
+
async runFlow(args) {
|
|
79
|
+
return runClaudeFlow(args);
|
|
80
|
+
},
|
|
81
|
+
};
|
|
82
|
+
case "codex":
|
|
83
|
+
return createManualAdapter("codex", "Codex", ["codex"]);
|
|
84
|
+
case "openclaw":
|
|
85
|
+
return createManualAdapter("openclaw", "OpenClaw", ["openclaw"]);
|
|
86
|
+
default:
|
|
87
|
+
return createManualAdapter("custom", "Custom Agent", []);
|
|
88
|
+
}
|
|
89
|
+
}
|
package/dist/cli.js
ADDED
|
@@ -0,0 +1,211 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
"use strict";
|
|
3
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
4
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
5
|
+
};
|
|
6
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
7
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
8
|
+
const node_process_1 = __importDefault(require("node:process"));
|
|
9
|
+
const startFlow_1 = require("./steps/startFlow");
|
|
10
|
+
const runFlow_1 = require("./steps/runFlow");
|
|
11
|
+
const utils_1 = require("./core/utils");
|
|
12
|
+
const setupProject_1 = require("./steps/setupProject");
|
|
13
|
+
const doctor_1 = require("./steps/doctor");
|
|
14
|
+
const invokeFlow_1 = require("./steps/invokeFlow");
|
|
15
|
+
const ALLOWED_AGENTS = ["cursor", "claude-code", "codex", "openclaw", "custom"];
|
|
16
|
+
function getArgValue(flag) {
|
|
17
|
+
const idx = node_process_1.default.argv.indexOf(flag);
|
|
18
|
+
if (idx === -1)
|
|
19
|
+
return null;
|
|
20
|
+
const v = node_process_1.default.argv[idx + 1];
|
|
21
|
+
if (!v)
|
|
22
|
+
return null;
|
|
23
|
+
return v;
|
|
24
|
+
}
|
|
25
|
+
function printHelp() {
|
|
26
|
+
// eslint-disable-next-line no-console
|
|
27
|
+
console.log([
|
|
28
|
+
"sdd-flow-kit 用法:",
|
|
29
|
+
"",
|
|
30
|
+
"1) 生成流程产物骨架:",
|
|
31
|
+
" npx sdd-flow-kit start --product ADI --version 2.3.2",
|
|
32
|
+
"",
|
|
33
|
+
"2) 初始化环境(环境选择 + 分支处理 + opsx 检测安装):",
|
|
34
|
+
" npx sdd-flow-kit setup --agent claude-code",
|
|
35
|
+
"",
|
|
36
|
+
"3) 生成骨架并进入可执行模式(推荐):",
|
|
37
|
+
" npx sdd-flow-kit run --product ADI --version 2.3.2 --agent claude-code",
|
|
38
|
+
"",
|
|
39
|
+
"4) 一句话触发:",
|
|
40
|
+
" npx sdd-flow-kit invoke \"开发 ADI 2.3.2 需求\"",
|
|
41
|
+
"",
|
|
42
|
+
"5) 环境健检:",
|
|
43
|
+
" npx sdd-flow-kit doctor --agent claude-code",
|
|
44
|
+
"",
|
|
45
|
+
"可选参数:",
|
|
46
|
+
" --projectRoot <path> 目标项目根目录(默认当前目录)",
|
|
47
|
+
" --agent <name> cursor | claude-code | codex | openclaw | custom(默认 claude-code)",
|
|
48
|
+
" --branch <name> 开发分支;不存在会自动创建",
|
|
49
|
+
" -y 非交互模式(使用默认值)",
|
|
50
|
+
" --dry-run 预演模式:仅检测并输出计划,不执行写入/安装/切分支",
|
|
51
|
+
" --plan-json 输出机器可读 JSON 计划结果",
|
|
52
|
+
"",
|
|
53
|
+
"说明:当前版本会生成 openspec/PRD/<runId>/ 下的模板与提示词文件,",
|
|
54
|
+
"并把“需要你在 Cursor/Claude Code/Codex/OpenClaw 中执行”的任务显式化。",
|
|
55
|
+
].join("\n"));
|
|
56
|
+
}
|
|
57
|
+
async function main() {
|
|
58
|
+
const cmd = node_process_1.default.argv[2];
|
|
59
|
+
if (!cmd || cmd === "--help" || cmd === "-h") {
|
|
60
|
+
printHelp();
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
if (cmd !== "start" && cmd !== "run" && cmd !== "setup" && cmd !== "doctor" && cmd !== "invoke") {
|
|
64
|
+
printHelp();
|
|
65
|
+
node_process_1.default.exitCode = 1;
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
const product = getArgValue("--product");
|
|
69
|
+
const version = getArgValue("--version");
|
|
70
|
+
const projectRoot = getArgValue("--projectRoot") ?? node_process_1.default.cwd();
|
|
71
|
+
const branch = getArgValue("--branch") ?? undefined;
|
|
72
|
+
const yes = node_process_1.default.argv.includes("-y");
|
|
73
|
+
const dryRun = node_process_1.default.argv.includes("--dry-run");
|
|
74
|
+
const planJson = node_process_1.default.argv.includes("--plan-json");
|
|
75
|
+
const agentValue = getArgValue("--agent") ?? "claude-code";
|
|
76
|
+
if (!ALLOWED_AGENTS.includes(agentValue)) {
|
|
77
|
+
// eslint-disable-next-line no-console
|
|
78
|
+
console.error(`不支持的 --agent: ${agentValue}`);
|
|
79
|
+
printHelp();
|
|
80
|
+
node_process_1.default.exitCode = 1;
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
const agent = agentValue;
|
|
84
|
+
if (cmd === "doctor") {
|
|
85
|
+
const doctor = await (0, doctor_1.runDoctor)({ projectRoot: node_path_1.default.resolve(projectRoot), agent });
|
|
86
|
+
// eslint-disable-next-line no-console
|
|
87
|
+
console.log(`doctor=${doctor.ok ? "ok" : "failed"}, agent=${agent}`);
|
|
88
|
+
for (const check of doctor.checks) {
|
|
89
|
+
// eslint-disable-next-line no-console
|
|
90
|
+
console.log(`[${check.ok ? "OK" : "FAIL"}] ${check.name}: ${check.detail}`);
|
|
91
|
+
}
|
|
92
|
+
if (planJson) {
|
|
93
|
+
// eslint-disable-next-line no-console
|
|
94
|
+
console.log(JSON.stringify({ command: "doctor", projectRoot: node_path_1.default.resolve(projectRoot), result: doctor }, null, 2));
|
|
95
|
+
}
|
|
96
|
+
if (!doctor.ok)
|
|
97
|
+
node_process_1.default.exitCode = 1;
|
|
98
|
+
return;
|
|
99
|
+
}
|
|
100
|
+
if (cmd === "setup") {
|
|
101
|
+
const result = await (0, setupProject_1.setupProject)({
|
|
102
|
+
projectRoot: node_path_1.default.resolve(projectRoot),
|
|
103
|
+
agent,
|
|
104
|
+
branch,
|
|
105
|
+
yes,
|
|
106
|
+
dryRun,
|
|
107
|
+
});
|
|
108
|
+
// eslint-disable-next-line no-console
|
|
109
|
+
console.log(`setup 完成:agent=${result.agent}, branch=${result.branch}, setupExecuted=${result.setupExecuted}, dryRun=${dryRun}`);
|
|
110
|
+
if (result.plannedActions.length > 0) {
|
|
111
|
+
// eslint-disable-next-line no-console
|
|
112
|
+
console.log(`planned: ${result.plannedActions.join(" | ")}`);
|
|
113
|
+
}
|
|
114
|
+
if (planJson) {
|
|
115
|
+
// eslint-disable-next-line no-console
|
|
116
|
+
console.log(JSON.stringify({
|
|
117
|
+
command: "setup",
|
|
118
|
+
projectRoot: node_path_1.default.resolve(projectRoot),
|
|
119
|
+
dryRun,
|
|
120
|
+
result,
|
|
121
|
+
}, null, 2));
|
|
122
|
+
}
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
if (!product || !version) {
|
|
126
|
+
if (cmd === "invoke") {
|
|
127
|
+
const raw = node_process_1.default.argv
|
|
128
|
+
.slice(3)
|
|
129
|
+
.filter((token) => !token.startsWith("--") && token !== "-y")
|
|
130
|
+
.join(" ")
|
|
131
|
+
.trim();
|
|
132
|
+
const fromFlag = getArgValue("--text");
|
|
133
|
+
const text = (fromFlag ?? raw).trim();
|
|
134
|
+
const parsed = (0, invokeFlow_1.parseInvokeText)(text);
|
|
135
|
+
if (!parsed) {
|
|
136
|
+
throw new Error("invoke 解析失败,请使用:开发 ADI 2.3.2 需求");
|
|
137
|
+
}
|
|
138
|
+
const resolvedRoot = node_path_1.default.resolve(projectRoot);
|
|
139
|
+
const options = (0, invokeFlow_1.toRunOptions)({
|
|
140
|
+
parsed,
|
|
141
|
+
projectRoot: resolvedRoot,
|
|
142
|
+
branch,
|
|
143
|
+
yes,
|
|
144
|
+
dryRun,
|
|
145
|
+
agent,
|
|
146
|
+
});
|
|
147
|
+
const result = await (0, runFlow_1.runFlow)(options);
|
|
148
|
+
// eslint-disable-next-line no-console
|
|
149
|
+
console.log(`invoke 成功:${parsed.product} ${parsed.version} -> runId=${result.runId}`);
|
|
150
|
+
if (planJson) {
|
|
151
|
+
// eslint-disable-next-line no-console
|
|
152
|
+
console.log(JSON.stringify({ command: "invoke", input: text, parsed, result }, null, 2));
|
|
153
|
+
}
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
printHelp();
|
|
157
|
+
node_process_1.default.exitCode = 1;
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
const resolvedRoot = node_path_1.default.resolve(projectRoot);
|
|
161
|
+
const runId = `${(0, utils_1.formatYYYYMMDD)(new Date())}-${product}-${version}`;
|
|
162
|
+
if (cmd === "start") {
|
|
163
|
+
await (0, startFlow_1.startFlowScaffold)({
|
|
164
|
+
product,
|
|
165
|
+
version,
|
|
166
|
+
projectRoot: resolvedRoot,
|
|
167
|
+
});
|
|
168
|
+
// eslint-disable-next-line no-console
|
|
169
|
+
console.log(`已生成 SDD 流程产物骨架:openspec/PRD/${runId}/`);
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
const result = await (0, runFlow_1.runFlow)({
|
|
173
|
+
product,
|
|
174
|
+
version,
|
|
175
|
+
projectRoot: resolvedRoot,
|
|
176
|
+
agent,
|
|
177
|
+
branch,
|
|
178
|
+
yes,
|
|
179
|
+
dryRun,
|
|
180
|
+
});
|
|
181
|
+
if (dryRun) {
|
|
182
|
+
// eslint-disable-next-line no-console
|
|
183
|
+
console.log("dry-run 模式:未创建流程文件,仅输出检测与计划。");
|
|
184
|
+
}
|
|
185
|
+
// eslint-disable-next-line no-console
|
|
186
|
+
console.log(`流程已初始化:openspec/PRD/${result.runId}/`);
|
|
187
|
+
// eslint-disable-next-line no-console
|
|
188
|
+
console.log(`agent=${result.adapterName}, cli=${result.adapterAvailable ? "detected" : "missing"}, autoExecute=${result.executed ? "yes" : "manual"}`);
|
|
189
|
+
// eslint-disable-next-line no-console
|
|
190
|
+
console.log(`branch=${result.branch}, envSetup=${result.setupExecuted ? "installed" : "already-ready"}`);
|
|
191
|
+
if (result.plannedActions.length > 0) {
|
|
192
|
+
// eslint-disable-next-line no-console
|
|
193
|
+
console.log(`planned: ${result.plannedActions.join(" | ")}`);
|
|
194
|
+
}
|
|
195
|
+
if (planJson) {
|
|
196
|
+
// eslint-disable-next-line no-console
|
|
197
|
+
console.log(JSON.stringify({
|
|
198
|
+
command: "run",
|
|
199
|
+
projectRoot: resolvedRoot,
|
|
200
|
+
dryRun,
|
|
201
|
+
product,
|
|
202
|
+
version,
|
|
203
|
+
result,
|
|
204
|
+
}, null, 2));
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
main().catch((e) => {
|
|
208
|
+
// eslint-disable-next-line no-console
|
|
209
|
+
console.error(e);
|
|
210
|
+
node_process_1.default.exitCode = 1;
|
|
211
|
+
});
|
package/dist/core/fs.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
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.readFileIfExists = readFileIfExists;
|
|
7
|
+
exports.writeTextFileEnsuredDir = writeTextFileEnsuredDir;
|
|
8
|
+
const promises_1 = __importDefault(require("node:fs/promises"));
|
|
9
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
10
|
+
async function readFileIfExists(filePath) {
|
|
11
|
+
try {
|
|
12
|
+
return await promises_1.default.readFile(filePath, "utf8");
|
|
13
|
+
}
|
|
14
|
+
catch (err) {
|
|
15
|
+
// 仅吞掉文件不存在错误,其它错误需要抛出便于定位问题。
|
|
16
|
+
if (err && typeof err === "object" && "code" in err) {
|
|
17
|
+
const code = err.code;
|
|
18
|
+
if (code === "ENOENT")
|
|
19
|
+
return null;
|
|
20
|
+
}
|
|
21
|
+
throw err;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
async function writeTextFileEnsuredDir(filePath, content) {
|
|
25
|
+
await promises_1.default.mkdir(node_path_1.default.dirname(filePath), { recursive: true });
|
|
26
|
+
await promises_1.default.writeFile(filePath, content, "utf8");
|
|
27
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
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.loadTemplateText = loadTemplateText;
|
|
7
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
8
|
+
const fs_1 = require("./fs");
|
|
9
|
+
function resolveCandidatePaths(relPathFromPackageRoot) {
|
|
10
|
+
// 在 TS 开发态:<pkg>/src/templates/xxx
|
|
11
|
+
// 在编译态:<pkg>/dist/cli.js -> __dirname=<pkg>/dist,所以 src/templates 要向上找两级
|
|
12
|
+
const here = __dirname; // src/core/* 或 dist/*
|
|
13
|
+
const pkgRoot = node_path_1.default.resolve(here, "../.."); // 兼容 src/ 与 dist/
|
|
14
|
+
return [
|
|
15
|
+
node_path_1.default.join(pkgRoot, "src", "templates", relPathFromPackageRoot),
|
|
16
|
+
node_path_1.default.join(pkgRoot, "dist", "templates", relPathFromPackageRoot),
|
|
17
|
+
];
|
|
18
|
+
}
|
|
19
|
+
async function loadTemplateText(relPathFromPackageRoot) {
|
|
20
|
+
const candidates = resolveCandidatePaths(relPathFromPackageRoot);
|
|
21
|
+
for (const filePath of candidates) {
|
|
22
|
+
const content = await (0, fs_1.readFileIfExists)(filePath);
|
|
23
|
+
if (content != null)
|
|
24
|
+
return content;
|
|
25
|
+
}
|
|
26
|
+
throw new Error(`模板文件未找到:${relPathFromPackageRoot}`);
|
|
27
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.formatYYYYMMDD = formatYYYYMMDD;
|
|
4
|
+
exports.ensurePosixRelativePath = ensurePosixRelativePath;
|
|
5
|
+
/**
|
|
6
|
+
* 生成 YYYYMMDD 字符串,用于 openspec/PRD 目录命名。
|
|
7
|
+
*/
|
|
8
|
+
function formatYYYYMMDD(d) {
|
|
9
|
+
const yyyy = d.getFullYear();
|
|
10
|
+
const mm = String(d.getMonth() + 1).padStart(2, "0");
|
|
11
|
+
const dd = String(d.getDate()).padStart(2, "0");
|
|
12
|
+
return `${yyyy}${mm}${dd}`;
|
|
13
|
+
}
|
|
14
|
+
function ensurePosixRelativePath(p) {
|
|
15
|
+
// 用于模板占位符中展示,避免反斜杠导致工具误解析。
|
|
16
|
+
return p.replaceAll("\\", "/");
|
|
17
|
+
}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.toRunOptions = exports.parseInvokeText = exports.runDoctor = exports.setupProject = exports.runFlow = exports.startFlowScaffold = void 0;
|
|
4
|
+
var startFlow_1 = require("./steps/startFlow");
|
|
5
|
+
Object.defineProperty(exports, "startFlowScaffold", { enumerable: true, get: function () { return startFlow_1.startFlowScaffold; } });
|
|
6
|
+
var runFlow_1 = require("./steps/runFlow");
|
|
7
|
+
Object.defineProperty(exports, "runFlow", { enumerable: true, get: function () { return runFlow_1.runFlow; } });
|
|
8
|
+
var setupProject_1 = require("./steps/setupProject");
|
|
9
|
+
Object.defineProperty(exports, "setupProject", { enumerable: true, get: function () { return setupProject_1.setupProject; } });
|
|
10
|
+
var doctor_1 = require("./steps/doctor");
|
|
11
|
+
Object.defineProperty(exports, "runDoctor", { enumerable: true, get: function () { return doctor_1.runDoctor; } });
|
|
12
|
+
var invokeFlow_1 = require("./steps/invokeFlow");
|
|
13
|
+
Object.defineProperty(exports, "parseInvokeText", { enumerable: true, get: function () { return invokeFlow_1.parseInvokeText; } });
|
|
14
|
+
Object.defineProperty(exports, "toRunOptions", { enumerable: true, get: function () { return invokeFlow_1.toRunOptions; } });
|
|
@@ -0,0 +1,76 @@
|
|
|
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.runDoctor = runDoctor;
|
|
7
|
+
const node_child_process_1 = require("node:child_process");
|
|
8
|
+
const promises_1 = __importDefault(require("node:fs/promises"));
|
|
9
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
10
|
+
function hasCommand(cmd) {
|
|
11
|
+
const out = (0, node_child_process_1.spawnSync)("which", [cmd], { stdio: "ignore" });
|
|
12
|
+
return out.status === 0;
|
|
13
|
+
}
|
|
14
|
+
async function hasPath(filePath) {
|
|
15
|
+
try {
|
|
16
|
+
await promises_1.default.access(filePath);
|
|
17
|
+
return true;
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
function agentBin(agent) {
|
|
24
|
+
if (agent === "claude-code")
|
|
25
|
+
return "claude";
|
|
26
|
+
if (agent === "cursor")
|
|
27
|
+
return "cursor";
|
|
28
|
+
if (agent === "codex")
|
|
29
|
+
return "codex";
|
|
30
|
+
if (agent === "openclaw")
|
|
31
|
+
return "openclaw";
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
async function runDoctor(options) {
|
|
35
|
+
const checks = [];
|
|
36
|
+
checks.push({
|
|
37
|
+
name: "git",
|
|
38
|
+
ok: hasCommand("git"),
|
|
39
|
+
detail: "git 命令可用性",
|
|
40
|
+
});
|
|
41
|
+
checks.push({
|
|
42
|
+
name: "npx",
|
|
43
|
+
ok: hasCommand("npx"),
|
|
44
|
+
detail: "npx 命令可用性",
|
|
45
|
+
});
|
|
46
|
+
const bin = agentBin(options.agent);
|
|
47
|
+
if (bin) {
|
|
48
|
+
checks.push({
|
|
49
|
+
name: `${options.agent}-cli`,
|
|
50
|
+
ok: hasCommand(bin),
|
|
51
|
+
detail: `${bin} 命令可用性`,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
checks.push({
|
|
55
|
+
name: "openspec-dir",
|
|
56
|
+
ok: await hasPath(node_path_1.default.join(options.projectRoot, "openspec")),
|
|
57
|
+
detail: "openspec 目录存在性",
|
|
58
|
+
});
|
|
59
|
+
const toolDirMap = {
|
|
60
|
+
cursor: ".cursor",
|
|
61
|
+
"claude-code": ".claude",
|
|
62
|
+
codex: ".codex",
|
|
63
|
+
openclaw: ".openclaw",
|
|
64
|
+
custom: null,
|
|
65
|
+
};
|
|
66
|
+
const expectedDir = toolDirMap[options.agent];
|
|
67
|
+
if (expectedDir) {
|
|
68
|
+
checks.push({
|
|
69
|
+
name: `${options.agent}-dir`,
|
|
70
|
+
ok: await hasPath(node_path_1.default.join(options.projectRoot, expectedDir)),
|
|
71
|
+
detail: `${expectedDir} 目录存在性`,
|
|
72
|
+
});
|
|
73
|
+
}
|
|
74
|
+
const ok = checks.every((c) => c.ok);
|
|
75
|
+
return { ok, checks };
|
|
76
|
+
}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.parseInvokeText = parseInvokeText;
|
|
4
|
+
exports.toRunOptions = toRunOptions;
|
|
5
|
+
function parseInvokeText(text) {
|
|
6
|
+
const normalized = text.trim();
|
|
7
|
+
const match = normalized.match(/开发\s*([A-Za-z][A-Za-z0-9_-]*)\s*([0-9]+(?:\.[0-9]+){1,3})\s*需求?/i);
|
|
8
|
+
if (!match)
|
|
9
|
+
return null;
|
|
10
|
+
const product = match[1].toUpperCase();
|
|
11
|
+
const version = match[2];
|
|
12
|
+
return { product, version };
|
|
13
|
+
}
|
|
14
|
+
function toRunOptions(args) {
|
|
15
|
+
return {
|
|
16
|
+
product: args.parsed.product,
|
|
17
|
+
version: args.parsed.version,
|
|
18
|
+
projectRoot: args.projectRoot,
|
|
19
|
+
branch: args.branch,
|
|
20
|
+
yes: args.yes,
|
|
21
|
+
dryRun: args.dryRun,
|
|
22
|
+
agent: args.agent ?? args.parsed.agent ?? "claude-code",
|
|
23
|
+
};
|
|
24
|
+
}
|
|
@@ -0,0 +1,78 @@
|
|
|
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.runFlow = runFlow;
|
|
7
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
8
|
+
const adapters_1 = require("../adapters");
|
|
9
|
+
const fs_1 = require("../core/fs");
|
|
10
|
+
const startFlow_1 = require("./startFlow");
|
|
11
|
+
const setupProject_1 = require("./setupProject");
|
|
12
|
+
function buildChecklist(args) {
|
|
13
|
+
const availability = args.adapterAvailable ? "已检测到" : "未检测到";
|
|
14
|
+
return [
|
|
15
|
+
"# AUTO-RUN-CHECKLIST",
|
|
16
|
+
"",
|
|
17
|
+
`runId: \`${args.runId}\``,
|
|
18
|
+
`agent: \`${args.adapterName}\`(${availability}本地 CLI)`,
|
|
19
|
+
"",
|
|
20
|
+
"## 你现在可以直接执行",
|
|
21
|
+
`- 在目标 AI 工具中输入:\`开发 ${args.product} ${args.version} 需求\``,
|
|
22
|
+
"- 或按 `RUNME.md` 逐步执行。",
|
|
23
|
+
"",
|
|
24
|
+
"## 最小闭环检查",
|
|
25
|
+
"- Step1 完成后:`source/PRD.md` 已回填",
|
|
26
|
+
"- Step2 提问模式:仅更新 `03-待确认问题清单.md`",
|
|
27
|
+
"- Step2 交付模式:产出 `01/02` 且标记“矛盾点池状态:已闭合”",
|
|
28
|
+
"- Step4 完成后:`04-技术文档草稿.md` 已补全",
|
|
29
|
+
"- Step5 完成后:`05-openspec-提案草稿.md` 已可投喂 openspec",
|
|
30
|
+
"",
|
|
31
|
+
].join("\n");
|
|
32
|
+
}
|
|
33
|
+
async function runFlow(options) {
|
|
34
|
+
const setup = await (0, setupProject_1.setupProject)({
|
|
35
|
+
projectRoot: options.projectRoot,
|
|
36
|
+
agent: options.agent,
|
|
37
|
+
branch: options.branch,
|
|
38
|
+
yes: options.yes,
|
|
39
|
+
dryRun: options.dryRun,
|
|
40
|
+
});
|
|
41
|
+
const scaffold = options.dryRun
|
|
42
|
+
? {
|
|
43
|
+
runId: "dry-run-no-files-generated",
|
|
44
|
+
outputRoot: node_path_1.default.join(options.projectRoot, "openspec", "PRD"),
|
|
45
|
+
}
|
|
46
|
+
: await (0, startFlow_1.startFlowScaffold)(options);
|
|
47
|
+
const adapter = (0, adapters_1.getAdapter)(setup.agent);
|
|
48
|
+
const adapterAvailable = await adapter.checkAvailable();
|
|
49
|
+
const executed = options.dryRun
|
|
50
|
+
? false
|
|
51
|
+
: await adapter.runFlow({
|
|
52
|
+
runId: scaffold.runId,
|
|
53
|
+
outputRoot: scaffold.outputRoot,
|
|
54
|
+
product: options.product,
|
|
55
|
+
version: options.version,
|
|
56
|
+
});
|
|
57
|
+
const checklist = buildChecklist({
|
|
58
|
+
runId: scaffold.runId,
|
|
59
|
+
outputRoot: scaffold.outputRoot,
|
|
60
|
+
adapterName: adapter.displayName,
|
|
61
|
+
adapterAvailable,
|
|
62
|
+
product: options.product,
|
|
63
|
+
version: options.version,
|
|
64
|
+
});
|
|
65
|
+
if (!options.dryRun) {
|
|
66
|
+
await (0, fs_1.writeTextFileEnsuredDir)(node_path_1.default.join(scaffold.outputRoot, "AUTO-RUN-CHECKLIST.md"), checklist);
|
|
67
|
+
}
|
|
68
|
+
return {
|
|
69
|
+
...scaffold,
|
|
70
|
+
adapterName: adapter.displayName,
|
|
71
|
+
adapterAvailable,
|
|
72
|
+
executed,
|
|
73
|
+
branch: setup.branch,
|
|
74
|
+
setupExecuted: setup.setupExecuted,
|
|
75
|
+
dryRun: Boolean(options.dryRun),
|
|
76
|
+
plannedActions: setup.plannedActions,
|
|
77
|
+
};
|
|
78
|
+
}
|
|
@@ -0,0 +1,189 @@
|
|
|
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.setupProject = setupProject;
|
|
7
|
+
const node_child_process_1 = require("node:child_process");
|
|
8
|
+
const promises_1 = __importDefault(require("node:fs/promises"));
|
|
9
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
10
|
+
const promises_2 = require("node:readline/promises");
|
|
11
|
+
const node_process_1 = __importDefault(require("node:process"));
|
|
12
|
+
const fs_1 = require("../core/fs");
|
|
13
|
+
const EDITOR_CHOICES = ["claude-code", "cursor", "codex", "openclaw"];
|
|
14
|
+
function runGit(args, cwd) {
|
|
15
|
+
const out = (0, node_child_process_1.spawnSync)("git", args, { cwd, encoding: "utf8" });
|
|
16
|
+
if (out.status !== 0) {
|
|
17
|
+
const err = out.stderr?.trim() || out.stdout?.trim() || `git ${args.join(" ")} failed`;
|
|
18
|
+
throw new Error(err);
|
|
19
|
+
}
|
|
20
|
+
return out.stdout.trim();
|
|
21
|
+
}
|
|
22
|
+
function pickDefaultBranch(projectRoot) {
|
|
23
|
+
try {
|
|
24
|
+
const current = runGit(["rev-parse", "--abbrev-ref", "HEAD"], projectRoot);
|
|
25
|
+
if (current && current !== "HEAD")
|
|
26
|
+
return current;
|
|
27
|
+
}
|
|
28
|
+
catch {
|
|
29
|
+
// ignore and fallback
|
|
30
|
+
}
|
|
31
|
+
return "develop";
|
|
32
|
+
}
|
|
33
|
+
function listLocalBranches(projectRoot) {
|
|
34
|
+
try {
|
|
35
|
+
const output = runGit(["branch", "--format", "%(refname:short)"], projectRoot);
|
|
36
|
+
return output
|
|
37
|
+
.split("\n")
|
|
38
|
+
.map((s) => s.trim())
|
|
39
|
+
.filter(Boolean);
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return [];
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
async function askAgent(defaultAgent) {
|
|
46
|
+
if (!node_process_1.default.stdin.isTTY)
|
|
47
|
+
return defaultAgent;
|
|
48
|
+
const rl = (0, promises_2.createInterface)({ input: node_process_1.default.stdin, output: node_process_1.default.stdout });
|
|
49
|
+
const prompt = [
|
|
50
|
+
"请选择开发环境:",
|
|
51
|
+
"1) claude-code (default)",
|
|
52
|
+
"2) cursor",
|
|
53
|
+
"3) codex",
|
|
54
|
+
"4) openclaw",
|
|
55
|
+
"输入编号(回车默认 1):",
|
|
56
|
+
].join("\n");
|
|
57
|
+
const answer = (await rl.question(`${prompt}\n`)).trim();
|
|
58
|
+
rl.close();
|
|
59
|
+
if (!answer)
|
|
60
|
+
return defaultAgent;
|
|
61
|
+
const idx = Number(answer);
|
|
62
|
+
if (Number.isNaN(idx) || idx < 1 || idx > EDITOR_CHOICES.length)
|
|
63
|
+
return defaultAgent;
|
|
64
|
+
return EDITOR_CHOICES[idx - 1];
|
|
65
|
+
}
|
|
66
|
+
async function askBranch(defaultBranch) {
|
|
67
|
+
if (!node_process_1.default.stdin.isTTY)
|
|
68
|
+
return defaultBranch;
|
|
69
|
+
const rl = (0, promises_2.createInterface)({ input: node_process_1.default.stdin, output: node_process_1.default.stdout });
|
|
70
|
+
const answer = (await rl.question(`请输入开发分支(默认 ${defaultBranch}):\n`)).trim();
|
|
71
|
+
rl.close();
|
|
72
|
+
return answer || defaultBranch;
|
|
73
|
+
}
|
|
74
|
+
function isEditorEnvironmentReady(projectRoot, agent) {
|
|
75
|
+
const checks = {
|
|
76
|
+
cursor: ["openspec", ".cursor"],
|
|
77
|
+
"claude-code": ["openspec", ".claude"],
|
|
78
|
+
codex: ["openspec", ".codex"],
|
|
79
|
+
openclaw: ["openspec", ".openclaw"],
|
|
80
|
+
custom: ["openspec"],
|
|
81
|
+
};
|
|
82
|
+
const targetPaths = checks[agent] ?? ["openspec"];
|
|
83
|
+
return Promise.all(targetPaths.map(async (p) => {
|
|
84
|
+
try {
|
|
85
|
+
await promises_1.default.access(node_path_1.default.join(projectRoot, p));
|
|
86
|
+
return true;
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
return false;
|
|
90
|
+
}
|
|
91
|
+
})).then((arr) => arr.every(Boolean));
|
|
92
|
+
}
|
|
93
|
+
function buildSetupCommand(projectRoot, agent) {
|
|
94
|
+
const baseArgs = ["@lixin5257xxx/opsx-workflow", "setup"];
|
|
95
|
+
if (agent === "cursor") {
|
|
96
|
+
return {
|
|
97
|
+
cmd: "npx",
|
|
98
|
+
args: [...baseArgs, "--editor", "cursor", "--project", projectRoot, "-y"],
|
|
99
|
+
};
|
|
100
|
+
}
|
|
101
|
+
if (agent === "claude-code") {
|
|
102
|
+
return {
|
|
103
|
+
cmd: "npx",
|
|
104
|
+
args: [...baseArgs, "--editor", "claude-code", "-y"],
|
|
105
|
+
};
|
|
106
|
+
}
|
|
107
|
+
if (agent === "openclaw") {
|
|
108
|
+
return {
|
|
109
|
+
cmd: "npx",
|
|
110
|
+
args: [...baseArgs, "--editor", "openclaw", "-y"],
|
|
111
|
+
};
|
|
112
|
+
}
|
|
113
|
+
return {
|
|
114
|
+
cmd: "npx",
|
|
115
|
+
args: [...baseArgs, "--editor", "codex", "-y"],
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
function ensureBranch(projectRoot, branch) {
|
|
119
|
+
const branches = listLocalBranches(projectRoot);
|
|
120
|
+
if (branches.includes(branch)) {
|
|
121
|
+
runGit(["checkout", branch], projectRoot);
|
|
122
|
+
return { branch, created: false };
|
|
123
|
+
}
|
|
124
|
+
runGit(["checkout", "-b", branch], projectRoot);
|
|
125
|
+
return { branch, created: true };
|
|
126
|
+
}
|
|
127
|
+
async function setupProject(options) {
|
|
128
|
+
const defaultAgent = options.agent ?? "claude-code";
|
|
129
|
+
const selectedAgent = options.yes ? defaultAgent : await askAgent(defaultAgent);
|
|
130
|
+
const defaultBranch = options.branch ?? pickDefaultBranch(options.projectRoot);
|
|
131
|
+
const selectedBranch = options.yes ? defaultBranch : await askBranch(defaultBranch);
|
|
132
|
+
const plannedActions = [];
|
|
133
|
+
const branches = listLocalBranches(options.projectRoot);
|
|
134
|
+
const branchExists = branches.includes(selectedBranch);
|
|
135
|
+
const branchResult = options.dryRun
|
|
136
|
+
? { branch: selectedBranch, created: !branchExists }
|
|
137
|
+
: ensureBranch(options.projectRoot, selectedBranch);
|
|
138
|
+
if (options.dryRun) {
|
|
139
|
+
plannedActions.push(branchExists
|
|
140
|
+
? `would checkout existing branch: ${selectedBranch}`
|
|
141
|
+
: `would create and checkout branch: ${selectedBranch}`);
|
|
142
|
+
}
|
|
143
|
+
let environmentReady = await isEditorEnvironmentReady(options.projectRoot, selectedAgent);
|
|
144
|
+
let setupExecuted = false;
|
|
145
|
+
if (!environmentReady) {
|
|
146
|
+
const { cmd, args } = buildSetupCommand(options.projectRoot, selectedAgent);
|
|
147
|
+
if (options.dryRun) {
|
|
148
|
+
plannedActions.push(`would run: ${cmd} ${args.join(" ")}`);
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
const ret = (0, node_child_process_1.spawnSync)(cmd, args, {
|
|
152
|
+
cwd: options.projectRoot,
|
|
153
|
+
stdio: "inherit",
|
|
154
|
+
env: node_process_1.default.env,
|
|
155
|
+
});
|
|
156
|
+
if (ret.status !== 0) {
|
|
157
|
+
throw new Error(`环境安装失败:${cmd} ${args.join(" ")}`);
|
|
158
|
+
}
|
|
159
|
+
setupExecuted = true;
|
|
160
|
+
environmentReady = await isEditorEnvironmentReady(options.projectRoot, selectedAgent);
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
else if (options.dryRun) {
|
|
164
|
+
plannedActions.push("environment already ready, no setup needed");
|
|
165
|
+
}
|
|
166
|
+
const summary = [
|
|
167
|
+
"# SETUP-SUMMARY",
|
|
168
|
+
"",
|
|
169
|
+
`agent: ${selectedAgent}`,
|
|
170
|
+
`branch: ${branchResult.branch}`,
|
|
171
|
+
`branchCreated: ${branchResult.created}`,
|
|
172
|
+
`environmentReady: ${environmentReady}`,
|
|
173
|
+
`setupExecuted: ${setupExecuted}`,
|
|
174
|
+
`dryRun: ${Boolean(options.dryRun)}`,
|
|
175
|
+
"",
|
|
176
|
+
"plannedActions:",
|
|
177
|
+
...plannedActions.map((x) => `- ${x}`),
|
|
178
|
+
"",
|
|
179
|
+
].join("\n");
|
|
180
|
+
await (0, fs_1.writeTextFileEnsuredDir)(node_path_1.default.join(options.projectRoot, ".sdd-flow-kit", "setup-summary.md"), summary);
|
|
181
|
+
return {
|
|
182
|
+
agent: selectedAgent,
|
|
183
|
+
branch: branchResult.branch,
|
|
184
|
+
branchCreated: branchResult.created,
|
|
185
|
+
environmentReady,
|
|
186
|
+
setupExecuted,
|
|
187
|
+
plannedActions,
|
|
188
|
+
};
|
|
189
|
+
}
|
|
@@ -0,0 +1,101 @@
|
|
|
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.startFlowScaffold = startFlowScaffold;
|
|
7
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
8
|
+
const promises_1 = __importDefault(require("node:fs/promises"));
|
|
9
|
+
const utils_1 = require("../core/utils");
|
|
10
|
+
const fs_1 = require("../core/fs");
|
|
11
|
+
const templates_1 = require("../core/templates");
|
|
12
|
+
function renderTemplate(template, vars) {
|
|
13
|
+
let out = template;
|
|
14
|
+
for (const [k, v] of Object.entries(vars)) {
|
|
15
|
+
out = out.replaceAll(`{{${k}}}`, v);
|
|
16
|
+
}
|
|
17
|
+
return out;
|
|
18
|
+
}
|
|
19
|
+
async function startFlowScaffold(options) {
|
|
20
|
+
const now = new Date();
|
|
21
|
+
const runId = `${(0, utils_1.formatYYYYMMDD)(now)}-${options.product}-${options.version}`;
|
|
22
|
+
const outputRoot = node_path_1.default.join(options.projectRoot, "openspec", "PRD", runId);
|
|
23
|
+
const sourceDir = node_path_1.default.join(outputRoot, "source");
|
|
24
|
+
await promises_1.default.mkdir(sourceDir, { recursive: true });
|
|
25
|
+
// 1) Step1: adi-doc-skill 指引
|
|
26
|
+
const step1 = await (0, templates_1.loadTemplateText)("artifacts/01-adi-doc-skill-指引.template.md");
|
|
27
|
+
await (0, fs_1.writeTextFileEnsuredDir)(node_path_1.default.join(outputRoot, "01-获取需求文档指引.md"), step1);
|
|
28
|
+
// source 占位:让第 2 步提示词在任何工具里都能直接读
|
|
29
|
+
await (0, fs_1.writeTextFileEnsuredDir)(node_path_1.default.join(sourceDir, "PRD.md"), [
|
|
30
|
+
"# source/PRD.md",
|
|
31
|
+
"",
|
|
32
|
+
"请把《PRD》markdown 内容粘贴到这里(由 `adi-doc-skill` 下载并回填)。",
|
|
33
|
+
"",
|
|
34
|
+
"- 建议:保持原有标题层级与编号。",
|
|
35
|
+
].join("\n"));
|
|
36
|
+
await (0, fs_1.writeTextFileEnsuredDir)(node_path_1.default.join(sourceDir, "接口文档.md"), [
|
|
37
|
+
"# source/接口文档.md",
|
|
38
|
+
"",
|
|
39
|
+
"(可选)如果你已经有接口文档,请粘贴到这里;没有也不影响后续 SDD 循环。",
|
|
40
|
+
"",
|
|
41
|
+
"- 注意:接口文档中的字段/示例越完整,分析报告越贴近落地。",
|
|
42
|
+
].join("\n"));
|
|
43
|
+
await (0, fs_1.writeTextFileEnsuredDir)(node_path_1.default.join(sourceDir, "openspec-knowledge-base-summary.md"), [
|
|
44
|
+
"# source/openspec-knowledge-base-summary.md",
|
|
45
|
+
"",
|
|
46
|
+
"(可选)现有知识库摘要/快速索引,建议由你在执行第 2 步前手动整理。",
|
|
47
|
+
"",
|
|
48
|
+
"如果没有这份摘要,你也可以在运行提示词时留空。",
|
|
49
|
+
].join("\n"));
|
|
50
|
+
// 2) Step2: SDD 循环提示词
|
|
51
|
+
const sddLoopPrompt = await (0, templates_1.loadTemplateText)("prompts/02-sdd-loop-prompt.md");
|
|
52
|
+
const step2PromptTemplate = await (0, templates_1.loadTemplateText)("artifacts/02-需求分析提示词.template.md");
|
|
53
|
+
const step2Prompt = renderTemplate(step2PromptTemplate, { SDD_LOOP_PROMPT: sddLoopPrompt });
|
|
54
|
+
await (0, fs_1.writeTextFileEnsuredDir)(node_path_1.default.join(outputRoot, "02-需求分析提示词.md"), step2Prompt);
|
|
55
|
+
// 3) Step2 输出文件占位(由 AI 工具生成并覆盖)
|
|
56
|
+
const file01Template = await (0, templates_1.loadTemplateText)("artifacts/01-需求分析报告.template.md");
|
|
57
|
+
const file02Template = await (0, templates_1.loadTemplateText)("artifacts/02-改动点清单.template.md");
|
|
58
|
+
const file03Template = await (0, templates_1.loadTemplateText)("artifacts/03-待确认问题清单.template.md");
|
|
59
|
+
await (0, fs_1.writeTextFileEnsuredDir)(node_path_1.default.join(outputRoot, "01-需求分析报告.md"), file01Template);
|
|
60
|
+
await (0, fs_1.writeTextFileEnsuredDir)(node_path_1.default.join(outputRoot, "02-改动点清单.md"), file02Template);
|
|
61
|
+
await (0, fs_1.writeTextFileEnsuredDir)(node_path_1.default.join(outputRoot, "03-待确认问题清单.md"), file03Template);
|
|
62
|
+
// 4) Step4 技术文档入口(草稿)
|
|
63
|
+
const techDocTemplate = await (0, templates_1.loadTemplateText)("artifacts/04-技术文档.template.md");
|
|
64
|
+
await (0, fs_1.writeTextFileEnsuredDir)(node_path_1.default.join(outputRoot, "04-技术文档草稿.md"), techDocTemplate);
|
|
65
|
+
// 5) Step5 openspec 提案入口(草稿)
|
|
66
|
+
const openspecEntryTemplate = await (0, templates_1.loadTemplateText)("artifacts/05-openspec-提案草稿.template.md");
|
|
67
|
+
await (0, fs_1.writeTextFileEnsuredDir)(node_path_1.default.join(outputRoot, "05-openspec-提案草稿.md"), openspecEntryTemplate);
|
|
68
|
+
await (0, fs_1.writeTextFileEnsuredDir)(node_path_1.default.join(outputRoot, ".session-state.json"), JSON.stringify({
|
|
69
|
+
product: options.product,
|
|
70
|
+
version: options.version,
|
|
71
|
+
runId,
|
|
72
|
+
outputRoot,
|
|
73
|
+
status: "step1_prd_prompt_generated",
|
|
74
|
+
createdAtISO: new Date().toISOString(),
|
|
75
|
+
}, null, 2));
|
|
76
|
+
// runDir 自述
|
|
77
|
+
await (0, fs_1.writeTextFileEnsuredDir)(node_path_1.default.join(outputRoot, "RUNME.md"), [
|
|
78
|
+
"# RUNME(SDD 流程执行说明)",
|
|
79
|
+
"",
|
|
80
|
+
`本次 runId: \`${runId}\``,
|
|
81
|
+
"",
|
|
82
|
+
"## Step 1:获取需求文档(adi-doc-skill)",
|
|
83
|
+
"请打开 `01-获取需求文档指引.md`,并使用你的 AI 工具执行相应动作,把 PRD/接口文档回填到 `source/`。",
|
|
84
|
+
"",
|
|
85
|
+
"## Step 2:执行 SDD 循环并生成 01/02/03 文件",
|
|
86
|
+
"请打开 `02-需求分析提示词.md`,在 Cursor/Claude Code/Codex/OpenClaw 中执行,并覆盖生成 `01-需求分析报告.md`、`02-改动点清单.md`、`03-待确认问题清单.md`。",
|
|
87
|
+
"",
|
|
88
|
+
"## Step 3:人工核验待确认问题",
|
|
89
|
+
"将 `03-待确认问题清单.md` 收敛为闭环(必要时调整 01/02)。",
|
|
90
|
+
"",
|
|
91
|
+
"## Step 4:输出技术文档草稿",
|
|
92
|
+
"根据 `04-技术文档草稿.md` 完成技术设计说明。",
|
|
93
|
+
"",
|
|
94
|
+
"## Step 5:触发 openspec + superpowers 提案",
|
|
95
|
+
"使用 `05-openspec-提案草稿.md` 作为 openspec 的提案输入入口。",
|
|
96
|
+
].join("\n"));
|
|
97
|
+
return {
|
|
98
|
+
runId,
|
|
99
|
+
outputRoot,
|
|
100
|
+
};
|
|
101
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "sdd-flow-kit",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"private": false,
|
|
5
|
+
"description": "Cross-agent SDD automated development workflow kit (ADI/ADI-like).",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"type": "commonjs",
|
|
8
|
+
"main": "dist/index.js",
|
|
9
|
+
"files": [
|
|
10
|
+
"dist",
|
|
11
|
+
"src/templates",
|
|
12
|
+
"README.md"
|
|
13
|
+
],
|
|
14
|
+
"bin": {
|
|
15
|
+
"sdd-flow-kit": "dist/cli.js"
|
|
16
|
+
},
|
|
17
|
+
"scripts": {
|
|
18
|
+
"build": "tsc -p tsconfig.json",
|
|
19
|
+
"start": "node dist/cli.js",
|
|
20
|
+
"prepublishOnly": "npm run build"
|
|
21
|
+
},
|
|
22
|
+
"devDependencies": {
|
|
23
|
+
"@types/node": "^20.14.10",
|
|
24
|
+
"typescript": "^5.6.3"
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
# 01-获取需求文档(adi-doc-skill 指引)
|
|
2
|
+
|
|
3
|
+
你的步骤 1 目标:从 Confluence 拉取指定需求版本的 Markdown,并落到本目录的 `source/` 下。
|
|
4
|
+
|
|
5
|
+
## 请执行
|
|
6
|
+
1. 使用你当前 AI 工具(Cursor / Claude Code / Codex / OpenClaw)调用现有的 `adi-doc-skill`。
|
|
7
|
+
2. 命令/动作建议:让工具“获取 ADI 2.3.2 需求文档”(或等价的触发词),并把下载结果写入本目录:
|
|
8
|
+
- `source/PRD.md`(PRD 主内容)
|
|
9
|
+
- `source/接口文档.md`(接口文档,如有)
|
|
10
|
+
|
|
11
|
+
## 成功校验
|
|
12
|
+
- 你应该能在 `source/PRD.md` 看到完整 PRD markdown
|
|
13
|
+
|
|
14
|
+
(如果你希望自动回填,请在 adapter 中实现对应能力。)
|
|
15
|
+
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# 02-需求分析提示词(SDD 循环)
|
|
2
|
+
|
|
3
|
+
你需要在 AI 工具中把以下内容作为“用户输入/提示词”执行:
|
|
4
|
+
|
|
5
|
+
--- 你将要运行的提示词开始 ---
|
|
6
|
+
|
|
7
|
+
{{SDD_LOOP_PROMPT}}
|
|
8
|
+
|
|
9
|
+
--- 你将要运行的提示词结束 ---
|
|
10
|
+
|
|
11
|
+
## 输入注入(建议做法)
|
|
12
|
+
- 将 `source/PRD.md` 的内容作为 `{{PRD_MARKDOWN}}` 注入
|
|
13
|
+
- 将 `source/接口文档.md` 的内容作为 `{{INTERFACE_DOC_MARKDOWN}}` 注入(没有则忽略)
|
|
14
|
+
- 将 `openspec/` 目录的现有知识库摘要作为 `{{KNOWLEDGE_BASE_SUMMARY}}` 注入(没有则留空或描述为空)
|
|
15
|
+
|
|
16
|
+
## 输出要求(AI 应该生成的文件)
|
|
17
|
+
- `01-需求分析报告.md`
|
|
18
|
+
- `02-改动点清单.md`
|
|
19
|
+
- `03-待确认问题清单.md`
|
|
20
|
+
|
package/src/templates/artifacts/04-/346/212/200/346/234/257/346/226/207/346/241/243.template.md
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# 04-技术文档草稿(由第 2/3 步产物生成)
|
|
2
|
+
|
|
3
|
+
> 使用输入:
|
|
4
|
+
> - `01-需求分析报告.md`
|
|
5
|
+
> - `02-改动点清单.md`
|
|
6
|
+
> - `03-待确认问题清单.md`
|
|
7
|
+
>
|
|
8
|
+
> 说明:请先把第 3 步“提问/待确认”闭环,否则技术设计可能缺失关键约束。
|
|
9
|
+
|
|
10
|
+
## 总体方案
|
|
11
|
+
- (架构改动概述)
|
|
12
|
+
|
|
13
|
+
## 模块/页面/接口改动
|
|
14
|
+
- (按 `02-改动点清单.md` 列表逐条描述)
|
|
15
|
+
|
|
16
|
+
## 关键数据流与状态治理
|
|
17
|
+
- (说明前端状态/服务端状态边界)
|
|
18
|
+
|
|
19
|
+
## 风险与回滚预案
|
|
20
|
+
- (列出关键风险点与回滚方式)
|
|
21
|
+
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# 05-openspec-提案入口草稿(由第 2/3/4 步汇总)
|
|
2
|
+
|
|
3
|
+
> 目标:把技术文档与改动清单聚合为 openspec / superpower 模式可直接使用的提案描述。
|
|
4
|
+
|
|
5
|
+
## 使用方式
|
|
6
|
+
1. 打开你的 openspec 工作区
|
|
7
|
+
2. 选择(或新建)对应 change
|
|
8
|
+
3. 将本文件中的内容复制给 openspec + superpowers 的“提案输入”
|
|
9
|
+
4. 进入生成 UI 图 + 任务拆解 + 开发阶段
|
|
10
|
+
|
|
11
|
+
## 输入摘要
|
|
12
|
+
- `01-需求分析报告.md`:已完成
|
|
13
|
+
- `02-改动点清单.md`:已完成(仅代码文件改动)
|
|
14
|
+
- `03-待确认问题清单.md`:已闭环(无悬而未决)
|
|
15
|
+
- `04-技术文档草稿.md`:已完成
|
|
16
|
+
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
### 角色设定
|
|
2
|
+
你现在的角色是【前端架构分析专家】与【资深业务分析师】。
|
|
3
|
+
你正在执行 **SDD (Software Design Description) 模式**。这是一个基于“循环探索”的工程环节,目的是在编码前,基于 PRD 和现有知识库,产出完备的需求分析与改动清单。
|
|
4
|
+
|
|
5
|
+
### 🔄 循环工作流 (Loop Protocol)
|
|
6
|
+
请严格遵守以下循环逻辑,直到满足“循环结束条件”:
|
|
7
|
+
1. **输入**:接收 PRD 文档 和 `openspec/` 目录下的现有知识库。
|
|
8
|
+
2. **探索**:扫描现有代码/文档,寻找 PRD 需求的对应点。
|
|
9
|
+
3. **矛盾点识别**:
|
|
10
|
+
* **断点 (Breakpoint)**:代码中已存在但与 PRD 冲突、缺失实现或逻辑无法理解的部分。例如:现有状态机不支持新状态,或常量文件中缺少新角色定义。
|
|
11
|
+
* **起点缺失 (Missing Starting Point)**:PRD 中描述的流程,在现有代码中找不到对应的触发点(如页面入口、事件监听、路由配置)。
|
|
12
|
+
* **推测缺口 (Speculative Gap)**:基于现有代码的假设,在未与人工确认前存在不确定性。例如:现有组件看似可复用,但业务语义可能不同;代码中的隐含约束可能影响新需求。
|
|
13
|
+
4. **判断**:
|
|
14
|
+
* 如果 **存在** 断点/起点缺失/推测缺口 -> 进入 **“提问模式”**。
|
|
15
|
+
* 如果 **不存在** -> 进入 **“交付模式”**。
|
|
16
|
+
5. **迭代**:根据人工反馈更新矛盾点池,直到所有问题闭合。
|
|
17
|
+
|
|
18
|
+
---
|
|
19
|
+
|
|
20
|
+
### 🛑 循环结束条件 (Exit Criteria)
|
|
21
|
+
只有当以下所有条件同时满足时,方可结束循环并输出最终文档:
|
|
22
|
+
1. **✅ 无新断点**:AI 无法再提出新的逻辑断点或疑问。
|
|
23
|
+
2. **✅ 无新起点缺失**:AI 无法再发现新的未覆盖的业务起点。
|
|
24
|
+
3. **✅ 推测缺口闭合**:所有基于代码的推测都已得到验证,没有悬而未决的假设。
|
|
25
|
+
4. **✅ 人工核验**:用户确认“没有偏差和漏洞”。
|
|
26
|
+
5. **✅ 文档完整**:输出的文档结构完整,包含所有必要板块。
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
### 📂 输出规范 (Output Specification)
|
|
31
|
+
请在 `openspec/PRD/[时间戳或需求ID]` 目录下生成以下文件。
|
|
32
|
+
|
|
33
|
+
#### 1. 文件命名规则
|
|
34
|
+
* `[时间戳]` 建议使用 `YYYYMMDD` 格式。
|
|
35
|
+
* 文件名:`01-需求分析报告.md`, `02-改动点清单.md`, `03-待确认问题清单.md`
|
|
36
|
+
|
|
37
|
+
#### 2. 文档模板要求
|
|
38
|
+
|
|
39
|
+
**📄 01-需求分析报告.md**
|
|
40
|
+
* **业务概述**:PRD 核心目标摘要。
|
|
41
|
+
* **现状映射**:现有系统中对应的模块描述(引用 `openspec` 中的现有定义)。
|
|
42
|
+
* **差异分析**:PRD 与现状的具体差异(新增/修改/删除)。
|
|
43
|
+
* **风险评估**:技术风险、兼容性问题、副作用分析。
|
|
44
|
+
|
|
45
|
+
**📄 02-改动点清单.md**
|
|
46
|
+
* **格式**:表格形式。
|
|
47
|
+
* **字段**:文件路径 | 改动类型 (Add/Modify/Delete) | 模块类型 (Component/Store/API) | 依赖关系 | 预估影响。
|
|
48
|
+
* **重要约束**:本清单仅记录**代码文件**的改动(如 `.tsx`, `.ts`, `.js`, `.css` 等),**不包含** `openspec/spec/` 目录下规格文档的改动。规格文档的合并将通过 `openspec` 流程单独处理。
|
|
49
|
+
|
|
50
|
+
**📄 03-待确认问题清单.md**
|
|
51
|
+
* **格式**:表格形式(仅在“提问模式”下生成或更新)。
|
|
52
|
+
* **字段**:问题类型 (断点/起点缺失/推测缺口) | 问题描述 | 代码位置 (如有) | 建议/待确认项。
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
### 🚦 核心指令 (Execution Logic)
|
|
57
|
+
|
|
58
|
+
请根据当前状态选择执行路径:
|
|
59
|
+
|
|
60
|
+
#### 路径 A:提问模式 (当前存在矛盾点)
|
|
61
|
+
> **触发条件**:当你发现 PRD 与现有代码库不一致,或代码逻辑存在盲区时。
|
|
62
|
+
|
|
63
|
+
**任务**:
|
|
64
|
+
1. **不要强行编写解决方案。**
|
|
65
|
+
2. 列出所有 **断点**、**起点缺失** 和 **推测缺口**。
|
|
66
|
+
3. 输出 `03-待确认问题清单.md`,并暂停生成最终报告,等待人工输入。
|
|
67
|
+
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
#### 路径 B:交付模式 (矛盾点池已闭合)
|
|
71
|
+
> **触发条件**:人工纠偏后,所有疑问已解决,且无新问题产生。
|
|
72
|
+
|
|
73
|
+
**任务**:
|
|
74
|
+
1. 生成最终版 `01-需求分析报告.md` 和 `02-改动点清单.md`。
|
|
75
|
+
2. 在报告中明确标注“**矛盾点池状态:已闭合**”。
|
|
76
|
+
3. 确认 `02-改动点清单.md` 中仅包含代码文件改动,不涉及 `openspec/spec/` 内容。
|
|
77
|
+
|
|
78
|
+
---
|
|
79
|
+
|
|
80
|
+
## 额外信息(由执行工具在运行时注入)
|
|
81
|
+
- PRD 文档内容(markdown):{{PRD_MARKDOWN}}
|
|
82
|
+
- 接口文档内容(可选 markdown):{{INTERFACE_DOC_MARKDOWN}}
|
|
83
|
+
- openspec/ 现有知识库摘要(可选):{{KNOWLEDGE_BASE_SUMMARY}}
|
|
84
|
+
|