specflow-cc 1.13.0 → 1.14.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 +49 -0
- package/README.md +16 -9
- package/agents/spec-creator.md +3 -2
- package/agents/spec-splitter.md +1 -1
- package/bin/install.js +20 -0
- package/bin/lib/config.cjs +91 -0
- package/bin/lib/core.cjs +120 -0
- package/bin/lib/spec.cjs +130 -0
- package/bin/lib/state.cjs +241 -0
- package/bin/lib/verify.cjs +117 -0
- package/bin/sf-tools.cjs +103 -0
- package/commands/sf/done.md +21 -2
- package/commands/sf/health.md +220 -0
- package/commands/sf/help.md +2 -0
- package/commands/sf/split.md +14 -3
- package/commands/sf/validate.md +154 -0
- package/hooks/context-monitor.js +121 -0
- package/hooks/statusline.js +17 -0
- package/package.json +1 -1
|
@@ -0,0 +1,241 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* bin/lib/state.cjs — STATE.md CRUD operations
|
|
3
|
+
*
|
|
4
|
+
* Exports: cmdStateGet(), cmdStateSetActive(), cmdQueueNext()
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
const path = require('path');
|
|
11
|
+
const { output, error, safeReadFile } = require('./core.cjs');
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Extract a bold-field value from STATE.md content.
|
|
15
|
+
* Matches patterns like: **Field:** value
|
|
16
|
+
* @param {string} content - STATE.md content
|
|
17
|
+
* @param {string} field - Field name (e.g., "Status")
|
|
18
|
+
* @returns {string|null}
|
|
19
|
+
*/
|
|
20
|
+
function extractBoldField(content, field) {
|
|
21
|
+
const regex = new RegExp(`\\*\\*${field}:\\*\\*\\s*(.+)`, 'i');
|
|
22
|
+
const match = content.match(regex);
|
|
23
|
+
return match ? match[1].trim() : null;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Extract the active spec ID from STATE.md.
|
|
28
|
+
* The spec ID is on the line immediately after "## Active Specification".
|
|
29
|
+
* @param {string} content - STATE.md content
|
|
30
|
+
* @returns {string|null}
|
|
31
|
+
*/
|
|
32
|
+
function extractActiveSpec(content) {
|
|
33
|
+
const lines = content.split('\n');
|
|
34
|
+
for (let i = 0; i < lines.length; i++) {
|
|
35
|
+
if (lines[i].trim() === '## Active Specification') {
|
|
36
|
+
// Next non-empty line has the spec ID
|
|
37
|
+
for (let j = i + 1; j < lines.length; j++) {
|
|
38
|
+
const line = lines[j].trim();
|
|
39
|
+
if (line && !line.startsWith('**') && !line.startsWith('#')) {
|
|
40
|
+
return line;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
return null;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Parse the queue table from STATE.md.
|
|
50
|
+
* Expects pipe-delimited markdown table with columns:
|
|
51
|
+
* Priority | ID | Title | Status | Complexity | Depends On
|
|
52
|
+
* @param {string} content - STATE.md content
|
|
53
|
+
* @returns {Array<Object>}
|
|
54
|
+
*/
|
|
55
|
+
function parseQueueTable(content) {
|
|
56
|
+
const lines = content.split('\n');
|
|
57
|
+
const queue = [];
|
|
58
|
+
let inQueue = false;
|
|
59
|
+
let headerFound = false;
|
|
60
|
+
let separatorFound = false;
|
|
61
|
+
|
|
62
|
+
for (const line of lines) {
|
|
63
|
+
const trimmed = line.trim();
|
|
64
|
+
|
|
65
|
+
if (trimmed === '## Queue') {
|
|
66
|
+
inQueue = true;
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
if (inQueue && trimmed.startsWith('##') && trimmed !== '## Queue') {
|
|
71
|
+
break; // next section
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
if (!inQueue) continue;
|
|
75
|
+
|
|
76
|
+
if (trimmed.startsWith('|') && !headerFound) {
|
|
77
|
+
// Check if this is the header row
|
|
78
|
+
if (trimmed.toLowerCase().includes('priority') && trimmed.toLowerCase().includes('id')) {
|
|
79
|
+
headerFound = true;
|
|
80
|
+
continue;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
if (headerFound && !separatorFound && trimmed.startsWith('|') && trimmed.includes('---')) {
|
|
85
|
+
separatorFound = true;
|
|
86
|
+
continue;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
if (headerFound && separatorFound && trimmed.startsWith('|')) {
|
|
90
|
+
const cells = trimmed.split('|').map(c => c.trim()).filter(c => c !== '');
|
|
91
|
+
if (cells.length >= 4) {
|
|
92
|
+
queue.push({
|
|
93
|
+
priority: cells[0] || '',
|
|
94
|
+
id: cells[1] || '',
|
|
95
|
+
title: cells[2] || '',
|
|
96
|
+
status: cells[3] || '',
|
|
97
|
+
complexity: cells[4] || '',
|
|
98
|
+
depends_on: cells[5] || '',
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
return queue;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* Get current active spec, status, and next step from STATE.md.
|
|
109
|
+
* @param {string} cwd - Working directory
|
|
110
|
+
* @param {boolean} raw - Output raw string
|
|
111
|
+
*/
|
|
112
|
+
function cmdStateGet(cwd, raw) {
|
|
113
|
+
const statePath = path.join(cwd, '.specflow', 'STATE.md');
|
|
114
|
+
const content = safeReadFile(statePath);
|
|
115
|
+
|
|
116
|
+
if (!content) {
|
|
117
|
+
error('STATE.md not found at ' + statePath);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
const activeSpec = extractActiveSpec(content);
|
|
121
|
+
const status = extractBoldField(content, 'Status');
|
|
122
|
+
const nextStep = extractBoldField(content, 'Next Step');
|
|
123
|
+
|
|
124
|
+
const result = {
|
|
125
|
+
active_spec: activeSpec || null,
|
|
126
|
+
status: status || null,
|
|
127
|
+
next_step: nextStep || null,
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
output(result, raw, result.active_spec);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
/**
|
|
134
|
+
* Update active spec, status, and optionally next step in STATE.md.
|
|
135
|
+
* @param {string} cwd - Working directory
|
|
136
|
+
* @param {string} id - Spec ID (e.g., "SPEC-007")
|
|
137
|
+
* @param {string} status - New status
|
|
138
|
+
* @param {string} [nextStep] - Optional new next step
|
|
139
|
+
* @param {boolean} raw - Output raw string
|
|
140
|
+
*/
|
|
141
|
+
function cmdStateSetActive(cwd, id, status, nextStep, raw) {
|
|
142
|
+
const statePath = path.join(cwd, '.specflow', 'STATE.md');
|
|
143
|
+
const content = safeReadFile(statePath);
|
|
144
|
+
|
|
145
|
+
if (!content) {
|
|
146
|
+
error('STATE.md not found at ' + statePath);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
const lines = content.split('\n');
|
|
150
|
+
const result = [];
|
|
151
|
+
let inActiveSection = false;
|
|
152
|
+
let specIdReplaced = false;
|
|
153
|
+
|
|
154
|
+
for (let i = 0; i < lines.length; i++) {
|
|
155
|
+
const trimmed = lines[i].trim();
|
|
156
|
+
|
|
157
|
+
if (trimmed === '## Active Specification') {
|
|
158
|
+
inActiveSection = true;
|
|
159
|
+
result.push(lines[i]);
|
|
160
|
+
continue;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
if (inActiveSection && trimmed.startsWith('## ') && trimmed !== '## Active Specification') {
|
|
164
|
+
inActiveSection = false;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (inActiveSection && !specIdReplaced && trimmed && !trimmed.startsWith('**') && !trimmed.startsWith('#')) {
|
|
168
|
+
// This is the spec ID line — replace it
|
|
169
|
+
result.push(id);
|
|
170
|
+
specIdReplaced = true;
|
|
171
|
+
continue;
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
if (inActiveSection && trimmed.startsWith('**Status:**')) {
|
|
175
|
+
result.push('**Status:** ' + status);
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (inActiveSection && trimmed.startsWith('**Next Step:**')) {
|
|
180
|
+
if (nextStep !== undefined && nextStep !== null) {
|
|
181
|
+
result.push('**Next Step:** ' + nextStep);
|
|
182
|
+
} else {
|
|
183
|
+
result.push(lines[i]); // preserve existing
|
|
184
|
+
}
|
|
185
|
+
continue;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
result.push(lines[i]);
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
fs.writeFileSync(statePath, result.join('\n'), 'utf8');
|
|
192
|
+
|
|
193
|
+
const resultObj = {
|
|
194
|
+
updated: true,
|
|
195
|
+
active_spec: id,
|
|
196
|
+
status: status,
|
|
197
|
+
next_step: nextStep || extractBoldField(result.join('\n'), 'Next Step'),
|
|
198
|
+
};
|
|
199
|
+
|
|
200
|
+
output(resultObj, raw, 'updated');
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
/**
|
|
204
|
+
* Get the first actionable spec from the queue.
|
|
205
|
+
* "Actionable" = status is not "done" or "complete".
|
|
206
|
+
* @param {string} cwd - Working directory
|
|
207
|
+
* @param {boolean} raw - Output raw string
|
|
208
|
+
*/
|
|
209
|
+
function cmdQueueNext(cwd, raw) {
|
|
210
|
+
const statePath = path.join(cwd, '.specflow', 'STATE.md');
|
|
211
|
+
const content = safeReadFile(statePath);
|
|
212
|
+
|
|
213
|
+
if (!content) {
|
|
214
|
+
error('STATE.md not found at ' + statePath);
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
const queue = parseQueueTable(content);
|
|
218
|
+
|
|
219
|
+
const next = queue.find(entry => {
|
|
220
|
+
const s = entry.status.toLowerCase();
|
|
221
|
+
return s !== 'done' && s !== 'complete';
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
if (next) {
|
|
225
|
+
output({
|
|
226
|
+
id: next.id,
|
|
227
|
+
title: next.title,
|
|
228
|
+
status: next.status,
|
|
229
|
+
priority: next.priority,
|
|
230
|
+
}, raw, next.id);
|
|
231
|
+
} else {
|
|
232
|
+
output({ id: null }, raw, '');
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
module.exports = {
|
|
237
|
+
cmdStateGet,
|
|
238
|
+
cmdStateSetActive,
|
|
239
|
+
cmdQueueNext,
|
|
240
|
+
extractActiveSpec,
|
|
241
|
+
};
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* bin/lib/verify.cjs — Structure verification checks
|
|
3
|
+
*
|
|
4
|
+
* Exports: cmdVerifyStructure()
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
'use strict';
|
|
8
|
+
|
|
9
|
+
const fs = require('fs');
|
|
10
|
+
const path = require('path');
|
|
11
|
+
const { output, safeReadFile, parseFrontmatter } = require('./core.cjs');
|
|
12
|
+
const { extractActiveSpec } = require('./state.cjs');
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Verify .specflow/ directory structure and integrity.
|
|
16
|
+
* Performs 6 read-only checks. Does NOT repair issues.
|
|
17
|
+
*
|
|
18
|
+
* @param {string} cwd - Working directory
|
|
19
|
+
* @param {boolean} raw - Output raw string
|
|
20
|
+
*/
|
|
21
|
+
function cmdVerifyStructure(cwd, raw) {
|
|
22
|
+
const checks = [];
|
|
23
|
+
const errors = [];
|
|
24
|
+
|
|
25
|
+
// Check 1: .specflow/ directory exists
|
|
26
|
+
const specflowDir = path.join(cwd, '.specflow');
|
|
27
|
+
const specflowExists = fs.existsSync(specflowDir) && fs.statSync(specflowDir).isDirectory();
|
|
28
|
+
checks.push({ name: '.specflow/ directory exists', passed: specflowExists });
|
|
29
|
+
if (!specflowExists) {
|
|
30
|
+
errors.push('.specflow/ directory not found');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Check 2: STATE.md exists and has "Active Specification" section
|
|
34
|
+
const statePath = path.join(cwd, '.specflow', 'STATE.md');
|
|
35
|
+
const stateContent = safeReadFile(statePath);
|
|
36
|
+
const stateHasSection = stateContent ? stateContent.includes('## Active Specification') : false;
|
|
37
|
+
const stateValid = !!stateContent && stateHasSection;
|
|
38
|
+
checks.push({ name: 'STATE.md exists with Active Specification section', passed: stateValid });
|
|
39
|
+
if (!stateContent) {
|
|
40
|
+
errors.push('STATE.md not found');
|
|
41
|
+
} else if (!stateHasSection) {
|
|
42
|
+
errors.push('STATE.md missing "## Active Specification" section');
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
// Check 3: specs/ directory exists
|
|
46
|
+
const specsDir = path.join(cwd, '.specflow', 'specs');
|
|
47
|
+
const specsDirExists = fs.existsSync(specsDir) && fs.statSync(specsDir).isDirectory();
|
|
48
|
+
checks.push({ name: '.specflow/specs/ directory exists', passed: specsDirExists });
|
|
49
|
+
if (!specsDirExists) {
|
|
50
|
+
errors.push('.specflow/specs/ directory not found');
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// Check 4: archive/ directory exists
|
|
54
|
+
const archiveDir = path.join(cwd, '.specflow', 'archive');
|
|
55
|
+
const archiveDirExists = fs.existsSync(archiveDir) && fs.statSync(archiveDir).isDirectory();
|
|
56
|
+
checks.push({ name: '.specflow/archive/ directory exists', passed: archiveDirExists });
|
|
57
|
+
if (!archiveDirExists) {
|
|
58
|
+
errors.push('.specflow/archive/ directory not found');
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Check 5: Active spec referenced in STATE.md exists in specs/ (if set)
|
|
62
|
+
let activeSpecCheck = true;
|
|
63
|
+
if (stateContent) {
|
|
64
|
+
const activeSpec = extractActiveSpec(stateContent);
|
|
65
|
+
|
|
66
|
+
if (activeSpec && activeSpec !== 'None' && activeSpec !== '(none)') {
|
|
67
|
+
const activeSpecPath = path.join(specsDir, activeSpec + '.md');
|
|
68
|
+
const activeSpecExists = fs.existsSync(activeSpecPath);
|
|
69
|
+
activeSpecCheck = activeSpecExists;
|
|
70
|
+
if (!activeSpecExists) {
|
|
71
|
+
errors.push('Active spec ' + activeSpec + ' not found in specs/');
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
checks.push({ name: 'Active spec exists in specs/ (if set)', passed: activeSpecCheck });
|
|
76
|
+
|
|
77
|
+
// Check 6: All specs in specs/ have valid YAML frontmatter with required fields
|
|
78
|
+
let allSpecsValid = true;
|
|
79
|
+
if (specsDirExists) {
|
|
80
|
+
let specFiles;
|
|
81
|
+
try {
|
|
82
|
+
specFiles = fs.readdirSync(specsDir).filter(f => f.endsWith('.md'));
|
|
83
|
+
} catch (e) {
|
|
84
|
+
specFiles = [];
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
for (const file of specFiles) {
|
|
88
|
+
const content = safeReadFile(path.join(specsDir, file));
|
|
89
|
+
if (!content) {
|
|
90
|
+
allSpecsValid = false;
|
|
91
|
+
errors.push('Cannot read spec file: ' + file);
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
const parsed = parseFrontmatter(content);
|
|
96
|
+
const fm = parsed.frontmatter;
|
|
97
|
+
const requiredFields = ['id', 'type', 'status'];
|
|
98
|
+
const missingFields = requiredFields.filter(f => !fm[f]);
|
|
99
|
+
|
|
100
|
+
if (missingFields.length > 0) {
|
|
101
|
+
allSpecsValid = false;
|
|
102
|
+
errors.push(file + ' missing required frontmatter fields: ' + missingFields.join(', '));
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
} else {
|
|
106
|
+
allSpecsValid = false;
|
|
107
|
+
}
|
|
108
|
+
checks.push({ name: 'All specs have valid frontmatter (id, type, status)', passed: allSpecsValid });
|
|
109
|
+
|
|
110
|
+
const valid = checks.every(c => c.passed);
|
|
111
|
+
|
|
112
|
+
output({ valid, checks, errors }, raw, valid ? 'valid' : 'invalid');
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
module.exports = {
|
|
116
|
+
cmdVerifyStructure,
|
|
117
|
+
};
|
package/bin/sf-tools.cjs
ADDED
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* bin/sf-tools.cjs — Centralized CLI for SpecFlow operations
|
|
5
|
+
*
|
|
6
|
+
* Usage: node bin/sf-tools.cjs <command> [args...]
|
|
7
|
+
*
|
|
8
|
+
* Commands:
|
|
9
|
+
* spec load <id> Parse spec file, return frontmatter + body
|
|
10
|
+
* spec list List all specs
|
|
11
|
+
* spec next-id Next available SPEC-XXX number
|
|
12
|
+
* queue next First actionable spec from queue
|
|
13
|
+
* state get Current active spec, status, next step
|
|
14
|
+
* state set-active <id> <status> [next] Update active spec in STATE.md
|
|
15
|
+
* resolve-model <agent-type> Model for agent by current profile
|
|
16
|
+
* verify-structure Check .specflow/ integrity
|
|
17
|
+
* generate-slug <text> Text to URL-safe slug
|
|
18
|
+
*/
|
|
19
|
+
|
|
20
|
+
'use strict';
|
|
21
|
+
|
|
22
|
+
const { output, error, generateSlug } = require('./lib/core.cjs');
|
|
23
|
+
const { cmdStateGet, cmdStateSetActive, cmdQueueNext } = require('./lib/state.cjs');
|
|
24
|
+
const { cmdSpecLoad, cmdSpecList, cmdSpecNextId } = require('./lib/spec.cjs');
|
|
25
|
+
const { cmdResolveModel } = require('./lib/config.cjs');
|
|
26
|
+
const { cmdVerifyStructure } = require('./lib/verify.cjs');
|
|
27
|
+
|
|
28
|
+
const cwd = process.cwd();
|
|
29
|
+
const args = process.argv.slice(2);
|
|
30
|
+
const raw = args.includes('--raw');
|
|
31
|
+
const filteredArgs = args.filter(a => a !== '--raw');
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Command dispatch table.
|
|
35
|
+
* Keys are "command subcommand" or just "command".
|
|
36
|
+
*/
|
|
37
|
+
const COMMANDS = {
|
|
38
|
+
'spec load': () => {
|
|
39
|
+
if (!filteredArgs[2]) error('Missing spec ID. Usage: spec load <id>');
|
|
40
|
+
cmdSpecLoad(cwd, filteredArgs[2], raw);
|
|
41
|
+
},
|
|
42
|
+
'spec list': () => cmdSpecList(cwd, raw),
|
|
43
|
+
'spec next-id': () => cmdSpecNextId(cwd, raw),
|
|
44
|
+
'queue next': () => cmdQueueNext(cwd, raw),
|
|
45
|
+
'state get': () => cmdStateGet(cwd, raw),
|
|
46
|
+
'state set-active': () => {
|
|
47
|
+
if (!filteredArgs[2] || !filteredArgs[3]) {
|
|
48
|
+
error('Missing arguments. Usage: state set-active <id> <status> [next_step]');
|
|
49
|
+
}
|
|
50
|
+
cmdStateSetActive(cwd, filteredArgs[2], filteredArgs[3], filteredArgs[4], raw);
|
|
51
|
+
},
|
|
52
|
+
'resolve-model': () => {
|
|
53
|
+
if (!filteredArgs[1]) error('Missing agent type. Usage: resolve-model <agent-type>');
|
|
54
|
+
cmdResolveModel(cwd, filteredArgs[1], raw);
|
|
55
|
+
},
|
|
56
|
+
'verify-structure': () => cmdVerifyStructure(cwd, raw),
|
|
57
|
+
'generate-slug': () => {
|
|
58
|
+
if (!filteredArgs[1]) error('Missing text. Usage: generate-slug <text>');
|
|
59
|
+
output({ slug: generateSlug(filteredArgs[1]) }, raw, generateSlug(filteredArgs[1]));
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
|
|
63
|
+
function printUsage() {
|
|
64
|
+
const usage = `sf-tools — SpecFlow CLI
|
|
65
|
+
|
|
66
|
+
Usage: node bin/sf-tools.cjs <command> [args...] [--raw]
|
|
67
|
+
|
|
68
|
+
Commands:
|
|
69
|
+
spec load <id> Parse spec file, return frontmatter + body
|
|
70
|
+
spec list List all specs from .specflow/specs/
|
|
71
|
+
spec next-id Next available SPEC-XXX number
|
|
72
|
+
queue next First actionable spec from queue table
|
|
73
|
+
state get Current active spec, status, next step
|
|
74
|
+
state set-active <id> <status> [next] Update active spec, status, next step
|
|
75
|
+
resolve-model <agent-type> Resolve model for agent by current profile
|
|
76
|
+
verify-structure Check .specflow/ directory integrity
|
|
77
|
+
generate-slug <text> Convert text to URL-safe slug
|
|
78
|
+
|
|
79
|
+
Options:
|
|
80
|
+
--raw Output plain string instead of JSON
|
|
81
|
+
|
|
82
|
+
All commands output JSON to stdout. Errors go to stderr with exit code 1.
|
|
83
|
+
`;
|
|
84
|
+
process.stdout.write(usage);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
// Main dispatch
|
|
88
|
+
if (filteredArgs.length === 0) {
|
|
89
|
+
printUsage();
|
|
90
|
+
process.exit(0);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Try two-word command first, then single-word
|
|
94
|
+
const twoWord = filteredArgs[0] + ' ' + (filteredArgs[1] || '');
|
|
95
|
+
const oneWord = filteredArgs[0];
|
|
96
|
+
|
|
97
|
+
if (COMMANDS[twoWord]) {
|
|
98
|
+
COMMANDS[twoWord]();
|
|
99
|
+
} else if (COMMANDS[oneWord]) {
|
|
100
|
+
COMMANDS[oneWord]();
|
|
101
|
+
} else {
|
|
102
|
+
error('Unknown command: ' + filteredArgs.join(' ') + '. Run without arguments for usage help.');
|
|
103
|
+
}
|
package/commands/sf/done.md
CHANGED
|
@@ -175,7 +175,7 @@ mkdir -p .specflow/archive
|
|
|
175
175
|
Update frontmatter:
|
|
176
176
|
- status → "done"
|
|
177
177
|
|
|
178
|
-
Add completion
|
|
178
|
+
Add completion summary section to the spec:
|
|
179
179
|
|
|
180
180
|
```markdown
|
|
181
181
|
---
|
|
@@ -185,14 +185,33 @@ Add completion timestamp:
|
|
|
185
185
|
**Completed:** {date} {time}
|
|
186
186
|
**Total Commits:** {count from Execution Summary}
|
|
187
187
|
**Review Cycles:** {count of Review v[N] entries}
|
|
188
|
+
|
|
189
|
+
### Outcome
|
|
190
|
+
|
|
191
|
+
{1-2 sentence summary of what was delivered}
|
|
192
|
+
|
|
193
|
+
### Key Files
|
|
194
|
+
|
|
195
|
+
- `{path}` — {what it does/why it matters}
|
|
196
|
+
|
|
197
|
+
### Patterns Established
|
|
198
|
+
|
|
199
|
+
{List any new patterns, conventions, or architectural decisions introduced.
|
|
200
|
+
If none: "None — followed existing patterns."}
|
|
201
|
+
|
|
202
|
+
### Deviations
|
|
203
|
+
|
|
204
|
+
{Any deviations from the original spec during implementation.
|
|
205
|
+
If none: "None — implemented as specified."}
|
|
188
206
|
```
|
|
189
207
|
|
|
190
208
|
## Step 7: Extract Decisions
|
|
191
209
|
|
|
192
|
-
Scan specification for important decisions:
|
|
210
|
+
Scan specification and Completion section for important decisions:
|
|
193
211
|
- Technology choices mentioned in Context or Assumptions
|
|
194
212
|
- Patterns established during implementation
|
|
195
213
|
- Constraints discovered
|
|
214
|
+
- Deviations that became new conventions
|
|
196
215
|
|
|
197
216
|
If significant decisions found, add to STATE.md Decisions table:
|
|
198
217
|
|