specsmd 0.0.0-dev.53 → 0.0.0-dev.55

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,319 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * FIRE Run Initialization Script
5
+ *
6
+ * Creates run record in state.yaml and run folder structure.
7
+ * Ensures deterministic run ID generation by checking BOTH:
8
+ * - runs.completed history in state.yaml
9
+ * - existing run folders in .specs-fire/runs/
10
+ *
11
+ * Usage: node init-run.js <rootPath> <workItemId> <intentId> <mode>
12
+ *
13
+ * Example: node init-run.js /project login-endpoint user-auth confirm
14
+ */
15
+
16
+ const fs = require('fs');
17
+ const path = require('path');
18
+ const yaml = require('yaml');
19
+
20
+ // =============================================================================
21
+ // Error Helper
22
+ // =============================================================================
23
+
24
+ function fireError(message, code, suggestion) {
25
+ const err = new Error(`FIRE Error [${code}]: ${message} ${suggestion}`);
26
+ err.code = code;
27
+ err.suggestion = suggestion;
28
+ return err;
29
+ }
30
+
31
+ // =============================================================================
32
+ // Validation
33
+ // =============================================================================
34
+
35
+ const VALID_MODES = ['autopilot', 'confirm', 'validate'];
36
+
37
+ function validateInputs(rootPath, workItemId, intentId, mode) {
38
+ if (!rootPath || typeof rootPath !== 'string' || rootPath.trim() === '') {
39
+ throw fireError('rootPath is required.', 'INIT_001', 'Provide a valid project root path.');
40
+ }
41
+
42
+ if (!workItemId || typeof workItemId !== 'string' || workItemId.trim() === '') {
43
+ throw fireError('workItemId is required.', 'INIT_010', 'Provide a valid work item ID.');
44
+ }
45
+
46
+ if (!intentId || typeof intentId !== 'string' || intentId.trim() === '') {
47
+ throw fireError('intentId is required.', 'INIT_020', 'Provide a valid intent ID.');
48
+ }
49
+
50
+ if (!mode || !VALID_MODES.includes(mode)) {
51
+ throw fireError(
52
+ `Invalid mode: "${mode}".`,
53
+ 'INIT_030',
54
+ `Valid modes are: ${VALID_MODES.join(', ')}`
55
+ );
56
+ }
57
+
58
+ if (!fs.existsSync(rootPath)) {
59
+ throw fireError(
60
+ `Project root not found: "${rootPath}".`,
61
+ 'INIT_040',
62
+ 'Ensure the path exists and is accessible.'
63
+ );
64
+ }
65
+ }
66
+
67
+ function validateFireProject(rootPath) {
68
+ const fireDir = path.join(rootPath, '.specs-fire');
69
+ const statePath = path.join(fireDir, 'state.yaml');
70
+ const runsPath = path.join(fireDir, 'runs');
71
+
72
+ if (!fs.existsSync(fireDir)) {
73
+ throw fireError(
74
+ `FIRE project not initialized at: "${rootPath}".`,
75
+ 'INIT_041',
76
+ 'Run fire-init first to initialize the project.'
77
+ );
78
+ }
79
+
80
+ if (!fs.existsSync(statePath)) {
81
+ throw fireError(
82
+ `State file not found at: "${statePath}".`,
83
+ 'INIT_042',
84
+ 'The project may be corrupted. Try re-initializing.'
85
+ );
86
+ }
87
+
88
+ return { fireDir, statePath, runsPath };
89
+ }
90
+
91
+ // =============================================================================
92
+ // State Operations
93
+ // =============================================================================
94
+
95
+ function readState(statePath) {
96
+ try {
97
+ const content = fs.readFileSync(statePath, 'utf8');
98
+ const state = yaml.parse(content);
99
+ if (!state || typeof state !== 'object') {
100
+ throw fireError('State file is empty or invalid.', 'INIT_050', 'Check state.yaml format.');
101
+ }
102
+ return state;
103
+ } catch (err) {
104
+ if (err.code && err.code.startsWith('INIT_')) throw err;
105
+ throw fireError(
106
+ `Failed to read state file: ${err.message}`,
107
+ 'INIT_051',
108
+ 'Check file permissions and YAML syntax.'
109
+ );
110
+ }
111
+ }
112
+
113
+ function writeState(statePath, state) {
114
+ try {
115
+ fs.writeFileSync(statePath, yaml.stringify(state));
116
+ } catch (err) {
117
+ throw fireError(
118
+ `Failed to write state file: ${err.message}`,
119
+ 'INIT_052',
120
+ 'Check file permissions and disk space.'
121
+ );
122
+ }
123
+ }
124
+
125
+ // =============================================================================
126
+ // Run ID Generation (CRITICAL - checks both history and file system)
127
+ // =============================================================================
128
+
129
+ function generateRunId(runsPath, state) {
130
+ // Ensure runs directory exists
131
+ if (!fs.existsSync(runsPath)) {
132
+ fs.mkdirSync(runsPath, { recursive: true });
133
+ }
134
+
135
+ // Source 1: Get max from state.yaml runs.completed history
136
+ let maxFromHistory = 0;
137
+ if (state.runs && Array.isArray(state.runs.completed)) {
138
+ for (const run of state.runs.completed) {
139
+ if (run.id) {
140
+ const match = run.id.match(/^run-(\d+)$/);
141
+ if (match) {
142
+ const num = parseInt(match[1], 10);
143
+ if (num > maxFromHistory) maxFromHistory = num;
144
+ }
145
+ }
146
+ }
147
+ }
148
+
149
+ // Source 2: Get max from file system (defensive)
150
+ let maxFromFileSystem = 0;
151
+ try {
152
+ const entries = fs.readdirSync(runsPath);
153
+ for (const entry of entries) {
154
+ if (/^run-\d{3,}$/.test(entry)) {
155
+ const num = parseInt(entry.replace('run-', ''), 10);
156
+ if (num > maxFromFileSystem) maxFromFileSystem = num;
157
+ }
158
+ }
159
+ } catch (err) {
160
+ throw fireError(
161
+ `Failed to read runs directory: ${err.message}`,
162
+ 'INIT_060',
163
+ 'Check directory permissions.'
164
+ );
165
+ }
166
+
167
+ // Use MAX of both to ensure no duplicates
168
+ const maxNum = Math.max(maxFromHistory, maxFromFileSystem);
169
+ const nextNum = maxNum + 1;
170
+
171
+ return `run-${String(nextNum).padStart(3, '0')}`;
172
+ }
173
+
174
+ // =============================================================================
175
+ // Run Folder Creation
176
+ // =============================================================================
177
+
178
+ function createRunFolder(runPath) {
179
+ try {
180
+ fs.mkdirSync(runPath, { recursive: true });
181
+ } catch (err) {
182
+ throw fireError(
183
+ `Failed to create run folder: ${err.message}`,
184
+ 'INIT_070',
185
+ 'Check directory permissions and disk space.'
186
+ );
187
+ }
188
+ }
189
+
190
+ function createRunLog(runPath, runId, workItemId, intentId, mode, startTime) {
191
+ const runLog = `---
192
+ id: ${runId}
193
+ work_item: ${workItemId}
194
+ intent: ${intentId}
195
+ mode: ${mode}
196
+ status: in_progress
197
+ started: ${startTime}
198
+ completed: null
199
+ ---
200
+
201
+ # Run: ${runId}
202
+
203
+ ## Work Item
204
+ ${workItemId}
205
+
206
+ ## Files Created
207
+ (none yet)
208
+
209
+ ## Files Modified
210
+ (none yet)
211
+
212
+ ## Decisions
213
+ (none yet)
214
+ `;
215
+
216
+ const runLogPath = path.join(runPath, 'run.md');
217
+ try {
218
+ fs.writeFileSync(runLogPath, runLog);
219
+ } catch (err) {
220
+ throw fireError(
221
+ `Failed to create run log: ${err.message}`,
222
+ 'INIT_071',
223
+ 'Check file permissions.'
224
+ );
225
+ }
226
+ }
227
+
228
+ // =============================================================================
229
+ // Main Function
230
+ // =============================================================================
231
+
232
+ function initRun(rootPath, workItemId, intentId, mode) {
233
+ // Validate inputs
234
+ validateInputs(rootPath, workItemId, intentId, mode);
235
+
236
+ // Validate FIRE project structure
237
+ const { statePath, runsPath } = validateFireProject(rootPath);
238
+
239
+ // Read state
240
+ const state = readState(statePath);
241
+
242
+ // Check for existing active run
243
+ if (state.active_run) {
244
+ throw fireError(
245
+ `A run is already active: "${state.active_run.id}".`,
246
+ 'INIT_080',
247
+ `Complete or cancel run "${state.active_run.id}" before starting a new one.`
248
+ );
249
+ }
250
+
251
+ // Generate run ID (checks both history AND file system)
252
+ const runId = generateRunId(runsPath, state);
253
+ const runPath = path.join(runsPath, runId);
254
+
255
+ // Create run folder
256
+ createRunFolder(runPath);
257
+
258
+ // Create run log
259
+ const startTime = new Date().toISOString();
260
+ createRunLog(runPath, runId, workItemId, intentId, mode, startTime);
261
+
262
+ // Update state with active run
263
+ state.active_run = {
264
+ id: runId,
265
+ work_item: workItemId,
266
+ intent: intentId,
267
+ mode: mode,
268
+ started: startTime,
269
+ };
270
+
271
+ // Save state
272
+ writeState(statePath, state);
273
+
274
+ // Return result
275
+ return {
276
+ success: true,
277
+ runId: runId,
278
+ runPath: runPath,
279
+ workItemId: workItemId,
280
+ intentId: intentId,
281
+ mode: mode,
282
+ started: startTime,
283
+ };
284
+ }
285
+
286
+ // =============================================================================
287
+ // CLI Interface
288
+ // =============================================================================
289
+
290
+ if (require.main === module) {
291
+ const args = process.argv.slice(2);
292
+
293
+ if (args.length < 4) {
294
+ console.error('Usage: node init-run.js <rootPath> <workItemId> <intentId> <mode>');
295
+ console.error('');
296
+ console.error('Arguments:');
297
+ console.error(' rootPath - Project root directory');
298
+ console.error(' workItemId - Work item ID to execute');
299
+ console.error(' intentId - Intent ID containing the work item');
300
+ console.error(' mode - Execution mode (autopilot, confirm, validate)');
301
+ console.error('');
302
+ console.error('Example:');
303
+ console.error(' node init-run.js /my/project login-endpoint user-auth confirm');
304
+ process.exit(1);
305
+ }
306
+
307
+ const [rootPath, workItemId, intentId, mode] = args;
308
+
309
+ try {
310
+ const result = initRun(rootPath, workItemId, intentId, mode);
311
+ console.log(JSON.stringify(result, null, 2));
312
+ process.exit(0);
313
+ } catch (err) {
314
+ console.error(err.message);
315
+ process.exit(1);
316
+ }
317
+ }
318
+
319
+ module.exports = { initRun };
@@ -0,0 +1,81 @@
1
+ ---
2
+ run: {{run_id}}
3
+ work_item: {{work_item_id}}
4
+ intent: {{intent_id}}
5
+ generated: {{generated_at}}
6
+ status: {{status}}
7
+ ---
8
+
9
+ # Test Report: {{title}}
10
+
11
+ ## Summary
12
+
13
+ | Category | Passed | Failed | Skipped | Coverage |
14
+ |----------|--------|--------|---------|----------|
15
+ | Unit | {{unit_passed}} | {{unit_failed}} | {{unit_skipped}} | {{unit_coverage}}% |
16
+ | Integration | {{integration_passed}} | {{integration_failed}} | {{integration_skipped}} | {{integration_coverage}}% |
17
+ | **Total** | {{total_passed}} | {{total_failed}} | {{total_skipped}} | {{total_coverage}}% |
18
+
19
+ ## Acceptance Criteria Validation
20
+
21
+ {{#each acceptance_criteria}}
22
+ - {{#if this.passed}}✅{{else}}❌{{/if}} **{{this.criterion}}** — {{this.status}}
23
+ {{/each}}
24
+
25
+ ## Tests Written
26
+
27
+ ### Unit Tests
28
+
29
+ {{#each unit_tests}}
30
+ - `{{this.file}}` — {{this.description}}
31
+ {{/each}}
32
+
33
+ ### Integration Tests
34
+
35
+ {{#each integration_tests}}
36
+ - `{{this.file}}` — {{this.description}}
37
+ {{/each}}
38
+
39
+ ## Test Commands
40
+
41
+ ```bash
42
+ # Run all tests
43
+ {{test_command}}
44
+
45
+ # Run with coverage
46
+ {{coverage_command}}
47
+ ```
48
+
49
+ ## Coverage Details
50
+
51
+ {{#if coverage_details}}
52
+ | Module | Statements | Branches | Functions | Lines |
53
+ |--------|------------|----------|-----------|-------|
54
+ {{#each coverage_details}}
55
+ | `{{this.module}}` | {{this.statements}}% | {{this.branches}}% | {{this.functions}}% | {{this.lines}}% |
56
+ {{/each}}
57
+ {{else}}
58
+ Coverage details not available.
59
+ {{/if}}
60
+
61
+ ## Issues Found
62
+
63
+ {{#if issues}}
64
+ | Issue | Severity | Status |
65
+ |-------|----------|--------|
66
+ {{#each issues}}
67
+ | {{this.description}} | {{this.severity}} | {{this.status}} |
68
+ {{/each}}
69
+ {{else}}
70
+ No issues found during testing.
71
+ {{/if}}
72
+
73
+ ## Ready for Completion
74
+
75
+ - [{{#if all_tests_pass}}x{{else}} {{/if}}] All tests passing
76
+ - [{{#if coverage_met}}x{{else}} {{/if}}] Coverage target met ({{coverage_target}}%)
77
+ - [{{#if acceptance_met}}x{{else}} {{/if}}] All acceptance criteria validated
78
+ - [{{#if no_critical_issues}}x{{else}} {{/if}}] No critical issues open
79
+
80
+ ---
81
+ *Generated by FIRE Run {{run_id}}*
@@ -104,8 +104,11 @@ Initialize a new FIRE project by detecting workspace type and setting up standar
104
104
  <action>Create .specs-fire/runs/</action>
105
105
  <action>Create .specs-fire/standards/</action>
106
106
  <action>Generate .specs-fire/state.yaml (include autonomy_bias)</action>
107
- <action>Generate .specs-fire/standards/tech-stack.md</action>
108
- <action>Generate .specs-fire/standards/coding-standards.md</action>
107
+ <action>Generate standards using templates:</action>
108
+ <substep>tech-stack.md — templates/tech-stack.md.hbs</substep>
109
+ <substep>coding-standards.md — templates/coding-standards.md.hbs</substep>
110
+ <substep>testing-standards.md — templates/testing-standards.md.hbs</substep>
111
+ <substep>system-architecture.md — templates/system-architecture.md.hbs</substep>
109
112
  </step>
110
113
 
111
114
  <step n="6" title="Complete">
@@ -120,7 +123,9 @@ Initialize a new FIRE project by detecting workspace type and setting up standar
120
123
  ├── runs/
121
124
  └── standards/
122
125
  ├── tech-stack.md
123
- └── coding-standards.md
126
+ ├── coding-standards.md
127
+ ├── testing-standards.md
128
+ └── system-architecture.md
124
129
  ```
125
130
 
126
131
  Ready to capture your first intent.
@@ -136,6 +141,10 @@ Initialize a new FIRE project by detecting workspace type and setting up standar
136
141
 
137
142
  ## Output
138
143
 
139
- - `.specs-fire/state.yaml` Central state file
140
- - `.specs-fire/standards/tech-stack.md` — Technology choices
141
- - `.specs-fire/standards/coding-standards.md` — Coding conventions
144
+ | Artifact | Location | Template |
145
+ |----------|----------|----------|
146
+ | State | `.specs-fire/state.yaml` | |
147
+ | Tech Stack | `.specs-fire/standards/tech-stack.md` | `templates/tech-stack.md.hbs` |
148
+ | Coding Standards | `.specs-fire/standards/coding-standards.md` | `templates/coding-standards.md.hbs` |
149
+ | Testing Standards | `.specs-fire/standards/testing-standards.md` | `templates/testing-standards.md.hbs` |
150
+ | System Architecture | `.specs-fire/standards/system-architecture.md` | `templates/system-architecture.md.hbs` |
@@ -0,0 +1,149 @@
1
+ # Coding Standards
2
+
3
+ ## Overview
4
+
5
+ {{overview}}
6
+
7
+ ## Code Formatting
8
+
9
+ **Tool**: {{formatter}}
10
+ **Config**: {{formatter_config}}
11
+ **Enforcement**: {{formatter_enforcement}}
12
+
13
+ ### Key Settings
14
+
15
+ {{#each formatter_settings}}
16
+ - **{{this.setting}}**: {{this.value}}
17
+ {{/each}}
18
+
19
+ ## Linting
20
+
21
+ **Tool**: {{linter}}
22
+ **Base Config**: {{linter_base}}
23
+ **Strictness**: {{linter_strictness}}
24
+
25
+ ### Key Rules
26
+
27
+ {{#each linter_rules}}
28
+ - `{{this.rule}}`: {{this.setting}} — {{this.rationale}}
29
+ {{/each}}
30
+
31
+ ## Naming Conventions
32
+
33
+ ### Variables and Functions
34
+
35
+ | Element | Convention | Example |
36
+ |---------|------------|---------|
37
+ {{#each naming_conventions}}
38
+ | {{this.element}} | {{this.convention}} | `{{this.example}}` |
39
+ {{/each}}
40
+
41
+ ### Files and Folders
42
+
43
+ {{#each file_naming}}
44
+ - **{{this.type}}**: {{this.convention}} (e.g., `{{this.example}}`)
45
+ {{/each}}
46
+
47
+ ## File Organization
48
+
49
+ ### Project Structure
50
+
51
+ ```
52
+ {{project_structure}}
53
+ ```
54
+
55
+ ### Conventions
56
+
57
+ {{#each organization_conventions}}
58
+ - **{{this.item}}**: {{this.convention}}
59
+ {{/each}}
60
+
61
+ ## Import Order
62
+
63
+ ```{{language}}
64
+ {{import_order_example}}
65
+ ```
66
+
67
+ **Rules**:
68
+ {{#each import_rules}}
69
+ - {{this}}
70
+ {{/each}}
71
+
72
+ ## Error Handling
73
+
74
+ ### Pattern
75
+
76
+ **Approach**: {{error_pattern}}
77
+
78
+ ### Guidelines
79
+
80
+ {{#each error_guidelines}}
81
+ - {{this}}
82
+ {{/each}}
83
+
84
+ ### Example
85
+
86
+ ```{{language}}
87
+ {{error_example}}
88
+ ```
89
+
90
+ ## Logging
91
+
92
+ **Tool**: {{logging_tool}}
93
+ **Format**: {{logging_format}}
94
+
95
+ ### Log Levels
96
+
97
+ | Level | Usage |
98
+ |-------|-------|
99
+ {{#each log_levels}}
100
+ | {{this.level}} | {{this.usage}} |
101
+ {{/each}}
102
+
103
+ ### Guidelines
104
+
105
+ **Always log**:
106
+ {{#each log_always}}
107
+ - {{this}}
108
+ {{/each}}
109
+
110
+ **Never log**:
111
+ {{#each log_never}}
112
+ - {{this}}
113
+ {{/each}}
114
+
115
+ ## Comments and Documentation
116
+
117
+ ### When to Comment
118
+
119
+ {{#each comment_guidelines}}
120
+ - {{this}}
121
+ {{/each}}
122
+
123
+ ### Documentation Format
124
+
125
+ **Functions**: {{doc_format_functions}}
126
+ **Classes**: {{doc_format_classes}}
127
+
128
+ ## Code Patterns
129
+
130
+ ### Preferred Patterns
131
+
132
+ {{#each preferred_patterns}}
133
+ #### {{this.name}}
134
+
135
+ {{this.description}}
136
+
137
+ ```{{../language}}
138
+ {{this.example}}
139
+ ```
140
+ {{/each}}
141
+
142
+ ### Anti-Patterns to Avoid
143
+
144
+ {{#each anti_patterns}}
145
+ - **{{this.name}}**: {{this.why}}
146
+ {{/each}}
147
+
148
+ ---
149
+ *Generated by FIRE project-init*
@@ -0,0 +1,101 @@
1
+ # System Architecture
2
+
3
+ ## Overview
4
+
5
+ {{overview}}
6
+
7
+ ## System Context
8
+
9
+ {{system_context}}
10
+
11
+ ### Context Diagram
12
+
13
+ ```
14
+ {{context_diagram}}
15
+ ```
16
+
17
+ ### Users
18
+
19
+ {{#each users}}
20
+ - **{{this.name}}**: {{this.description}}
21
+ {{/each}}
22
+
23
+ ### External Systems
24
+
25
+ {{#each external_systems}}
26
+ - **{{this.name}}**: {{this.purpose}}
27
+ {{/each}}
28
+
29
+ ## Architecture Pattern
30
+
31
+ **Pattern**: {{architecture_pattern}}
32
+ **Rationale**: {{architecture_rationale}}
33
+
34
+ ## Component Architecture
35
+
36
+ ### Components
37
+
38
+ {{#each components}}
39
+ #### {{this.name}}
40
+
41
+ - **Purpose**: {{this.purpose}}
42
+ - **Responsibilities**: {{this.responsibilities}}
43
+ - **Dependencies**: {{this.dependencies}}
44
+ {{/each}}
45
+
46
+ ### Component Diagram
47
+
48
+ ```
49
+ {{component_diagram}}
50
+ ```
51
+
52
+ ## Data Flow
53
+
54
+ {{data_flow_description}}
55
+
56
+ ```
57
+ {{data_flow_diagram}}
58
+ ```
59
+
60
+ ## Technology Stack
61
+
62
+ | Layer | Technology | Purpose |
63
+ |-------|------------|---------|
64
+ {{#each tech_stack}}
65
+ | {{this.layer}} | {{this.technology}} | {{this.purpose}} |
66
+ {{/each}}
67
+
68
+ ## Non-Functional Requirements
69
+
70
+ ### Performance
71
+
72
+ {{#each nfr_performance}}
73
+ - **{{this.metric}}**: {{this.target}}
74
+ {{/each}}
75
+
76
+ ### Security
77
+
78
+ {{#each nfr_security}}
79
+ - {{this}}
80
+ {{/each}}
81
+
82
+ ### Scalability
83
+
84
+ {{scalability_approach}}
85
+
86
+ ## Constraints
87
+
88
+ {{#each constraints}}
89
+ - {{this}}
90
+ {{/each}}
91
+
92
+ ## Key Decisions
93
+
94
+ | Decision | Choice | Rationale |
95
+ |----------|--------|-----------|
96
+ {{#each key_decisions}}
97
+ | {{this.decision}} | {{this.choice}} | {{this.rationale}} |
98
+ {{/each}}
99
+
100
+ ---
101
+ *Generated by FIRE project-init*