specflow-cc 1.0.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/CHANGELOG.md +73 -0
- package/LICENSE +21 -0
- package/README.md +246 -0
- package/agents/impl-reviewer.md +271 -0
- package/agents/spec-auditor.md +196 -0
- package/agents/spec-creator.md +155 -0
- package/agents/spec-executor.md +235 -0
- package/agents/spec-reviser.md +184 -0
- package/agents/spec-splitter.md +197 -0
- package/bin/install.js +398 -0
- package/commands/sf/audit.md +210 -0
- package/commands/sf/deps.md +234 -0
- package/commands/sf/done.md +271 -0
- package/commands/sf/fix.md +272 -0
- package/commands/sf/help.md +263 -0
- package/commands/sf/history.md +268 -0
- package/commands/sf/init.md +217 -0
- package/commands/sf/list.md +127 -0
- package/commands/sf/metrics.md +319 -0
- package/commands/sf/new.md +171 -0
- package/commands/sf/next.md +182 -0
- package/commands/sf/pause.md +211 -0
- package/commands/sf/plan.md +210 -0
- package/commands/sf/priority.md +198 -0
- package/commands/sf/resume.md +248 -0
- package/commands/sf/review.md +258 -0
- package/commands/sf/revise.md +232 -0
- package/commands/sf/run.md +265 -0
- package/commands/sf/show.md +203 -0
- package/commands/sf/split.md +341 -0
- package/commands/sf/status.md +170 -0
- package/commands/sf/todo.md +130 -0
- package/commands/sf/todos.md +133 -0
- package/hooks/statusline.js +69 -0
- package/package.json +37 -0
- package/templates/audit.md +61 -0
- package/templates/project.md +39 -0
- package/templates/spec.md +59 -0
- package/templates/state.md +32 -0
- package/templates/todo.md +15 -0
package/bin/install.js
ADDED
|
@@ -0,0 +1,398 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* SpecFlow installer
|
|
5
|
+
* Copies commands, agents, templates, and hooks to ~/.claude/
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const fs = require('fs');
|
|
9
|
+
const path = require('path');
|
|
10
|
+
const os = require('os');
|
|
11
|
+
const readline = require('readline');
|
|
12
|
+
|
|
13
|
+
// Colors
|
|
14
|
+
const cyan = '\x1b[36m';
|
|
15
|
+
const green = '\x1b[32m';
|
|
16
|
+
const yellow = '\x1b[33m';
|
|
17
|
+
const dim = '\x1b[2m';
|
|
18
|
+
const reset = '\x1b[0m';
|
|
19
|
+
|
|
20
|
+
// Get version from package.json
|
|
21
|
+
const pkg = require('../package.json');
|
|
22
|
+
|
|
23
|
+
const banner = `
|
|
24
|
+
${cyan} ███████╗██████╗ ███████╗ ██████╗███████╗██╗ ██████╗ ██╗ ██╗
|
|
25
|
+
██╔════╝██╔══██╗██╔════╝██╔════╝██╔════╝██║ ██╔═══██╗██║ ██║
|
|
26
|
+
███████╗██████╔╝█████╗ ██║ █████╗ ██║ ██║ ██║██║ █╗ ██║
|
|
27
|
+
╚════██║██╔═══╝ ██╔══╝ ██║ ██╔══╝ ██║ ██║ ██║██║███╗██║
|
|
28
|
+
███████║██║ ███████╗╚██████╗██║ ███████╗╚██████╔╝╚███╔███╔╝
|
|
29
|
+
╚══════╝╚═╝ ╚══════╝ ╚═════╝╚═╝ ╚══════╝ ╚═════╝ ╚══╝╚══╝${reset}
|
|
30
|
+
|
|
31
|
+
Spec-Driven Development ${dim}v${pkg.version}${reset}
|
|
32
|
+
Quality-first workflow for Claude Code
|
|
33
|
+
`;
|
|
34
|
+
|
|
35
|
+
// Parse args
|
|
36
|
+
const args = process.argv.slice(2);
|
|
37
|
+
const hasGlobal = args.includes('--global') || args.includes('-g');
|
|
38
|
+
const hasLocal = args.includes('--local') || args.includes('-l');
|
|
39
|
+
const hasHelp = args.includes('--help') || args.includes('-h');
|
|
40
|
+
const forceStatusline = args.includes('--force-statusline');
|
|
41
|
+
|
|
42
|
+
console.log(banner);
|
|
43
|
+
|
|
44
|
+
// Show help if requested
|
|
45
|
+
if (hasHelp) {
|
|
46
|
+
console.log(` ${yellow}Usage:${reset} npx specflow-cc [options]
|
|
47
|
+
|
|
48
|
+
${yellow}Options:${reset}
|
|
49
|
+
${cyan}-g, --global${reset} Install globally (to ~/.claude)
|
|
50
|
+
${cyan}-l, --local${reset} Install locally (to ./.claude)
|
|
51
|
+
${cyan}--force-statusline${reset} Replace existing statusline config
|
|
52
|
+
${cyan}-h, --help${reset} Show this help message
|
|
53
|
+
|
|
54
|
+
${yellow}Examples:${reset}
|
|
55
|
+
${dim}# Install to default ~/.claude directory${reset}
|
|
56
|
+
npx specflow-cc --global
|
|
57
|
+
|
|
58
|
+
${dim}# Install to current project only${reset}
|
|
59
|
+
npx specflow-cc --local
|
|
60
|
+
`);
|
|
61
|
+
process.exit(0);
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/**
|
|
65
|
+
* Read and parse settings.json
|
|
66
|
+
*/
|
|
67
|
+
function readSettings(settingsPath) {
|
|
68
|
+
if (fs.existsSync(settingsPath)) {
|
|
69
|
+
try {
|
|
70
|
+
return JSON.parse(fs.readFileSync(settingsPath, 'utf8'));
|
|
71
|
+
} catch (e) {
|
|
72
|
+
return {};
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
return {};
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Write settings.json with proper formatting
|
|
80
|
+
*/
|
|
81
|
+
function writeSettings(settingsPath, settings) {
|
|
82
|
+
fs.writeFileSync(settingsPath, JSON.stringify(settings, null, 2) + '\n');
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
/**
|
|
86
|
+
* Recursively copy directory, replacing paths in .md files
|
|
87
|
+
*/
|
|
88
|
+
function copyWithPathReplacement(srcDir, destDir, pathPrefix) {
|
|
89
|
+
if (fs.existsSync(destDir)) {
|
|
90
|
+
fs.rmSync(destDir, { recursive: true });
|
|
91
|
+
}
|
|
92
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
93
|
+
|
|
94
|
+
const entries = fs.readdirSync(srcDir, { withFileTypes: true });
|
|
95
|
+
|
|
96
|
+
for (const entry of entries) {
|
|
97
|
+
const srcPath = path.join(srcDir, entry.name);
|
|
98
|
+
const destPath = path.join(destDir, entry.name);
|
|
99
|
+
|
|
100
|
+
if (entry.isDirectory()) {
|
|
101
|
+
copyWithPathReplacement(srcPath, destPath, pathPrefix);
|
|
102
|
+
} else if (entry.name.endsWith('.md')) {
|
|
103
|
+
// Replace ~/.claude/specflow-cc/ with the appropriate prefix
|
|
104
|
+
let content = fs.readFileSync(srcPath, 'utf8');
|
|
105
|
+
content = content.replace(/~\/\.claude\/specflow-cc\//g, pathPrefix);
|
|
106
|
+
fs.writeFileSync(destPath, content);
|
|
107
|
+
} else {
|
|
108
|
+
fs.copyFileSync(srcPath, destPath);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* Verify a directory exists and contains files
|
|
115
|
+
*/
|
|
116
|
+
function verifyInstalled(dirPath, description) {
|
|
117
|
+
if (!fs.existsSync(dirPath)) {
|
|
118
|
+
console.error(` ${yellow}✗${reset} Failed to install ${description}`);
|
|
119
|
+
return false;
|
|
120
|
+
}
|
|
121
|
+
try {
|
|
122
|
+
const entries = fs.readdirSync(dirPath);
|
|
123
|
+
if (entries.length === 0) {
|
|
124
|
+
console.error(` ${yellow}✗${reset} Failed to install ${description}: empty`);
|
|
125
|
+
return false;
|
|
126
|
+
}
|
|
127
|
+
} catch (e) {
|
|
128
|
+
console.error(` ${yellow}✗${reset} Failed to install ${description}: ${e.message}`);
|
|
129
|
+
return false;
|
|
130
|
+
}
|
|
131
|
+
return true;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Install to the specified directory
|
|
136
|
+
*/
|
|
137
|
+
function install(isGlobal) {
|
|
138
|
+
const src = path.join(__dirname, '..');
|
|
139
|
+
const claudeDir = isGlobal
|
|
140
|
+
? path.join(os.homedir(), '.claude')
|
|
141
|
+
: path.join(process.cwd(), '.claude');
|
|
142
|
+
|
|
143
|
+
const specflowDir = path.join(claudeDir, 'specflow-cc');
|
|
144
|
+
const locationLabel = isGlobal ? '~/.claude' : './.claude';
|
|
145
|
+
|
|
146
|
+
// Path prefix for file references
|
|
147
|
+
const pathPrefix = isGlobal ? '~/.claude/specflow-cc/' : './.claude/specflow-cc/';
|
|
148
|
+
|
|
149
|
+
console.log(` Installing to ${cyan}${locationLabel}${reset}\n`);
|
|
150
|
+
|
|
151
|
+
const failures = [];
|
|
152
|
+
|
|
153
|
+
// Ensure directories exist
|
|
154
|
+
fs.mkdirSync(specflowDir, { recursive: true });
|
|
155
|
+
fs.mkdirSync(path.join(claudeDir, 'commands'), { recursive: true });
|
|
156
|
+
|
|
157
|
+
// Copy commands/sf
|
|
158
|
+
const commandsSrc = path.join(src, 'commands', 'sf');
|
|
159
|
+
const commandsDest = path.join(claudeDir, 'commands', 'sf');
|
|
160
|
+
if (fs.existsSync(commandsSrc)) {
|
|
161
|
+
copyWithPathReplacement(commandsSrc, commandsDest, pathPrefix);
|
|
162
|
+
if (verifyInstalled(commandsDest, 'commands/sf')) {
|
|
163
|
+
console.log(` ${green}✓${reset} Installed commands/sf`);
|
|
164
|
+
} else {
|
|
165
|
+
failures.push('commands/sf');
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
// Copy agents to specflow-cc/agents (and also to ~/.claude/agents for subagent discovery)
|
|
170
|
+
const agentsSrc = path.join(src, 'agents');
|
|
171
|
+
if (fs.existsSync(agentsSrc)) {
|
|
172
|
+
// Copy to specflow-cc/agents
|
|
173
|
+
const agentsDest = path.join(specflowDir, 'agents');
|
|
174
|
+
copyWithPathReplacement(agentsSrc, agentsDest, pathPrefix);
|
|
175
|
+
|
|
176
|
+
// Also copy to ~/.claude/agents for subagent_type discovery
|
|
177
|
+
const globalAgentsDest = path.join(claudeDir, 'agents');
|
|
178
|
+
fs.mkdirSync(globalAgentsDest, { recursive: true });
|
|
179
|
+
|
|
180
|
+
// Remove old sf-* agents, then copy new ones
|
|
181
|
+
if (fs.existsSync(globalAgentsDest)) {
|
|
182
|
+
for (const file of fs.readdirSync(globalAgentsDest)) {
|
|
183
|
+
if (file.startsWith('sf-') && file.endsWith('.md')) {
|
|
184
|
+
fs.unlinkSync(path.join(globalAgentsDest, file));
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
for (const entry of fs.readdirSync(agentsSrc)) {
|
|
190
|
+
if (entry.endsWith('.md')) {
|
|
191
|
+
let content = fs.readFileSync(path.join(agentsSrc, entry), 'utf8');
|
|
192
|
+
content = content.replace(/~\/\.claude\/specflow-cc\//g, pathPrefix);
|
|
193
|
+
fs.writeFileSync(path.join(globalAgentsDest, `sf-${entry}`), content);
|
|
194
|
+
}
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
if (verifyInstalled(agentsDest, 'agents')) {
|
|
198
|
+
console.log(` ${green}✓${reset} Installed agents`);
|
|
199
|
+
} else {
|
|
200
|
+
failures.push('agents');
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// Copy templates
|
|
205
|
+
const templatesSrc = path.join(src, 'templates');
|
|
206
|
+
if (fs.existsSync(templatesSrc)) {
|
|
207
|
+
const templatesDest = path.join(specflowDir, 'templates');
|
|
208
|
+
copyWithPathReplacement(templatesSrc, templatesDest, pathPrefix);
|
|
209
|
+
if (verifyInstalled(templatesDest, 'templates')) {
|
|
210
|
+
console.log(` ${green}✓${reset} Installed templates`);
|
|
211
|
+
} else {
|
|
212
|
+
failures.push('templates');
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// Copy hooks
|
|
217
|
+
const hooksSrc = path.join(src, 'hooks');
|
|
218
|
+
if (fs.existsSync(hooksSrc)) {
|
|
219
|
+
const hooksDest = path.join(claudeDir, 'hooks');
|
|
220
|
+
fs.mkdirSync(hooksDest, { recursive: true });
|
|
221
|
+
for (const entry of fs.readdirSync(hooksSrc)) {
|
|
222
|
+
fs.copyFileSync(path.join(hooksSrc, entry), path.join(hooksDest, entry));
|
|
223
|
+
}
|
|
224
|
+
if (verifyInstalled(hooksDest, 'hooks')) {
|
|
225
|
+
console.log(` ${green}✓${reset} Installed hooks`);
|
|
226
|
+
} else {
|
|
227
|
+
failures.push('hooks');
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
// Copy CHANGELOG.md
|
|
232
|
+
const changelogSrc = path.join(src, 'CHANGELOG.md');
|
|
233
|
+
if (fs.existsSync(changelogSrc)) {
|
|
234
|
+
fs.copyFileSync(changelogSrc, path.join(specflowDir, 'CHANGELOG.md'));
|
|
235
|
+
console.log(` ${green}✓${reset} Installed CHANGELOG.md`);
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// Write VERSION file
|
|
239
|
+
fs.writeFileSync(path.join(specflowDir, 'VERSION'), pkg.version);
|
|
240
|
+
console.log(` ${green}✓${reset} Wrote VERSION (${pkg.version})`);
|
|
241
|
+
|
|
242
|
+
if (failures.length > 0) {
|
|
243
|
+
console.error(`\n ${yellow}Installation incomplete!${reset} Failed: ${failures.join(', ')}`);
|
|
244
|
+
process.exit(1);
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
// Configure statusline in settings.json
|
|
248
|
+
const settingsPath = path.join(claudeDir, 'settings.json');
|
|
249
|
+
const settings = readSettings(settingsPath);
|
|
250
|
+
const statuslineCommand = isGlobal
|
|
251
|
+
? 'node "$HOME/.claude/hooks/statusline.js"'
|
|
252
|
+
: 'node .claude/hooks/statusline.js';
|
|
253
|
+
|
|
254
|
+
return { settingsPath, settings, statuslineCommand };
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
/**
|
|
258
|
+
* Apply statusline config and print completion message
|
|
259
|
+
*/
|
|
260
|
+
function finishInstall(settingsPath, settings, statuslineCommand, shouldInstallStatusline) {
|
|
261
|
+
if (shouldInstallStatusline) {
|
|
262
|
+
settings.statusLine = {
|
|
263
|
+
type: 'command',
|
|
264
|
+
command: statuslineCommand
|
|
265
|
+
};
|
|
266
|
+
console.log(` ${green}✓${reset} Configured statusline`);
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
writeSettings(settingsPath, settings);
|
|
270
|
+
|
|
271
|
+
console.log(`
|
|
272
|
+
${green}Done!${reset} Launch Claude Code and run ${cyan}/sf help${reset}.
|
|
273
|
+
|
|
274
|
+
${yellow}Quick start:${reset}
|
|
275
|
+
/sf init - Initialize project
|
|
276
|
+
/sf new - Create specification
|
|
277
|
+
/sf help - Show all commands
|
|
278
|
+
`);
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
/**
|
|
282
|
+
* Handle statusline configuration
|
|
283
|
+
*/
|
|
284
|
+
function handleStatusline(settings, isInteractive, callback) {
|
|
285
|
+
const hasExisting = settings.statusLine != null;
|
|
286
|
+
|
|
287
|
+
if (!hasExisting) {
|
|
288
|
+
callback(true);
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
if (forceStatusline) {
|
|
293
|
+
callback(true);
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
if (!isInteractive) {
|
|
298
|
+
console.log(` ${yellow}⚠${reset} Skipping statusline (already configured)`);
|
|
299
|
+
console.log(` Use ${cyan}--force-statusline${reset} to replace\n`);
|
|
300
|
+
callback(false);
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
const existingCmd = settings.statusLine.command || settings.statusLine.url || '(custom)';
|
|
305
|
+
|
|
306
|
+
const rl = readline.createInterface({
|
|
307
|
+
input: process.stdin,
|
|
308
|
+
output: process.stdout
|
|
309
|
+
});
|
|
310
|
+
|
|
311
|
+
console.log(`
|
|
312
|
+
${yellow}⚠${reset} Existing statusline detected
|
|
313
|
+
|
|
314
|
+
Your current statusline:
|
|
315
|
+
${dim}command: ${existingCmd}${reset}
|
|
316
|
+
|
|
317
|
+
SpecFlow includes a statusline showing:
|
|
318
|
+
• Model name
|
|
319
|
+
• Current spec status [SF: SPEC-XXX status]
|
|
320
|
+
• Context window usage (color-coded)
|
|
321
|
+
|
|
322
|
+
${cyan}1${reset}) Keep existing
|
|
323
|
+
${cyan}2${reset}) Replace with SpecFlow statusline
|
|
324
|
+
`);
|
|
325
|
+
|
|
326
|
+
rl.question(` Choice ${dim}[1]${reset}: `, (answer) => {
|
|
327
|
+
rl.close();
|
|
328
|
+
const choice = answer.trim() || '1';
|
|
329
|
+
callback(choice === '2');
|
|
330
|
+
});
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
/**
|
|
334
|
+
* Prompt for install location
|
|
335
|
+
*/
|
|
336
|
+
function promptLocation() {
|
|
337
|
+
if (!process.stdin.isTTY) {
|
|
338
|
+
console.log(` ${yellow}Non-interactive terminal, defaulting to global install${reset}\n`);
|
|
339
|
+
const { settingsPath, settings, statuslineCommand } = install(true);
|
|
340
|
+
handleStatusline(settings, false, (shouldInstallStatusline) => {
|
|
341
|
+
finishInstall(settingsPath, settings, statuslineCommand, shouldInstallStatusline);
|
|
342
|
+
});
|
|
343
|
+
return;
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
const rl = readline.createInterface({
|
|
347
|
+
input: process.stdin,
|
|
348
|
+
output: process.stdout
|
|
349
|
+
});
|
|
350
|
+
|
|
351
|
+
let answered = false;
|
|
352
|
+
|
|
353
|
+
rl.on('close', () => {
|
|
354
|
+
if (!answered) {
|
|
355
|
+
answered = true;
|
|
356
|
+
console.log(`\n ${yellow}Input closed, defaulting to global install${reset}\n`);
|
|
357
|
+
const { settingsPath, settings, statuslineCommand } = install(true);
|
|
358
|
+
handleStatusline(settings, false, (shouldInstallStatusline) => {
|
|
359
|
+
finishInstall(settingsPath, settings, statuslineCommand, shouldInstallStatusline);
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
});
|
|
363
|
+
|
|
364
|
+
console.log(` ${yellow}Where would you like to install?${reset}
|
|
365
|
+
|
|
366
|
+
${cyan}1${reset}) Global ${dim}(~/.claude)${reset} - available in all projects
|
|
367
|
+
${cyan}2${reset}) Local ${dim}(./.claude)${reset} - this project only
|
|
368
|
+
`);
|
|
369
|
+
|
|
370
|
+
rl.question(` Choice ${dim}[1]${reset}: `, (answer) => {
|
|
371
|
+
answered = true;
|
|
372
|
+
rl.close();
|
|
373
|
+
const choice = answer.trim() || '1';
|
|
374
|
+
const isGlobal = choice !== '2';
|
|
375
|
+
const { settingsPath, settings, statuslineCommand } = install(isGlobal);
|
|
376
|
+
handleStatusline(settings, true, (shouldInstallStatusline) => {
|
|
377
|
+
finishInstall(settingsPath, settings, statuslineCommand, shouldInstallStatusline);
|
|
378
|
+
});
|
|
379
|
+
});
|
|
380
|
+
}
|
|
381
|
+
|
|
382
|
+
// Main
|
|
383
|
+
if (hasGlobal && hasLocal) {
|
|
384
|
+
console.error(` ${yellow}Cannot specify both --global and --local${reset}`);
|
|
385
|
+
process.exit(1);
|
|
386
|
+
} else if (hasGlobal) {
|
|
387
|
+
const { settingsPath, settings, statuslineCommand } = install(true);
|
|
388
|
+
handleStatusline(settings, false, (shouldInstallStatusline) => {
|
|
389
|
+
finishInstall(settingsPath, settings, statuslineCommand, shouldInstallStatusline);
|
|
390
|
+
});
|
|
391
|
+
} else if (hasLocal) {
|
|
392
|
+
const { settingsPath, settings, statuslineCommand } = install(false);
|
|
393
|
+
handleStatusline(settings, false, (shouldInstallStatusline) => {
|
|
394
|
+
finishInstall(settingsPath, settings, statuslineCommand, shouldInstallStatusline);
|
|
395
|
+
});
|
|
396
|
+
} else {
|
|
397
|
+
promptLocation();
|
|
398
|
+
}
|
|
@@ -0,0 +1,210 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: sf:audit
|
|
3
|
+
description: Audit the active specification in a fresh context
|
|
4
|
+
allowed-tools:
|
|
5
|
+
- Read
|
|
6
|
+
- Write
|
|
7
|
+
- Bash
|
|
8
|
+
- Glob
|
|
9
|
+
- Grep
|
|
10
|
+
- Task
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
<purpose>
|
|
14
|
+
Audit the active specification using a fresh context subagent. The auditor evaluates clarity, completeness, testability, scope, and feasibility without bias from the creation process.
|
|
15
|
+
</purpose>
|
|
16
|
+
|
|
17
|
+
<context>
|
|
18
|
+
@.specflow/STATE.md
|
|
19
|
+
@.specflow/PROJECT.md
|
|
20
|
+
@~/.claude/specflow-cc/agents/spec-auditor.md
|
|
21
|
+
</context>
|
|
22
|
+
|
|
23
|
+
<workflow>
|
|
24
|
+
|
|
25
|
+
## Step 1: Verify Initialization
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
[ -d .specflow ] && echo "OK" || echo "NOT_INITIALIZED"
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
**If NOT_INITIALIZED:**
|
|
32
|
+
```
|
|
33
|
+
SpecFlow not initialized.
|
|
34
|
+
|
|
35
|
+
Run `/sf init` first.
|
|
36
|
+
```
|
|
37
|
+
Exit.
|
|
38
|
+
|
|
39
|
+
## Step 2: Get Active Specification
|
|
40
|
+
|
|
41
|
+
Read `.specflow/STATE.md` and extract Active Specification.
|
|
42
|
+
|
|
43
|
+
**If no active specification:**
|
|
44
|
+
```
|
|
45
|
+
No active specification to audit.
|
|
46
|
+
|
|
47
|
+
Run `/sf new "task description"` to create one.
|
|
48
|
+
```
|
|
49
|
+
Exit.
|
|
50
|
+
|
|
51
|
+
## Step 3: Load Specification
|
|
52
|
+
|
|
53
|
+
Read the active spec file: `.specflow/specs/SPEC-XXX.md`
|
|
54
|
+
|
|
55
|
+
**If status is not 'draft' or 'revision_requested':**
|
|
56
|
+
```
|
|
57
|
+
Specification SPEC-XXX is already audited (status: {status}).
|
|
58
|
+
|
|
59
|
+
Use `/sf run` to implement or `/sf status` to see current state.
|
|
60
|
+
```
|
|
61
|
+
Exit.
|
|
62
|
+
|
|
63
|
+
## Step 4: Spawn Auditor Agent
|
|
64
|
+
|
|
65
|
+
Launch the spec-auditor subagent with fresh context:
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
Task(prompt="
|
|
69
|
+
<specification>
|
|
70
|
+
@.specflow/specs/SPEC-XXX.md
|
|
71
|
+
</specification>
|
|
72
|
+
|
|
73
|
+
<project_context>
|
|
74
|
+
@.specflow/PROJECT.md
|
|
75
|
+
</project_context>
|
|
76
|
+
|
|
77
|
+
Audit this specification following the spec-auditor agent instructions.
|
|
78
|
+
Do NOT read any conversation history — audit with fresh eyes.
|
|
79
|
+
", subagent_type="sf-spec-auditor", description="Audit specification")
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
## Step 5: Handle Agent Response
|
|
83
|
+
|
|
84
|
+
The agent will:
|
|
85
|
+
1. Evaluate 5 quality dimensions
|
|
86
|
+
2. Categorize issues (critical vs recommendations)
|
|
87
|
+
3. Append audit to spec's Audit History
|
|
88
|
+
4. Update STATE.md
|
|
89
|
+
5. Return structured result
|
|
90
|
+
|
|
91
|
+
## Step 6: Display Result
|
|
92
|
+
|
|
93
|
+
### If APPROVED:
|
|
94
|
+
|
|
95
|
+
```
|
|
96
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
97
|
+
AUDIT PASSED
|
|
98
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
99
|
+
|
|
100
|
+
**Specification:** SPEC-XXX
|
|
101
|
+
**Status:** APPROVED
|
|
102
|
+
|
|
103
|
+
{Comment from auditor}
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
107
|
+
## Next Step
|
|
108
|
+
|
|
109
|
+
`/sf run` — implement specification
|
|
110
|
+
```
|
|
111
|
+
|
|
112
|
+
### If NEEDS_REVISION:
|
|
113
|
+
|
|
114
|
+
```
|
|
115
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
116
|
+
AUDIT: NEEDS REVISION
|
|
117
|
+
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
118
|
+
|
|
119
|
+
**Specification:** SPEC-XXX
|
|
120
|
+
**Status:** NEEDS_REVISION
|
|
121
|
+
|
|
122
|
+
### Critical Issues
|
|
123
|
+
|
|
124
|
+
1. [Issue 1]
|
|
125
|
+
2. [Issue 2]
|
|
126
|
+
|
|
127
|
+
### Recommendations
|
|
128
|
+
|
|
129
|
+
3. [Recommendation 1]
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## Next Step
|
|
134
|
+
|
|
135
|
+
`/sf revise` — address critical issues
|
|
136
|
+
|
|
137
|
+
Options:
|
|
138
|
+
- `/sf revise all` — apply all feedback
|
|
139
|
+
- `/sf revise 1,2` — fix specific issues
|
|
140
|
+
- `/sf revise [instructions]` — custom changes
|
|
141
|
+
```
|
|
142
|
+
|
|
143
|
+
</workflow>
|
|
144
|
+
|
|
145
|
+
<fallback>
|
|
146
|
+
|
|
147
|
+
**If agent spawning fails**, execute inline:
|
|
148
|
+
|
|
149
|
+
## Inline Audit
|
|
150
|
+
|
|
151
|
+
### Check Quality Dimensions
|
|
152
|
+
|
|
153
|
+
Read spec and evaluate:
|
|
154
|
+
|
|
155
|
+
**Clarity:**
|
|
156
|
+
- [ ] Title clearly describes task
|
|
157
|
+
- [ ] Context explains why
|
|
158
|
+
- [ ] No vague terms
|
|
159
|
+
|
|
160
|
+
**Completeness:**
|
|
161
|
+
- [ ] All files listed
|
|
162
|
+
- [ ] Interfaces defined (if needed)
|
|
163
|
+
- [ ] Deletions specified (if refactor)
|
|
164
|
+
|
|
165
|
+
**Testability:**
|
|
166
|
+
- [ ] Each criterion measurable
|
|
167
|
+
- [ ] Concrete success conditions
|
|
168
|
+
|
|
169
|
+
**Scope:**
|
|
170
|
+
- [ ] Clear boundaries
|
|
171
|
+
- [ ] No scope creep
|
|
172
|
+
|
|
173
|
+
**Feasibility:**
|
|
174
|
+
- [ ] Technically sound
|
|
175
|
+
- [ ] Reasonable assumptions
|
|
176
|
+
|
|
177
|
+
### Record Audit
|
|
178
|
+
|
|
179
|
+
Get audit version number:
|
|
180
|
+
|
|
181
|
+
```bash
|
|
182
|
+
AUDIT_COUNT=$(grep -c "### Audit v" .specflow/specs/SPEC-XXX.md 2>/dev/null || echo 0)
|
|
183
|
+
NEXT_VERSION=$((AUDIT_COUNT + 1))
|
|
184
|
+
```
|
|
185
|
+
|
|
186
|
+
Append to spec's Audit History:
|
|
187
|
+
|
|
188
|
+
```markdown
|
|
189
|
+
### Audit v{N} ({date} {time})
|
|
190
|
+
**Status:** [APPROVED | NEEDS_REVISION]
|
|
191
|
+
|
|
192
|
+
{Issues and recommendations}
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### Update STATE.md
|
|
196
|
+
|
|
197
|
+
- If APPROVED: Status → "audited", Next Step → "/sf run"
|
|
198
|
+
- If NEEDS_REVISION: Status → "revision_requested", Next Step → "/sf revise"
|
|
199
|
+
|
|
200
|
+
</fallback>
|
|
201
|
+
|
|
202
|
+
<success_criteria>
|
|
203
|
+
- [ ] Active specification identified
|
|
204
|
+
- [ ] Fresh context audit performed
|
|
205
|
+
- [ ] All 5 dimensions evaluated
|
|
206
|
+
- [ ] Issues categorized
|
|
207
|
+
- [ ] Audit recorded in spec's Audit History
|
|
208
|
+
- [ ] STATE.md updated
|
|
209
|
+
- [ ] Clear next step provided
|
|
210
|
+
</success_criteria>
|