@gavin-hjw/sddflow 0.3.2

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.
@@ -0,0 +1,29 @@
1
+ export interface DepStatus {
2
+ openspec: {
3
+ installed: boolean;
4
+ version?: string;
5
+ autoInstalled?: boolean;
6
+ };
7
+ superpowers: {
8
+ installed: boolean;
9
+ hint?: string;
10
+ path?: string;
11
+ checkedPaths: string[];
12
+ };
13
+ }
14
+ export interface CheckDependencyOptions {
15
+ cwd?: string;
16
+ tools?: string[];
17
+ }
18
+ export declare function checkDependencies(options?: CheckDependencyOptions): DepStatus;
19
+ export declare function tryAutoInstall(pkg: string): boolean;
20
+ export declare function checkOpenSpecInitialized(cwd: string): boolean;
21
+ export interface InitState {
22
+ openspec: boolean;
23
+ superpowers: boolean;
24
+ openspecProjectInitialized: boolean;
25
+ createdAt: string;
26
+ tools: string[];
27
+ }
28
+ export declare function readState(cwd: string): InitState | null;
29
+ export declare function writeState(cwd: string, state: InitState): void;
@@ -0,0 +1,87 @@
1
+ import { execSync } from 'child_process';
2
+ import { cmdExists, fileExists, dirExists, exec } from '../utils/shell.js';
3
+ import { DEPS, TOOL_PATHS } from './constants.js';
4
+ import { logger } from '../utils/logger.js';
5
+ import path from 'path';
6
+ import os from 'os';
7
+ import fs from 'fs';
8
+ export function checkDependencies(options = {}) {
9
+ const home = os.homedir();
10
+ const cwd = options.cwd ?? process.cwd();
11
+ const tools = options.tools?.length ? options.tools : Object.keys(TOOL_PATHS);
12
+ // Check OpenSpec
13
+ const openspecInstalled = cmdExists(DEPS.openspec.cliCmd);
14
+ let openspecVersion;
15
+ if (openspecInstalled) {
16
+ openspecVersion = exec('openspec --version') || undefined;
17
+ }
18
+ // Check Superpowers in the selected tools' local and global skill dirs.
19
+ const superpowersSkillPaths = getSuperpowersSkillPaths(cwd, home, tools);
20
+ const superpowersSkillPath = superpowersSkillPaths.find((candidate) => fs.existsSync(candidate));
21
+ const superpowersInstalled = Boolean(superpowersSkillPath);
22
+ return {
23
+ openspec: {
24
+ installed: openspecInstalled,
25
+ version: openspecVersion,
26
+ },
27
+ superpowers: {
28
+ installed: superpowersInstalled,
29
+ hint: superpowersInstalled ? undefined : DEPS.superpowers.installHint,
30
+ path: superpowersSkillPath,
31
+ checkedPaths: superpowersSkillPaths,
32
+ },
33
+ };
34
+ }
35
+ function getSuperpowersSkillPaths(cwd, home, tools) {
36
+ const candidates = new Set();
37
+ for (const tool of tools) {
38
+ const toolPaths = TOOL_PATHS[tool];
39
+ if (!toolPaths)
40
+ continue;
41
+ candidates.add(path.join(cwd, toolPaths.skillsDir, DEPS.superpowers.checkPath));
42
+ candidates.add(path.join(home, toolPaths.skillsDir, DEPS.superpowers.checkPath));
43
+ }
44
+ return [...candidates];
45
+ }
46
+ export function tryAutoInstall(pkg) {
47
+ logger.step(`Installing ${pkg} ...`);
48
+ try {
49
+ execSync(`npm install -g ${pkg}@latest`, { stdio: 'inherit' });
50
+ logger.success(`${pkg} installed`);
51
+ return true;
52
+ }
53
+ catch {
54
+ logger.error(`Failed to install ${pkg} — please run manually: npm install -g ${pkg}@latest`);
55
+ return false;
56
+ }
57
+ }
58
+ export function checkOpenSpecInitialized(cwd) {
59
+ return dirExists(path.join(cwd, 'openspec'));
60
+ }
61
+ export function readState(cwd) {
62
+ for (const stateFile of getStateFileCandidates(cwd)) {
63
+ if (!fileExists(stateFile))
64
+ continue;
65
+ try {
66
+ return JSON.parse(fs.readFileSync(stateFile, 'utf-8'));
67
+ }
68
+ catch {
69
+ return null;
70
+ }
71
+ }
72
+ return null;
73
+ }
74
+ export function writeState(cwd, state) {
75
+ const stateDir = path.join(cwd, '.sddflow');
76
+ if (!dirExists(stateDir)) {
77
+ fs.mkdirSync(stateDir, { recursive: true });
78
+ }
79
+ const stateFile = path.join(stateDir, 'state.json');
80
+ fs.writeFileSync(stateFile, JSON.stringify(state, null, 2) + '\n');
81
+ }
82
+ function getStateFileCandidates(cwd) {
83
+ return [
84
+ path.join(cwd, '.sddflow', 'state.json'),
85
+ path.join(cwd, '.claude', 'sddflow-state.json'),
86
+ ];
87
+ }
@@ -0,0 +1,8 @@
1
+ import type { DepStatus } from './dependency-check.js';
2
+ export interface GenerateOptions {
3
+ cwd: string;
4
+ tools: string[];
5
+ depStatus: DepStatus;
6
+ global?: boolean;
7
+ }
8
+ export declare function generateSkills(options: GenerateOptions): void;
@@ -0,0 +1,242 @@
1
+ import fs from 'fs';
2
+ import os from 'os';
3
+ import path from 'path';
4
+ import { fileURLToPath } from 'url';
5
+ import { fileExists } from '../utils/shell.js';
6
+ import { logger } from '../utils/logger.js';
7
+ import { SKILL_NAME, TOOL_PATHS, DEPS } from './constants.js';
8
+ const __filename = fileURLToPath(import.meta.url);
9
+ const __dirname = path.dirname(__filename);
10
+ // Resolve templates dir: from dist/core/ → ../../templates/
11
+ const TEMPLATES_DIR = path.resolve(__dirname, '..', '..', 'templates');
12
+ const PHASES = [
13
+ { name: 'proposal', description: 'Quick requirement capture' },
14
+ { name: 'brainstorming', description: 'Deep design exploration' },
15
+ { name: 'spec', description: 'Generate OpenSpec specs and translate to plan-ready.md' },
16
+ { name: 'amend', description: 'Revise requirements/specs before close' },
17
+ { name: 'build', description: 'Execute implementation' },
18
+ { name: 'close', description: 'Verify consistency and archive' },
19
+ ];
20
+ const PHASE_ALIAS_TOOLS = new Set(['claude', 'codex', 'cursor']);
21
+ export function generateSkills(options) {
22
+ const { cwd, tools, depStatus, global = false } = options;
23
+ const baseDir = global ? os.homedir() : cwd;
24
+ for (const tool of tools) {
25
+ const toolPaths = TOOL_PATHS[tool];
26
+ if (!toolPaths) {
27
+ logger.warn(`Unknown tool: ${tool}, skipping`);
28
+ continue;
29
+ }
30
+ const skillsDir = path.join(baseDir, toolPaths.skillsDir, SKILL_NAME);
31
+ const displayPath = global
32
+ ? path.join('~', toolPaths.skillsDir, SKILL_NAME)
33
+ : path.relative(cwd, skillsDir);
34
+ logger.step(`Generating ${tool} skills to ${displayPath}/`);
35
+ if (!fs.existsSync(skillsDir)) {
36
+ fs.mkdirSync(skillsDir, { recursive: true });
37
+ }
38
+ // Generate main SKILL.md
39
+ generateSkillFile(skillsDir, 'SKILL.md', depStatus);
40
+ // Generate phase files
41
+ for (const phase of PHASES) {
42
+ generateSkillFile(skillsDir, `${phase.name}.md`, depStatus);
43
+ }
44
+ if (PHASE_ALIAS_TOOLS.has(tool)) {
45
+ generatePhaseAliasSkills({
46
+ baseDir,
47
+ skillsDir: toolPaths.skillsDir,
48
+ cwd,
49
+ global,
50
+ });
51
+ }
52
+ logger.success(`${tool} skills generated`);
53
+ }
54
+ }
55
+ function generateSkillFile(skillsDir, filename, depStatus) {
56
+ const templatePath = path.join(TEMPLATES_DIR, filename);
57
+ let content;
58
+ if (fileExists(templatePath)) {
59
+ content = fs.readFileSync(templatePath, 'utf-8');
60
+ }
61
+ else {
62
+ // Fallback: use inline template
63
+ content = getInlineTemplate(filename, depStatus);
64
+ }
65
+ // Inject runtime dependency checks into build.md
66
+ if (filename === 'build.md') {
67
+ content = injectRuntimeDepCheck(content, depStatus);
68
+ }
69
+ // Inject runtime dependency checks into spec.md for OpenSpec
70
+ if (filename === 'spec.md') {
71
+ content = injectSpecRuntimeCheck(content, depStatus);
72
+ }
73
+ const targetPath = path.join(skillsDir, filename);
74
+ fs.writeFileSync(targetPath, content);
75
+ logger.step(` ${filename}`);
76
+ }
77
+ function generatePhaseAliasSkills(options) {
78
+ const { baseDir, skillsDir, cwd, global } = options;
79
+ for (const phase of PHASES) {
80
+ const aliasName = `${SKILL_NAME}-${phase.name}`;
81
+ const aliasDir = path.join(baseDir, skillsDir, aliasName);
82
+ const displayPath = global
83
+ ? path.join('~', skillsDir, aliasName, 'SKILL.md')
84
+ : path.relative(cwd, path.join(aliasDir, 'SKILL.md'));
85
+ if (!fs.existsSync(aliasDir)) {
86
+ fs.mkdirSync(aliasDir, { recursive: true });
87
+ }
88
+ fs.writeFileSync(path.join(aliasDir, 'SKILL.md'), getPhaseAliasTemplate(phase.name, phase.description));
89
+ logger.step(` ${displayPath}`);
90
+ }
91
+ }
92
+ function getPhaseAliasTemplate(phase, description) {
93
+ return `---
94
+ name: ${SKILL_NAME}-${phase}
95
+ description: "SDDFlow ${phase}: ${description}. Visibility alias for ${SKILL_NAME} ${phase}."
96
+ argument-hint: "[optional context]"
97
+ ---
98
+
99
+ # ${SKILL_NAME}-${phase}
100
+
101
+ 这是 \`${SKILL_NAME} ${phase}\` 的补全可见别名。
102
+
103
+ 执行时必须按以下方式处理:
104
+
105
+ 1. 将本次调用视为用户调用了 \`/${SKILL_NAME} ${phase} $ARGUMENTS\`
106
+ 2. 读取同级 skills 目录中的 \`${SKILL_NAME}/SKILL.md\`
107
+ 3. 读取 \`${SKILL_NAME}/${phase}.md\`
108
+ 4. 严格遵守主 sddflow 工作流、阶段写入边界和当前阶段文件
109
+ 5. 如果 \`$ARGUMENTS\` 中有额外需求或上下文,将它作为 ${phase} 阶段输入
110
+ `;
111
+ }
112
+ function injectRuntimeDepCheck(content, depStatus) {
113
+ const checkSection = `
114
+ ### 0. 依赖检测
115
+
116
+ 执行前检查以下依赖是否可用:
117
+
118
+ | 依赖 | 检测方式 | 不可用时 |
119
+ |------|----------|----------|
120
+ | Superpowers writing-plans | 当前工具的本地或全局 skills 目录下是否存在 \`writing-plans/SKILL.md\` | 降级为手动拆解 plan-ready.md 中的步骤,逐条执行 |
121
+ | OpenSpec CLI | \`openspec\` 命令是否可执行 | 不影响 build 阶段,但 close 阶段归档需手动 mv |
122
+
123
+ 如果 Superpowers 不可用,提示用户:
124
+ > "Superpowers 未安装,build 将使用手动执行模式。安装后体验更佳:${DEPS.superpowers.installHint}"
125
+
126
+ 如果 Superpowers 可用,调用其 \`writing-plans\` skill 生成详细实现计划。
127
+ `;
128
+ // Insert after the first heading
129
+ const lines = content.split('\n');
130
+ const firstH2Idx = lines.findIndex((l) => l.startsWith('## '));
131
+ if (firstH2Idx >= 0) {
132
+ lines.splice(firstH2Idx + 1, 0, checkSection);
133
+ }
134
+ else {
135
+ lines.unshift(checkSection);
136
+ }
137
+ return lines.join('\n');
138
+ }
139
+ function injectSpecRuntimeCheck(content, depStatus) {
140
+ const checkNote = `
141
+ > **OpenSpec 检测**:根据 proposal.md 生成 design.md + specs/ + tasks.md;如果 \`openspec\` CLI 可用,生成后运行 \`openspec validate <变更名> --strict\` 校验。
142
+ `;
143
+ const lines = content.split('\n');
144
+ const validateIdx = lines.findIndex((l) => l.includes('openspec validate'));
145
+ if (validateIdx >= 0) {
146
+ lines.splice(validateIdx, 0, checkNote);
147
+ }
148
+ return lines.join('\n');
149
+ }
150
+ function getInlineTemplate(filename, depStatus) {
151
+ const templates = {
152
+ 'SKILL.md': `---
153
+ name: sddflow
154
+ description: “OpenSpec + Superpowers 工作流协调器。使用 /sddflow proposal 轻量提问、/sddflow brainstorming 深度设计、/sddflow spec 生成规格、/sddflow amend 在归档前修订需求、/sddflow build 执行实现、/sddflow close 验证归档。串联需求规格与工程执行,消除格式鸿沟。”
155
+ argument-hint: “proposal | brainstorming | spec | amend | build | close”
156
+ ---
157
+
158
+ # sddflow - 工作流协调器
159
+
160
+ 根据用户调用的子命令和项目当前状态,路由到对应阶段。
161
+
162
+ ## 续接与中断恢复
163
+
164
+ 如果本轮没有显式 \`/sddflow ...\` 子命令,但上一轮已经进入 sddflow 任一阶段,并且用户是在补充范围、回答确认问题、说”继续”、修正需求、或说明新增/移除边界:
165
+
166
+ 1. 默认继续上一 sddflow 阶段,不把该回复当作普通编码请求
167
+ 2. 如果上一阶段是 proposal、brainstorming、spec 或 amend,只能继续产出/更新 OpenSpec 文档和计划文档,不得修改任何代码或实现文件
168
+ 3. 如果上一阶段是 build,但用户补充的是需求、验收条件或规格边界变更,切到 \`/sddflow amend\`,不要直接改代码
169
+ 4. 只有用户显式调用 \`/sddflow build\`,或状态检测明确进入 build 阶段后,才允许修改代码或实现文件
170
+ 5. 中断后恢复时,先重新读取当前阶段文件和 \`openspec/changes/\` 状态,再继续执行
171
+
172
+ 典型场景:
173
+ - proposal 阶段整理需求后,用户补充“运营端也要做回显”。这仍是需求范围修正,必须继续 proposal 文档收敛,不能直接进入代码实现。
174
+ - brainstorming 阶段询问“是否只覆盖企业端?”后,用户回复“运营端也要做回显”。这仍是设计范围修正,必须继续 brainstorming/proposal 文档收敛,不能直接进入代码实现。
175
+
176
+ ## 阶段写入边界
177
+
178
+ | 阶段 | 允许写入 | 禁止写入 |
179
+ |------|----------|----------|
180
+ | proposal | \`openspec/changes/**/proposal.md\` | 任何代码或实现文件 |
181
+ | brainstorming | \`openspec/changes/**/proposal.md\` | 任何代码或实现文件 |
182
+ | spec | \`openspec/changes/**\`、\`plan-ready.md\` | 任何代码或实现文件 |
183
+ | amend | \`openspec/changes/**\`、\`plan-ready.md\`、\`docs/superpowers/plans/*.md\` | 代码、测试、其他实现文件 |
184
+ | build | 代码、测试、实现计划状态 | 规格文档(除非另开变更) |
185
+ | close | 归档、验证记录、\`close-issues.md\` | 代码、测试、其他实现文件 |
186
+
187
+ 如果用户在 proposal/brainstorming/spec/amend 阶段提出“就按这个做”“范围改成 X”“继续”等话术,不代表进入 build;必须先完成该阶段文档产物并提示下一步。
188
+
189
+ ## 子命令
190
+
191
+ | 命令 | 阶段 | 说明 |
192
+ |------|------|------|
193
+ | \`/sddflow proposal\` | proposal | 轻量提问,快速收敛需求 |
194
+ | \`/sddflow brainstorming\` | brainstorming | 深度设计,多轮探索 |
195
+ | \`/sddflow spec\` | spec | 调用 OpenSpec 生成规格 + 翻译 |
196
+ | \`/sddflow amend\` | amend | build/close 前受控修改需求、规格和计划 |
197
+ | \`/sddflow build\` | build | 调用 Superpowers 执行实现 |
198
+ | \`/sddflow close\` | close | 验证一致性 + 归档 |
199
+
200
+ ## 状态检测
201
+
202
+ 当用户调用 \`/sddflow\` 不带子命令,或调用某个子命令需要确认前置条件时,执行以下状态检测:
203
+
204
+ | 检查项 | 怎么查 | 结果 |
205
+ |--------|--------|------|
206
+ | 有活跃变更? | \`openspec/changes/\` 下是否有非 archive 子目录 | 有→继续 |
207
+ | 有 plan-ready.md? | 变更目录下是否有 \`plan-ready.md\` | 有→看实现状态 |
208
+ | 实现已开始? | \`docs/superpowers/plans/\` 下是否有计划文件 | 有→看是否完成 |
209
+ | 实现已完成? | 计划文件全部 checkbox 已勾选 | 是→close 阶段 |
210
+
211
+ 判定结果:
212
+ - 无活跃变更 → proposal 阶段
213
+ - 有规格但无 plan-ready.md → spec 阶段(补生成翻译)
214
+ - 有 plan-ready.md 但实现未开始 → build 阶段
215
+ - 实现进行中 → 继续 build 阶段(断点恢复)
216
+ - 实现已完成 → close 阶段
217
+
218
+ ## 路由
219
+
220
+ 根据子命令或状态检测结果,读取对应阶段文件并执行:
221
+
222
+ 1. 如果这是上一 sddflow 阶段的续接回复,先按”续接与中断恢复”保持阶段
223
+ 2. 如果用户在 build 中明确提出需求变更、补充 spec、修改验收条件或重新生成规格,路由到 amend
224
+ 3. 如果用户指定了子命令(如 \`/sddflow build\`),优先按指定阶段执行,但检查前置条件
225
+ 4. 如果用户只输入 \`/sddflow\`,执行状态检测,自动路由到对应阶段
226
+ 5. 读取当前 sddflow skill 目录下的阶段文件:\`<阶段>.md\`(与本 \`SKILL.md\` 同目录;不要依赖 Claude 专属环境变量)
227
+ 6. 按阶段文件中的流程执行,并遵守阶段写入边界
228
+
229
+ ### 前置条件检查
230
+
231
+ | 阶段 | 前置条件 | 不满足时提示 |
232
+ |------|----------|-------------|
233
+ | proposal | 无 | — |
234
+ | brainstorming | 无 | — |
235
+ | spec | 需要有活跃变更目录或有用户需求 | "请先用 /sddflow proposal 或 /sddflow brainstorming 描述需求" |
236
+ | amend | 需要有活跃变更目录,通常需要 plan-ready.md | "还没有可修订的活跃变更,请先完成 /sddflow spec" |
237
+ | build | 需要存在 plan-ready.md | "请先完成 /sddflow spec 生成规格和翻译" |
238
+ | close | 需要实现已完成 | "实现尚未完成,请先用 /sddflow build 执行" |
239
+ `,
240
+ };
241
+ return templates[filename] ?? `# ${filename}\n\nTODO: implement\n`;
242
+ }
@@ -0,0 +1,8 @@
1
+ export declare const logger: {
2
+ info: (msg: string) => void;
3
+ success: (msg: string) => void;
4
+ warn: (msg: string) => void;
5
+ error: (msg: string) => void;
6
+ step: (msg: string) => void;
7
+ blank: () => void;
8
+ };
@@ -0,0 +1,9 @@
1
+ import chalk from 'chalk';
2
+ export const logger = {
3
+ info: (msg) => console.log(chalk.cyan('ℹ'), msg),
4
+ success: (msg) => console.log(chalk.green('✔'), msg),
5
+ warn: (msg) => console.log(chalk.yellow('⚠'), msg),
6
+ error: (msg) => console.log(chalk.red('✖'), msg),
7
+ step: (msg) => console.log(chalk.gray('→'), msg),
8
+ blank: () => console.log(),
9
+ };
@@ -0,0 +1,6 @@
1
+ export declare function exec(cmd: string, options?: {
2
+ stdio?: 'inherit' | 'pipe';
3
+ }): string;
4
+ export declare function cmdExists(cmd: string): boolean;
5
+ export declare function fileExists(path: string): boolean;
6
+ export declare function dirExists(path: string): boolean;
@@ -0,0 +1,39 @@
1
+ import { execSync } from 'child_process';
2
+ export function exec(cmd, options) {
3
+ try {
4
+ return execSync(cmd, {
5
+ encoding: 'utf-8',
6
+ stdio: options?.stdio ?? 'pipe',
7
+ }).trim();
8
+ }
9
+ catch {
10
+ return '';
11
+ }
12
+ }
13
+ export function cmdExists(cmd) {
14
+ try {
15
+ execSync(`which ${cmd}`, { encoding: 'utf-8', stdio: 'pipe' });
16
+ return true;
17
+ }
18
+ catch {
19
+ return false;
20
+ }
21
+ }
22
+ export function fileExists(path) {
23
+ try {
24
+ execSync(`test -f ${path}`, { stdio: 'pipe' });
25
+ return true;
26
+ }
27
+ catch {
28
+ return false;
29
+ }
30
+ }
31
+ export function dirExists(path) {
32
+ try {
33
+ execSync(`test -d ${path}`, { stdio: 'pipe' });
34
+ return true;
35
+ }
36
+ catch {
37
+ return false;
38
+ }
39
+ }
package/package.json ADDED
@@ -0,0 +1,49 @@
1
+ {
2
+ "name": "@gavin-hjw/sddflow",
3
+ "version": "0.3.2",
4
+ "description": "OpenSpec + Superpowers workflow orchestrator for Claude Code",
5
+ "bin": {
6
+ "sddflow": "bin/sddflow.js"
7
+ },
8
+ "type": "module",
9
+ "scripts": {
10
+ "build": "tsc",
11
+ "dev": "tsc --watch",
12
+ "prepublishOnly": "npm run build",
13
+ "postinstall": "node ./scripts/postinstall.js"
14
+ },
15
+ "keywords": [
16
+ "claude-code",
17
+ "openspec",
18
+ "superpowers",
19
+ "workflow",
20
+ "skill"
21
+ ],
22
+ "author": "gavin-hjw",
23
+ "license": "MIT",
24
+ "repository": {
25
+ "type": "git",
26
+ "url": "git+https://github.com/gavin-hjw/sddflow.git"
27
+ },
28
+ "engines": {
29
+ "node": ">=18.0.0"
30
+ },
31
+ "files": [
32
+ "bin",
33
+ "dist",
34
+ "templates",
35
+ "scripts"
36
+ ],
37
+ "dependencies": {
38
+ "chalk": "^5.3.0",
39
+ "commander": "^12.0.0",
40
+ "inquirer": "^13.4.3",
41
+ "ora": "^8.0.0",
42
+ "yaml": "^2.4.0"
43
+ },
44
+ "devDependencies": {
45
+ "@types/inquirer": "^9.0.9",
46
+ "@types/node": "^20.0.0",
47
+ "typescript": "^5.5.0"
48
+ }
49
+ }
@@ -0,0 +1,12 @@
1
+ console.log(`
2
+ sddflow — OpenSpec + Superpowers workflow orchestrator
3
+
4
+ Initialize a project:
5
+ sddflow init --tools claude
6
+
7
+ Check status:
8
+ sddflow status
9
+
10
+ Update skills:
11
+ sddflow update
12
+ `);
@@ -0,0 +1,87 @@
1
+ ---
2
+ name: sddflow
3
+ description: "OpenSpec + Superpowers workflow orchestrator. Use /sddflow proposal for quick capture, /sddflow brainstorming for deep design, /sddflow spec to generate specs + translate, /sddflow amend to revise requirements before close, /sddflow build to execute, /sddflow close to verify and archive. Bridges requirements specs and engineering execution."
4
+ argument-hint: "proposal | brainstorming | spec | amend | build | close"
5
+ ---
6
+
7
+ # sddflow - 工作流协调器
8
+
9
+ 根据用户调用的子命令和项目当前状态,路由到对应阶段。
10
+
11
+ ## 续接与中断恢复
12
+
13
+ 如果本轮没有显式 `/sddflow ...` 子命令,但上一轮已经进入 sddflow 任一阶段,并且用户是在补充范围、回答确认问题、说“继续”、修正需求、或说明新增/移除边界:
14
+
15
+ 1. 默认继续上一 sddflow 阶段,不把该回复当作普通编码请求
16
+ 2. 如果上一阶段是 proposal、brainstorming、spec 或 amend,只能继续产出/更新 OpenSpec 文档和计划文档,不得修改任何代码或实现文件
17
+ 3. 如果上一阶段是 build,但用户补充的是需求、验收条件或规格边界变更,切到 `/sddflow amend`,不要直接改代码
18
+ 4. 只有用户显式调用 `/sddflow build`,或状态检测明确进入 build 阶段后,才允许修改代码或实现文件
19
+ 5. 中断后恢复时,先重新读取当前阶段文件和 `openspec/changes/` 状态,再继续执行
20
+
21
+ 典型场景:
22
+ - proposal 阶段整理需求后,用户补充“运营端也要做回显”。这仍是需求范围修正,必须继续 proposal 文档收敛,不能直接进入代码实现。
23
+ - brainstorming 阶段询问“是否只覆盖企业端?”后,用户回复“运营端也要做回显”。这仍是设计范围修正,必须继续 brainstorming/proposal 文档收敛,不能直接进入代码实现。
24
+
25
+ ## 阶段写入边界
26
+
27
+ | 阶段 | 允许写入 | 禁止写入 |
28
+ |------|----------|----------|
29
+ | proposal | `openspec/changes/**/proposal.md` | 任何代码或实现文件 |
30
+ | brainstorming | `openspec/changes/**/proposal.md` | 任何代码或实现文件 |
31
+ | spec | `openspec/changes/**`、`plan-ready.md` | 任何代码或实现文件 |
32
+ | amend | `openspec/changes/**`、`plan-ready.md`、`docs/superpowers/plans/*.md` | 代码、测试、其他实现文件 |
33
+ | build | 代码、测试、实现计划状态 | 规格文档(除非另开变更) |
34
+ | close | 归档、验证记录、`close-issues.md` | 代码、测试、其他实现文件 |
35
+
36
+ 如果用户在 proposal/brainstorming/spec/amend 阶段提出“就按这个做”“范围改成 X”“继续”等话术,不代表进入 build;必须先完成该阶段文档产物并提示下一步。
37
+
38
+ ## 子命令
39
+
40
+ | 命令 | 阶段 | 说明 |
41
+ |------|------|------|
42
+ | `/sddflow proposal` | proposal | 轻量提问,快速收敛需求 |
43
+ | `/sddflow brainstorming` | brainstorming | 深度设计,多轮探索 |
44
+ | `/sddflow spec` | spec | 调用 OpenSpec 生成规格 + 翻译 |
45
+ | `/sddflow amend` | amend | build/close 前受控修改需求、规格和计划 |
46
+ | `/sddflow build` | build | 调用 Superpowers 执行实现 |
47
+ | `/sddflow close` | close | 验证一致性 + 归档 |
48
+
49
+ ## 状态检测
50
+
51
+ 当用户调用 `/sddflow` 不带子命令,或调用某个子命令需要确认前置条件时,执行以下状态检测:
52
+
53
+ | 检查项 | 怎么查 | 结果 |
54
+ |--------|--------|------|
55
+ | 有活跃变更? | `openspec/changes/` 下是否有非 archive 子目录 | 有→继续 |
56
+ | 有 plan-ready.md? | 变更目录下是否有 `plan-ready.md` | 有→看实现状态 |
57
+ | 实现已开始? | `docs/superpowers/plans/` 下是否有计划文件 | 有→看是否完成 |
58
+ | 实现已完成? | 计划文件全部 checkbox 已勾选 | 是→close 阶段 |
59
+
60
+ 判定结果:
61
+ - 无活跃变更 → proposal 阶段
62
+ - 有规格但无 plan-ready.md → spec 阶段(补生成翻译)
63
+ - 有 plan-ready.md 但实现未开始 → build 阶段
64
+ - 实现进行中 → 继续 build 阶段(断点恢复)
65
+ - 实现已完成 → close 阶段
66
+
67
+ ## 路由
68
+
69
+ 根据子命令或状态检测结果,读取对应阶段文件并执行:
70
+
71
+ 1. 如果这是上一 sddflow 阶段的续接回复,先按“续接与中断恢复”保持阶段
72
+ 2. 如果用户在 build 中明确提出需求变更、补充 spec、修改验收条件或重新生成规格,路由到 amend
73
+ 3. 如果用户指定了子命令(如 `/sddflow build`),优先按指定阶段执行,但检查前置条件
74
+ 4. 如果用户只输入 `/sddflow`,执行状态检测,自动路由到对应阶段
75
+ 5. 读取当前 sddflow skill 目录下的阶段文件:`<阶段>.md`(与本 `SKILL.md` 同目录;不要依赖 Claude 专属环境变量)
76
+ 6. 按阶段文件中的流程执行,并遵守阶段写入边界
77
+
78
+ ### 前置条件检查
79
+
80
+ | 阶段 | 前置条件 | 不满足时提示 |
81
+ |------|----------|-------------|
82
+ | proposal | 无 | — |
83
+ | brainstorming | 无 | — |
84
+ | spec | 需要有活跃变更目录或有用户需求 | "请先用 /sddflow proposal 或 /sddflow brainstorming 描述需求" |
85
+ | amend | 需要有活跃变更目录,通常需要 plan-ready.md | "还没有可修订的活跃变更,请先完成 /sddflow spec" |
86
+ | build | 需要存在 plan-ready.md | "请先完成 /sddflow spec 生成规格和翻译" |
87
+ | close | 需要实现已完成 | "实现尚未完成,请先用 /sddflow build 执行" |