@gavin-hjw/sddflow 0.3.2 → 0.3.4

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.
@@ -1,6 +1,6 @@
1
1
  import { execSync } from 'child_process';
2
- import { cmdExists, fileExists, dirExists, exec } from '../utils/shell.js';
3
- import { DEPS, TOOL_PATHS } from './constants.js';
2
+ import { cmdExists, fileExists, dirExists, exec, execOrThrow } from '../utils/shell.js';
3
+ import { DEPS, TOOL_PATHS, SUPERPOWERS_INSTALL_HINTS, SUPERPOWERS_PLUGIN_CACHE_DIRS, SUPERPOWERS_REQUIRED_SKILLS, OPENSPEC_INIT_MARKERS, } from './constants.js';
4
4
  import { logger } from '../utils/logger.js';
5
5
  import path from 'path';
6
6
  import os from 'os';
@@ -9,39 +9,189 @@ export function checkDependencies(options = {}) {
9
9
  const home = os.homedir();
10
10
  const cwd = options.cwd ?? process.cwd();
11
11
  const tools = options.tools?.length ? options.tools : Object.keys(TOOL_PATHS);
12
- // Check OpenSpec
13
12
  const openspecInstalled = cmdExists(DEPS.openspec.cliCmd);
14
13
  let openspecVersion;
15
14
  if (openspecInstalled) {
16
15
  openspecVersion = exec('openspec --version') || undefined;
17
16
  }
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);
17
+ const pluginInstalled = isSuperpowersPluginInstalled(home, tools);
18
+ const skills = checkSuperpowersSkills(cwd, home, tools);
19
+ const allSkillsInstalled = skills.every((skill) => skill.installed);
22
20
  return {
23
21
  openspec: {
24
22
  installed: openspecInstalled,
25
23
  version: openspecVersion,
26
24
  },
27
25
  superpowers: {
28
- installed: superpowersInstalled,
29
- hint: superpowersInstalled ? undefined : DEPS.superpowers.installHint,
30
- path: superpowersSkillPath,
31
- checkedPaths: superpowersSkillPaths,
26
+ pluginInstalled,
27
+ skills,
28
+ allSkillsInstalled,
29
+ installed: allSkillsInstalled,
32
30
  },
33
31
  };
34
32
  }
35
- function getSuperpowersSkillPaths(cwd, home, tools) {
36
- const candidates = new Set();
33
+ function isSuperpowersPluginInstalled(home, tools) {
34
+ for (const tool of tools) {
35
+ const cacheDir = SUPERPOWERS_PLUGIN_CACHE_DIRS[tool];
36
+ if (!cacheDir)
37
+ continue;
38
+ const cacheRoot = path.join(home, cacheDir);
39
+ if (!dirExists(cacheRoot))
40
+ continue;
41
+ const versions = fs.readdirSync(cacheRoot, { withFileTypes: true }).filter((entry) => entry.isDirectory());
42
+ if (versions.length > 0)
43
+ return true;
44
+ }
45
+ return false;
46
+ }
47
+ export function checkSuperpowersSkills(cwd, home, tools) {
48
+ return SUPERPOWERS_REQUIRED_SKILLS.map((skillName) => {
49
+ const skillPath = findSuperpowersSkillPath(cwd, home, tools, skillName);
50
+ return {
51
+ name: skillName,
52
+ installed: Boolean(skillPath),
53
+ path: skillPath,
54
+ };
55
+ });
56
+ }
57
+ function findSuperpowersSkillPath(cwd, home, tools, skillName) {
37
58
  for (const tool of tools) {
38
59
  const toolPaths = TOOL_PATHS[tool];
39
60
  if (!toolPaths)
40
61
  continue;
41
- candidates.add(path.join(cwd, toolPaths.skillsDir, DEPS.superpowers.checkPath));
42
- candidates.add(path.join(home, toolPaths.skillsDir, DEPS.superpowers.checkPath));
62
+ const localPath = path.join(cwd, toolPaths.skillsDir, skillName, 'SKILL.md');
63
+ if (fileExists(localPath))
64
+ return localPath;
65
+ const globalPath = path.join(home, toolPaths.skillsDir, skillName, 'SKILL.md');
66
+ if (fileExists(globalPath))
67
+ return globalPath;
68
+ const pluginCacheDir = SUPERPOWERS_PLUGIN_CACHE_DIRS[tool];
69
+ if (pluginCacheDir) {
70
+ const cached = findSkillInPluginCache(path.join(home, pluginCacheDir), skillName);
71
+ if (cached)
72
+ return cached;
73
+ }
74
+ }
75
+ return undefined;
76
+ }
77
+ function findSkillInPluginCache(pluginCacheRoot, skillName) {
78
+ if (!dirExists(pluginCacheRoot))
79
+ return undefined;
80
+ for (const entry of fs.readdirSync(pluginCacheRoot, { withFileTypes: true })) {
81
+ if (!entry.isDirectory())
82
+ continue;
83
+ const skillPath = path.join(pluginCacheRoot, entry.name, 'skills', skillName, 'SKILL.md');
84
+ if (fileExists(skillPath))
85
+ return skillPath;
43
86
  }
44
- return [...candidates];
87
+ return undefined;
88
+ }
89
+ /** 阶段 1:检测 OpenSpec CLI + Superpowers 插件是否已安装 */
90
+ export function validateComponentInstallation(depStatus, tools) {
91
+ const failures = [];
92
+ const toolsLabel = tools.join(',');
93
+ if (!depStatus.openspec.installed) {
94
+ failures.push({
95
+ id: 'openspec-cli',
96
+ name: DEPS.openspec.name,
97
+ reason: '未检测到 OpenSpec CLI(openspec 命令不可用)',
98
+ installSteps: [
99
+ DEPS.openspec.installHint,
100
+ `安装完成后重新运行: sddflow init --tools ${toolsLabel}`,
101
+ ],
102
+ });
103
+ }
104
+ if (!depStatus.superpowers.pluginInstalled) {
105
+ const installSteps = new Set([DEPS.superpowers.installHint]);
106
+ for (const tool of tools) {
107
+ for (const hint of SUPERPOWERS_INSTALL_HINTS[tool] ?? []) {
108
+ installSteps.add(hint);
109
+ }
110
+ }
111
+ installSteps.add(`安装完成后重新运行: sddflow init --tools ${toolsLabel}`);
112
+ failures.push({
113
+ id: 'superpowers-plugin',
114
+ name: DEPS.superpowers.name,
115
+ reason: '未检测到 Superpowers 插件',
116
+ installSteps: [...installSteps],
117
+ });
118
+ }
119
+ return failures;
120
+ }
121
+ /** 阶段 2:检测 Superpowers 必需 skills 是否齐全 */
122
+ export function validateSuperpowersSkills(depStatus, tools) {
123
+ const missing = depStatus.superpowers.skills.filter((skill) => !skill.installed);
124
+ if (missing.length === 0)
125
+ return [];
126
+ const toolsLabel = tools.join(',');
127
+ const installSteps = new Set([DEPS.superpowers.installHint]);
128
+ for (const tool of tools) {
129
+ for (const hint of SUPERPOWERS_INSTALL_HINTS[tool] ?? []) {
130
+ installSteps.add(hint);
131
+ }
132
+ }
133
+ installSteps.add(`安装完成后重新运行: sddflow init --tools ${toolsLabel}`);
134
+ return [
135
+ {
136
+ id: 'superpowers-skills',
137
+ name: `${DEPS.superpowers.name} Skills`,
138
+ reason: `缺少 ${missing.length} 个必需 skill: ${missing.map((s) => s.name).join(', ')}`,
139
+ installSteps: [...installSteps],
140
+ },
141
+ ];
142
+ }
143
+ export function verifyOpenSpecInitIntegrity(cwd, tools) {
144
+ const missing = [];
145
+ for (const tool of tools) {
146
+ const markers = OPENSPEC_INIT_MARKERS[tool];
147
+ if (!markers)
148
+ continue;
149
+ const found = markers.some((marker) => fileExists(path.join(cwd, marker)));
150
+ if (!found) {
151
+ missing.push(`${tool}: openspec init marker not found (expected one of: ${markers.join(', ')})`);
152
+ }
153
+ }
154
+ return { ok: missing.length === 0, missing };
155
+ }
156
+ export function runOpenSpecInit(cwd, tools) {
157
+ const toolsFlag = tools.join(',');
158
+ try {
159
+ execOrThrow(`openspec init --tools ${toolsFlag}`, { cwd, stdio: 'inherit' });
160
+ return true;
161
+ }
162
+ catch {
163
+ logger.error(`openspec init --tools ${toolsFlag} 执行失败`);
164
+ return false;
165
+ }
166
+ }
167
+ export function printInitPrerequisiteFailures(failures, title = '初始化已中止:缺少必要依赖,请先安装后再运行 sddflow init') {
168
+ logger.blank();
169
+ logger.error(title);
170
+ logger.blank();
171
+ for (const failure of failures) {
172
+ logger.warn(`${failure.name}: ${failure.reason}`);
173
+ logger.info(' 安装步骤:');
174
+ for (const step of failure.installSteps) {
175
+ logger.info(` ${step}`);
176
+ }
177
+ logger.blank();
178
+ }
179
+ }
180
+ export function printIntegrityFailures(result, tools) {
181
+ printInitPrerequisiteFailures([
182
+ {
183
+ id: 'openspec-init',
184
+ name: 'OpenSpec 初始化完整性',
185
+ reason: `openspec init 后仍缺少 ${result.missing.length} 个文件`,
186
+ installSteps: [
187
+ '请检查 openspec init 输出是否有报错',
188
+ `手动重试: openspec init --tools ${tools.join(',')}`,
189
+ `缺失文件:`,
190
+ ...result.missing.map((file) => ` - ${file}`),
191
+ `修复后重新运行: sddflow init --tools ${tools.join(',')}`,
192
+ ],
193
+ },
194
+ ], '初始化已中止:OpenSpec 文件完整性校验未通过');
45
195
  }
46
196
  export function tryAutoInstall(pkg) {
47
197
  logger.step(`Installing ${pkg} ...`);
@@ -66,7 +216,7 @@ export function readState(cwd) {
66
216
  return JSON.parse(fs.readFileSync(stateFile, 'utf-8'));
67
217
  }
68
218
  catch {
69
- return null;
219
+ continue;
70
220
  }
71
221
  }
72
222
  return null;
@@ -10,9 +10,8 @@ const __dirname = path.dirname(__filename);
10
10
  // Resolve templates dir: from dist/core/ → ../../templates/
11
11
  const TEMPLATES_DIR = path.resolve(__dirname, '..', '..', 'templates');
12
12
  const PHASES = [
13
- { name: 'proposal', description: 'Quick requirement capture' },
14
13
  { name: 'brainstorming', description: 'Deep design exploration' },
15
- { name: 'spec', description: 'Generate OpenSpec specs and translate to plan-ready.md' },
14
+ { name: 'spec', description: 'Complete OpenSpec artifacts per AGENTS.md, then plan-ready and writing-plans' },
16
15
  { name: 'amend', description: 'Revise requirements/specs before close' },
17
16
  { name: 'build', description: 'Execute implementation' },
18
17
  { name: 'close', description: 'Verify consistency and archive' },
@@ -66,10 +65,6 @@ function generateSkillFile(skillsDir, filename, depStatus) {
66
65
  if (filename === 'build.md') {
67
66
  content = injectRuntimeDepCheck(content, depStatus);
68
67
  }
69
- // Inject runtime dependency checks into spec.md for OpenSpec
70
- if (filename === 'spec.md') {
71
- content = injectSpecRuntimeCheck(content, depStatus);
72
- }
73
68
  const targetPath = path.join(skillsDir, filename);
74
69
  fs.writeFileSync(targetPath, content);
75
70
  logger.step(` ${filename}`);
@@ -109,21 +104,23 @@ argument-hint: "[optional context]"
109
104
  5. 如果 \`$ARGUMENTS\` 中有额外需求或上下文,将它作为 ${phase} 阶段输入
110
105
  `;
111
106
  }
112
- function injectRuntimeDepCheck(content, depStatus) {
107
+ function injectRuntimeDepCheck(content, _depStatus) {
113
108
  const checkSection = `
114
109
  ### 0. 依赖检测
115
110
 
116
- 执行前检查以下依赖是否可用:
111
+ 执行前检查以下依赖是否可用(**不在 build 阶段生成或重写计划文件**):
117
112
 
118
113
  | 依赖 | 检测方式 | 不可用时 |
119
114
  |------|----------|----------|
120
- | Superpowers writing-plans | 当前工具的本地或全局 skills 目录下是否存在 \`writing-plans/SKILL.md\` | 降级为手动拆解 plan-ready.md 中的步骤,逐条执行 |
121
- | OpenSpec CLI | \`openspec\` 命令是否可执行 | 不影响 build 阶段,但 close 阶段归档需手动 mv |
115
+ | 详细实现计划 | \`docs/superpowers/plans/\` 下存在含变更名的 \`.md\` 文件 | **终止 build**,提示先完成 \`/sddflow spec\` |
116
+ | Superpowers subagent-driven-development | skills 目录下是否存在 \`subagent-driven-development/SKILL.md\` | 降级为按 plan 文件逐步手动执行 |
117
+ | Superpowers test-driven-development | skills 目录下是否存在 \`test-driven-development/SKILL.md\` | 提示安装;仍按 plan 执行,须自述遵守 TDD |
118
+ | OpenSpec CLI | \`openspec\` 命令是否可执行 | 不影响 build;close 归档可改用 \`OpenSpec: Archive\` 或 \`openspec archive\` |
122
119
 
123
- 如果 Superpowers 不可用,提示用户:
124
- > "Superpowers 未安装,build 将使用手动执行模式。安装后体验更佳:${DEPS.superpowers.installHint}"
120
+ 如果 Superpowers 子技能缺失,提示用户:
121
+ > "Superpowers 未完整安装,build 将使用手动执行模式。安装后体验更佳:${DEPS.superpowers.installHint}"
125
122
 
126
- 如果 Superpowers 可用,调用其 \`writing-plans\` skill 生成详细实现计划。
123
+ **禁止**在 build 阶段调用 \`writing-plans\`;计划必须在 spec 阶段已生成。
127
124
  `;
128
125
  // Insert after the first heading
129
126
  const lines = content.split('\n');
@@ -136,107 +133,10 @@ function injectRuntimeDepCheck(content, depStatus) {
136
133
  }
137
134
  return lines.join('\n');
138
135
  }
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);
136
+ function getInlineTemplate(filename, _depStatus) {
137
+ const templatePath = path.join(TEMPLATES_DIR, filename);
138
+ if (fileExists(templatePath)) {
139
+ return fs.readFileSync(templatePath, 'utf-8');
147
140
  }
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`;
141
+ return `# ${filename}\n\nTODO: implement\n`;
242
142
  }
@@ -1,6 +1,11 @@
1
1
  export declare function exec(cmd: string, options?: {
2
2
  stdio?: 'inherit' | 'pipe';
3
+ cwd?: string;
3
4
  }): string;
5
+ export declare function execOrThrow(cmd: string, options?: {
6
+ stdio?: 'inherit' | 'pipe';
7
+ cwd?: string;
8
+ }): void;
4
9
  export declare function cmdExists(cmd: string): boolean;
5
10
  export declare function fileExists(path: string): boolean;
6
11
  export declare function dirExists(path: string): boolean;
@@ -4,15 +4,26 @@ export function exec(cmd, options) {
4
4
  return execSync(cmd, {
5
5
  encoding: 'utf-8',
6
6
  stdio: options?.stdio ?? 'pipe',
7
+ cwd: options?.cwd,
7
8
  }).trim();
8
9
  }
9
10
  catch {
10
11
  return '';
11
12
  }
12
13
  }
14
+ export function execOrThrow(cmd, options) {
15
+ execSync(cmd, {
16
+ encoding: 'utf-8',
17
+ stdio: options?.stdio ?? 'inherit',
18
+ cwd: options?.cwd,
19
+ });
20
+ }
21
+ import fs from 'fs';
13
22
  export function cmdExists(cmd) {
23
+ // Windows: 用 where;Unix: 用 which
24
+ const checkCmd = process.platform === 'win32' ? `where ${cmd}` : `which ${cmd}`;
14
25
  try {
15
- execSync(`which ${cmd}`, { encoding: 'utf-8', stdio: 'pipe' });
26
+ execSync(checkCmd, { encoding: 'utf-8', stdio: 'pipe' });
16
27
  return true;
17
28
  }
18
29
  catch {
@@ -21,8 +32,7 @@ export function cmdExists(cmd) {
21
32
  }
22
33
  export function fileExists(path) {
23
34
  try {
24
- execSync(`test -f ${path}`, { stdio: 'pipe' });
25
- return true;
35
+ return fs.existsSync(path) && fs.statSync(path).isFile();
26
36
  }
27
37
  catch {
28
38
  return false;
@@ -30,8 +40,7 @@ export function fileExists(path) {
30
40
  }
31
41
  export function dirExists(path) {
32
42
  try {
33
- execSync(`test -d ${path}`, { stdio: 'pipe' });
34
- return true;
43
+ return fs.existsSync(path) && fs.statSync(path).isDirectory();
35
44
  }
36
45
  catch {
37
46
  return false;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@gavin-hjw/sddflow",
3
- "version": "0.3.2",
3
+ "version": "0.3.4",
4
4
  "description": "OpenSpec + Superpowers workflow orchestrator for Claude Code",
5
5
  "bin": {
6
6
  "sddflow": "bin/sddflow.js"
@@ -1,7 +1,7 @@
1
1
  ---
2
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"
3
+ description: "OpenSpec + Superpowers workflow orchestrator. Use /sddflow brainstorming for design exploration, /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: "brainstorming | spec | amend | build | close"
5
5
  ---
6
6
 
7
7
  # sddflow - 工作流协调器
@@ -13,35 +13,39 @@ argument-hint: "proposal | brainstorming | spec | amend | build | close"
13
13
  如果本轮没有显式 `/sddflow ...` 子命令,但上一轮已经进入 sddflow 任一阶段,并且用户是在补充范围、回答确认问题、说“继续”、修正需求、或说明新增/移除边界:
14
14
 
15
15
  1. 默认继续上一 sddflow 阶段,不把该回复当作普通编码请求
16
- 2. 如果上一阶段是 proposal、brainstorming、spec 或 amend,只能继续产出/更新 OpenSpec 文档和计划文档,不得修改任何代码或实现文件
16
+ 2. 如果上一阶段是 brainstorming、spec 或 amend,只能继续产出/更新 OpenSpec 文档和计划文档,不得修改任何代码或实现文件
17
17
  3. 如果上一阶段是 build,但用户补充的是需求、验收条件或规格边界变更,切到 `/sddflow amend`,不要直接改代码
18
18
  4. 只有用户显式调用 `/sddflow build`,或状态检测明确进入 build 阶段后,才允许修改代码或实现文件
19
19
  5. 中断后恢复时,先重新读取当前阶段文件和 `openspec/changes/` 状态,再继续执行
20
20
 
21
+ **路由优先级(高于文件状态推断):**
22
+
23
+ 1. **续接回复** — 无显式子命令时,保持上一 sddflow 阶段;不因目录中已有 `plan-ready.md` 而自动进入 build
24
+ 2. **显式子命令** — 用户指定 `/sddflow <phase>` 时,按该阶段执行并检查前置条件
25
+ 3. **裸 `/sddflow`** — 仅此时执行下方「状态检测」自动路由
26
+ 4. **build 中需求变更** — 路由到 amend
27
+
21
28
  典型场景:
22
- - proposal 阶段整理需求后,用户补充“运营端也要做回显”。这仍是需求范围修正,必须继续 proposal 文档收敛,不能直接进入代码实现。
23
- - brainstorming 阶段询问“是否只覆盖企业端?”后,用户回复“运营端也要做回显”。这仍是设计范围修正,必须继续 brainstorming/proposal 文档收敛,不能直接进入代码实现。
29
+ - brainstorming 阶段询问“是否只覆盖企业端?”后,用户回复“运营端也要做回显”。这仍是设计范围修正,必须继续 brainstorming 文档收敛,不能直接进入代码实现。
24
30
 
25
31
  ## 阶段写入边界
26
32
 
27
33
  | 阶段 | 允许写入 | 禁止写入 |
28
34
  |------|----------|----------|
29
- | proposal | `openspec/changes/**/proposal.md` | 任何代码或实现文件 |
30
35
  | brainstorming | `openspec/changes/**/proposal.md` | 任何代码或实现文件 |
31
- | spec | `openspec/changes/**`、`plan-ready.md` | 任何代码或实现文件 |
36
+ | spec | `openspec/changes/**`、`plan-ready.md`、`docs/superpowers/plans/*.md` | 任何代码或实现文件 |
32
37
  | amend | `openspec/changes/**`、`plan-ready.md`、`docs/superpowers/plans/*.md` | 代码、测试、其他实现文件 |
33
- | build | 代码、测试、实现计划状态 | 规格文档(除非另开变更) |
38
+ | build | 代码、测试、实现计划 checkbox 状态 | 规格文档(除非另开变更);勿运行 OpenSpec Apply(与 sddflow build 冲突) |
34
39
  | close | 归档、验证记录、`close-issues.md` | 代码、测试、其他实现文件 |
35
40
 
36
- 如果用户在 proposal/brainstorming/spec/amend 阶段提出“就按这个做”“范围改成 X”“继续”等话术,不代表进入 build;必须先完成该阶段文档产物并提示下一步。
41
+ 如果用户在 brainstorming/spec/amend 阶段提出“就按这个做”“范围改成 X”“继续”等话术,不代表进入 build;必须先完成该阶段文档产物并提示下一步。
37
42
 
38
43
  ## 子命令
39
44
 
40
45
  | 命令 | 阶段 | 说明 |
41
46
  |------|------|------|
42
- | `/sddflow proposal` | proposal | 轻量提问,快速收敛需求 |
43
- | `/sddflow brainstorming` | brainstorming | 深度设计,多轮探索 |
44
- | `/sddflow spec` | spec | 调用 OpenSpec 生成规格 + 翻译 |
47
+ | `/sddflow brainstorming` | brainstorming | 深度设计,多轮探索,产出 `proposal.md` |
48
+ | `/sddflow spec` | spec | OpenSpec: Proposal + AGENTS.md 补齐规格,再翻译与 writing-plans |
45
49
  | `/sddflow amend` | amend | build/close 前受控修改需求、规格和计划 |
46
50
  | `/sddflow build` | build | 调用 Superpowers 执行实现 |
47
51
  | `/sddflow close` | close | 验证一致性 + 归档 |
@@ -58,8 +62,9 @@ argument-hint: "proposal | brainstorming | spec | amend | build | close"
58
62
  | 实现已完成? | 计划文件全部 checkbox 已勾选 | 是→close 阶段 |
59
63
 
60
64
  判定结果:
61
- - 无活跃变更 → proposal 阶段
62
- - 有规格但无 plan-ready.md → spec 阶段(补生成翻译)
65
+ - 无活跃变更 → brainstorming 阶段
66
+ - 有变更但无 `proposal.md`brainstorming 阶段
67
+ - 有 `proposal.md` 但无 plan-ready.md → spec 阶段(补生成翻译)
63
68
  - 有 plan-ready.md 但实现未开始 → build 阶段
64
69
  - 实现进行中 → 继续 build 阶段(断点恢复)
65
70
  - 实现已完成 → close 阶段
@@ -68,10 +73,10 @@ argument-hint: "proposal | brainstorming | spec | amend | build | close"
68
73
 
69
74
  根据子命令或状态检测结果,读取对应阶段文件并执行:
70
75
 
71
- 1. 如果这是上一 sddflow 阶段的续接回复,先按“续接与中断恢复”保持阶段
76
+ 1. 如果这是上一 sddflow 阶段的续接回复,先按“续接与中断恢复”保持阶段(**不得**因已有 `plan-ready.md` 覆盖为 build)
72
77
  2. 如果用户在 build 中明确提出需求变更、补充 spec、修改验收条件或重新生成规格,路由到 amend
73
- 3. 如果用户指定了子命令(如 `/sddflow build`),优先按指定阶段执行,但检查前置条件
74
- 4. 如果用户只输入 `/sddflow`,执行状态检测,自动路由到对应阶段
78
+ 3. 如果用户指定了子命令(如 `/sddflow build`),按指定阶段执行,但检查前置条件
79
+ 4. 如果用户只输入 `/sddflow`(无子命令、非续接),执行状态检测,自动路由到对应阶段
75
80
  5. 读取当前 sddflow skill 目录下的阶段文件:`<阶段>.md`(与本 `SKILL.md` 同目录;不要依赖 Claude 专属环境变量)
76
81
  6. 按阶段文件中的流程执行,并遵守阶段写入边界
77
82
 
@@ -79,9 +84,8 @@ argument-hint: "proposal | brainstorming | spec | amend | build | close"
79
84
 
80
85
  | 阶段 | 前置条件 | 不满足时提示 |
81
86
  |------|----------|-------------|
82
- | proposal | 无 | — |
83
87
  | brainstorming | 无 | — |
84
- | spec | 需要有活跃变更目录或有用户需求 | "请先用 /sddflow proposal 或 /sddflow brainstorming 描述需求" |
88
+ | spec | 需要有活跃变更目录或有用户需求 | "请先用 /sddflow brainstorming 描述需求" |
85
89
  | amend | 需要有活跃变更目录,通常需要 plan-ready.md | "还没有可修订的活跃变更,请先完成 /sddflow spec" |
86
90
  | build | 需要存在 plan-ready.md | "请先完成 /sddflow spec 生成规格和翻译" |
87
91
  | close | 需要实现已完成 | "实现尚未完成,请先用 /sddflow build 执行" |
@@ -17,7 +17,7 @@ description: Revise active OpenSpec requirements during build, regenerate plan-r
17
17
 
18
18
  不适用:
19
19
  - 只是代码没有实现现有 spec → 继续 `/sddflow build`
20
- - 变更已经 close/archive → 开新的 `/sddflow proposal` 或 `/sddflow brainstorming`
20
+ - 变更已经 close/archive → 开新的 `/sddflow brainstorming`
21
21
  - 只是文案、注释或实现细节调整,且不改变行为 → 继续当前实现阶段
22
22
 
23
23
  ## 前置条件
@@ -27,10 +27,10 @@ description: Revise active OpenSpec requirements during build, regenerate plan-r
27
27
  - 通常应已存在 `plan-ready.md`;如果没有,优先回到 `/sddflow spec`
28
28
 
29
29
  如果没有 active change,提示:
30
- > "还没有活跃变更。请先用 /sddflow proposal 或 /sddflow brainstorming 创建需求。"
30
+ > "还没有活跃变更。请先用 /sddflow brainstorming 创建需求。"
31
31
 
32
32
  如果变更已归档,提示:
33
- > "该变更已经归档。归档后的需求调整应开启新的 /sddflow proposal。"
33
+ > "该变更已经归档。归档后的需求调整应开启新的 /sddflow brainstorming。"
34
34
 
35
35
  ## 流程
36
36
 
@@ -62,16 +62,18 @@ description: Revise active OpenSpec requirements during build, regenerate plan-r
62
62
 
63
63
  ### 3. 修改 OpenSpec 文档
64
64
 
65
+ **必须**遵循 `openspec/AGENTS.md` 与 **OpenSpec: Proposal** 中的格式与 delta 规则(与 `/sddflow spec` 步骤 2 相同,不得使用 sddflow 自订 spec 格式)。
66
+
65
67
  按影响范围更新:
66
68
 
67
- - `proposal.md`:追加 `## Amendments`,记录本次需求变更的日期、原因和摘要
68
- - `design.md`:仅当技术方案或约束变化时修改
69
- - `specs/<capability>/spec.md`:使用 `ADDED`、`MODIFIED`、`REMOVED` 或 `RENAMED Requirements` 表达 delta
70
- - `tasks.md`:追加新任务;不要重写或删除已完成任务
69
+ - `proposal.md`:追加 `## Amendments`,记录本次需求变更的日期、原因和摘要(不改变 OpenSpec 既有章节语义)
70
+ - `design.md`:仅当 AGENTS.md「Creating Change Proposals」判定需要 technical decisions 时修改
71
+ - `specs/<capability>/spec.md`:仅使用 AGENTS.md 规定的 `## ADDED|MODIFIED|REMOVED|RENAMED Requirements` `#### Scenario:` 格式
72
+ - `tasks.md`:追加新任务;不要重写或删除已完成任务;格式与 OpenSpec: Proposal 步骤 6 一致
71
73
 
72
- 要求:
73
- - 每个新增或修改的 requirement 必须至少有一个 `#### Scenario:`
74
- - `MODIFIED Requirements` 必须包含完整的新 requirement 文本,不只写差异片段
74
+ 要求(摘自 AGENTS.md,不得简化):
75
+ - 每个新增或修改的 requirement 至少一个 `#### Scenario:`
76
+ - `MODIFIED Requirements` 必须包含完整 requirement 文本(从 `openspec/specs/` 复制后再编辑)
75
77
  - 已完成任务保留原 checkbox 状态
76
78
 
77
79
  ### 4. 校验 OpenSpec
@@ -115,14 +117,16 @@ openspec validate <变更名> --strict
115
117
 
116
118
  ### 6. 同步详细实现计划
117
119
 
120
+ 若需新增或调整实现任务,**追加部分须遵循** Superpowers `writing-plans` skill(与 `/sddflow spec` 步骤 5 相同),并满足 spec 中「**三文档 Checkbox 对齐扩展**」(tasks / plan-ready / superpowers plan 均保留任务级与 Step 级 checkbox),为每个新 `### Task N` 填写 `> **trace:**` / `> **sync:**`。
121
+
118
122
  如果 `docs/superpowers/plans/YYYY-MM-DD-<变更名>.md` 已存在:
119
- - 已勾选任务不动
120
- - 未完成任务可按新需求调整
121
- - 追加新 task,保留 checkbox
123
+ - 已勾选 Task/Step 不动
124
+ - 未完成任务可按新需求调整(仍符合 writing-plans 无占位符规则)
125
+ - 追加新 Task 时同步更新 `plan-ready.md` 对应 `### Task` 与 **任务完成** checkbox、`tasks.md` 新条目
122
126
  - 记录本次 amend 来源路径
123
127
 
124
128
  如果详细实现计划还不存在:
125
- - 不创建代码实现计划,提示下一步用 `/sddflow build`
129
+ - 不创建实现计划,提示先 `/sddflow spec` 完成步骤 5
126
130
 
127
131
  ### 7. 提示下一步
128
132