specsmd 0.1.21 → 0.1.22

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.
@@ -21,17 +21,23 @@ Generate implementation walkthrough for human review after run completion.
21
21
  <mandate>ALWAYS generate walkthrough after run completion</mandate>
22
22
  <mandate>Document DECISIONS, not just changes</mandate>
23
23
  <mandate>Include verification steps — how to test this works</mandate>
24
+ <mandate>Track DEVIATIONS from plan — compare to original work item</mandate>
25
+ <mandate>List ALL new dependencies added during implementation</mandate>
26
+ <mandate>Capture developer notes — gotchas save future debugging time</mandate>
24
27
  </llm>
25
28
 
26
29
  <flow>
27
30
  <step n="1" title="Gather Implementation Data">
28
31
  <action>Read run log from .specs-fire/runs/{run-id}/run.md</action>
32
+ <action>Read work item plan from .specs-fire/intents/{intent}/work-items/{work-item}/</action>
29
33
  <action>Collect:</action>
30
34
  <substep>Work item details (id, title, intent)</substep>
31
35
  <substep>Files created during implementation</substep>
32
36
  <substep>Files modified during implementation</substep>
33
37
  <substep>Decisions made during execution</substep>
34
38
  <substep>Tests added and coverage</substep>
39
+ <substep>Dependencies added (new packages in package.json, requirements.txt, etc.)</substep>
40
+ <substep>Deviations from the original work item plan</substep>
35
41
  </step>
36
42
 
37
43
  <step n="2" title="Analyze Implementation">
@@ -39,6 +45,10 @@ Generate implementation walkthrough for human review after run completion.
39
45
  <substep>Identify purpose of the file</substep>
40
46
  <substep>Summarize key changes</substep>
41
47
  <substep>Note patterns or approaches used</substep>
48
+ <action>Document structure overview:</action>
49
+ <substep>How the pieces fit together architecturally</substep>
50
+ <substep>Data flow or component relationships</substep>
51
+ <substep>Keep high-level, NO CODE</substep>
42
52
  </step>
43
53
 
44
54
  <step n="3" title="Document Key Details">
@@ -47,6 +57,14 @@ Generate implementation walkthrough for human review after run completion.
47
57
  <substep>Security considerations (if applicable)</substep>
48
58
  <substep>Performance considerations (if applicable)</substep>
49
59
  <substep>Integration points with existing code</substep>
60
+ <action>Document deviations from plan:</action>
61
+ <substep>Compare implementation to original work item plan</substep>
62
+ <substep>Note any changes and explain WHY they were made</substep>
63
+ <substep>If no deviations, state "None"</substep>
64
+ <action>Capture developer notes:</action>
65
+ <substep>Gotchas or non-obvious behaviors</substep>
66
+ <substep>Tips for future developers</substep>
67
+ <substep>Context that would save debugging time</substep>
50
68
  </step>
51
69
 
52
70
  <step n="4" title="Create Verification Steps">
@@ -83,6 +101,10 @@ Generate implementation walkthrough for human review after run completion.
83
101
 
84
102
  {2-3 sentences describing what was implemented}
85
103
 
104
+ ## Structure Overview
105
+
106
+ {High-level description of how pieces fit together - NO CODE}
107
+
86
108
  ## Files Changed
87
109
 
88
110
  ### Created
@@ -109,6 +131,16 @@ Generate implementation walkthrough for human review after run completion.
109
131
  |----------|--------|-----------|
110
132
  | {decision} | {choice} | {rationale} |
111
133
 
134
+ ## Deviations from Plan
135
+
136
+ {Changes from work item plan and why, or "None"}
137
+
138
+ ## Dependencies Added
139
+
140
+ | Package | Why Needed |
141
+ |---------|------------|
142
+ | `{package}` | {reason} |
143
+
112
144
  ## How to Verify
113
145
 
114
146
  1. **{Step Title}**
@@ -126,6 +158,10 @@ Generate implementation walkthrough for human review after run completion.
126
158
  - Coverage: {percentage}%
127
159
  - Status: {passing/failing}
128
160
 
161
+ ## Developer Notes
162
+
163
+ {Gotchas, tips, or context for future work - keep brief}
164
+
129
165
  ---
130
166
  *Generated by specs.md - fabriqa.ai FIRE Flow Run {run-id}*
131
167
  ```
@@ -134,8 +170,12 @@ Generate implementation walkthrough for human review after run completion.
134
170
 
135
171
  <success_criteria>
136
172
  <criterion>Walkthrough generated with all sections</criterion>
173
+ <criterion>Structure overview explains architecture (NO CODE)</criterion>
137
174
  <criterion>Files changed documented with purposes</criterion>
138
175
  <criterion>Decisions recorded with rationale</criterion>
176
+ <criterion>Deviations from plan documented (or "None")</criterion>
177
+ <criterion>Dependencies added listed with reasons</criterion>
139
178
  <criterion>Verification steps included</criterion>
179
+ <criterion>Developer notes capture gotchas and tips</criterion>
140
180
  <criterion>Saved to run folder</criterion>
141
181
  </success_criteria>
@@ -12,6 +12,10 @@ mode: {{mode}}
12
12
 
13
13
  {{summary}}
14
14
 
15
+ ## Structure Overview
16
+
17
+ {{structure_overview}}
18
+
15
19
  ## Files Changed
16
20
 
17
21
  ### Created
@@ -56,6 +60,25 @@ mode: {{mode}}
56
60
  | (none) | | |
57
61
  {{/unless}}
58
62
 
63
+ ## Deviations from Plan
64
+
65
+ {{#if deviations}}
66
+ {{deviations}}
67
+ {{else}}
68
+ None
69
+ {{/if}}
70
+
71
+ ## Dependencies Added
72
+
73
+ | Package | Why Needed |
74
+ |---------|------------|
75
+ {{#each dependencies_added}}
76
+ | `{{this.package}}` | {{this.reason}} |
77
+ {{/each}}
78
+ {{#unless dependencies_added}}
79
+ | (none) | |
80
+ {{/unless}}
81
+
59
82
  ## How to Verify
60
83
 
61
84
  {{#each verification_steps}}
@@ -73,5 +96,13 @@ mode: {{mode}}
73
96
  - Coverage: {{coverage}}%
74
97
  - Status: {{test_status}}
75
98
 
99
+ ## Developer Notes
100
+
101
+ {{#if developer_notes}}
102
+ {{developer_notes}}
103
+ {{else}}
104
+ (none)
105
+ {{/if}}
106
+
76
107
  ---
77
108
  *Generated by specs.md - fabriqa.ai FIRE Flow Run {{run_id}}*
@@ -31,6 +31,18 @@ Unlike traditional methodologies with 10-26 checkpoints, FIRE uses adaptive chec
31
31
  | **Confirm** | 1 | Medium | Standard features |
32
32
  | **Validate** | 2 | High | Security, payments, architecture |
33
33
 
34
+ ## Run Scope
35
+
36
+ Run scope determines how many work items execute in a single run:
37
+
38
+ | Scope | Description | Grouping |
39
+ |-------|-------------|----------|
40
+ | **Single** | One work item per run, most controlled | Each item in its own run |
41
+ | **Batch** | Group items by mode, respect dependencies | Autopilot together, confirm together |
42
+ | **Wide** | Maximum items per run, minimal interruption | All compatible items together |
43
+
44
+ The system learns your preference from run history and stores it in `workspace.run_scope_preference`.
45
+
34
46
  ## Project Structure
35
47
 
36
48
  When initialized, FIRE creates:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "specsmd",
3
- "version": "0.1.21",
3
+ "version": "0.1.22",
4
4
  "description": "Multi-agent orchestration system for AI-native software development. Delivers AI-DLC, Agile, and custom SDLC flows as markdown-based agent systems.",
5
5
  "main": "lib/installer.js",
6
6
  "bin": {
@@ -1,755 +0,0 @@
1
- /**
2
- * Render walkthrough from run data
3
- *
4
- * Generates walkthrough.md from run log and implementation data
5
- * with comprehensive validation and human/LLM-readable error messages.
6
- */
7
-
8
- import { writeFileSync, existsSync, statSync } from 'fs';
9
- import { join } from 'path';
10
-
11
- // ============================================================================
12
- // Types
13
- // ============================================================================
14
-
15
- export interface WalkthroughData {
16
- runId: string;
17
- workItemId: string;
18
- workItemTitle: string;
19
- intentId: string;
20
- mode: string;
21
- summary: string;
22
- filesCreated: Array<{ path: string; purpose: string }>;
23
- filesModified: Array<{ path: string; changes: string }>;
24
- implementationDetails: Array<{ title: string; content: string }>;
25
- decisions: Array<{ decision: string; choice: string; rationale: string }>;
26
- verificationSteps: Array<{
27
- title: string;
28
- command?: string;
29
- description: string;
30
- expected?: string;
31
- }>;
32
- testsAdded: number;
33
- coverage: number;
34
- testStatus: string;
35
- }
36
-
37
- export interface RenderWalkthroughResult {
38
- success: boolean;
39
- walkthroughPath: string;
40
- warnings: string[];
41
- }
42
-
43
- // ============================================================================
44
- // Error Handling Utilities
45
- // ============================================================================
46
-
47
- /**
48
- * Creates a standardized FIRE error with context, issue, and guidance.
49
- *
50
- * Format: "FIRE Error: {context} - {issue}. {guidance}"
51
- *
52
- * This format is designed to be:
53
- * - Human readable: Clear what went wrong
54
- * - LLM readable: Structured for AI agents to parse and act on
55
- * - Actionable: Includes guidance on how to fix the issue
56
- */
57
- function createFIREError(context: string, issue: string, guidance: string): Error {
58
- const message = `FIRE Error: ${context} - ${issue}. ${guidance}`;
59
- const error = new Error(message);
60
- error.name = 'FIREError';
61
- return error;
62
- }
63
-
64
- /**
65
- * Maps Node.js filesystem error codes to human-readable messages.
66
- */
67
- function mapNodeErrorToMessage(err: NodeJS.ErrnoException): string {
68
- const errorMap: Record<string, string> = {
69
- ENOENT: 'File or directory does not exist',
70
- EACCES: 'Permission denied - check file/folder permissions',
71
- ENOSPC: 'Disk is full - free up space and try again',
72
- EROFS: 'Read-only file system - cannot write to this location',
73
- ENAMETOOLONG: 'File path is too long - use a shorter path',
74
- EMFILE: 'Too many open files - close some files and try again',
75
- EEXIST: 'File already exists',
76
- EISDIR: 'Expected a file but found a directory',
77
- ENOTDIR: 'Expected a directory but found a file',
78
- };
79
-
80
- return errorMap[err.code ?? ''] ?? `System error: ${err.message}`;
81
- }
82
-
83
- // ============================================================================
84
- // Validation Utilities
85
- // ============================================================================
86
-
87
- /**
88
- * Validates that a value is a non-empty string.
89
- * Returns the trimmed string or throws a clear error.
90
- */
91
- function validateRequiredString(
92
- value: unknown,
93
- fieldName: string,
94
- context: string
95
- ): string {
96
- if (value === undefined || value === null) {
97
- throw createFIREError(
98
- context,
99
- `required field '${fieldName}' is missing`,
100
- `Ensure '${fieldName}' is provided in the walkthrough data.`
101
- );
102
- }
103
-
104
- if (typeof value !== 'string') {
105
- throw createFIREError(
106
- context,
107
- `field '${fieldName}' must be a string, but got ${typeof value}`,
108
- `Ensure '${fieldName}' is a string value, not ${typeof value}.`
109
- );
110
- }
111
-
112
- const trimmed = value.trim();
113
- if (trimmed.length === 0) {
114
- throw createFIREError(
115
- context,
116
- `required field '${fieldName}' is empty`,
117
- `Ensure '${fieldName}' contains a non-empty value.`
118
- );
119
- }
120
-
121
- return trimmed;
122
- }
123
-
124
- /**
125
- * Validates that a value is an array.
126
- * Returns the array or an empty array if null/undefined, with a warning.
127
- */
128
- function validateArray<T>(
129
- value: unknown,
130
- fieldName: string,
131
- context: string,
132
- warnings: string[]
133
- ): T[] {
134
- if (value === undefined || value === null) {
135
- warnings.push(
136
- `Warning: '${fieldName}' was not provided, defaulting to empty array.`
137
- );
138
- return [];
139
- }
140
-
141
- if (!Array.isArray(value)) {
142
- throw createFIREError(
143
- context,
144
- `field '${fieldName}' must be an array, but got ${typeof value}`,
145
- `Ensure '${fieldName}' is an array. Current value type: ${typeof value}.`
146
- );
147
- }
148
-
149
- return value as T[];
150
- }
151
-
152
- /**
153
- * Validates and coerces a number with bounds checking.
154
- * Returns the number or a default if invalid.
155
- */
156
- function validateNumber(
157
- value: unknown,
158
- fieldName: string,
159
- defaultValue: number,
160
- warnings: string[],
161
- min?: number,
162
- max?: number
163
- ): number {
164
- if (value === undefined || value === null) {
165
- warnings.push(
166
- `Warning: '${fieldName}' was not provided, defaulting to ${defaultValue}.`
167
- );
168
- return defaultValue;
169
- }
170
-
171
- const num = typeof value === 'number' ? value : parseFloat(String(value));
172
-
173
- if (isNaN(num)) {
174
- warnings.push(
175
- `Warning: '${fieldName}' value '${value}' is not a valid number, defaulting to ${defaultValue}.`
176
- );
177
- return defaultValue;
178
- }
179
-
180
- if (min !== undefined && num < min) {
181
- warnings.push(
182
- `Warning: '${fieldName}' value ${num} is below minimum ${min}, clamping to ${min}.`
183
- );
184
- return min;
185
- }
186
-
187
- if (max !== undefined && num > max) {
188
- warnings.push(
189
- `Warning: '${fieldName}' value ${num} exceeds maximum ${max}, clamping to ${max}.`
190
- );
191
- return max;
192
- }
193
-
194
- return num;
195
- }
196
-
197
- /**
198
- * Validates that a path exists and is a directory.
199
- */
200
- function validateDirectoryExists(
201
- path: string,
202
- pathDescription: string,
203
- context: string,
204
- guidance: string
205
- ): void {
206
- if (!existsSync(path)) {
207
- throw createFIREError(
208
- context,
209
- `${pathDescription} not found at '${path}'`,
210
- guidance
211
- );
212
- }
213
-
214
- try {
215
- const stat = statSync(path);
216
- if (!stat.isDirectory()) {
217
- throw createFIREError(
218
- context,
219
- `${pathDescription} exists but is not a directory at '${path}'`,
220
- `Expected a directory but found a file. ${guidance}`
221
- );
222
- }
223
- } catch (err) {
224
- if ((err as Error).name === 'FIREError') {
225
- throw err;
226
- }
227
- const nodeErr = err as NodeJS.ErrnoException;
228
- throw createFIREError(
229
- context,
230
- `cannot access ${pathDescription} at '${path}'`,
231
- mapNodeErrorToMessage(nodeErr)
232
- );
233
- }
234
- }
235
-
236
- // ============================================================================
237
- // Markdown Utilities
238
- // ============================================================================
239
-
240
- /**
241
- * Escapes special characters in markdown table cells.
242
- * Handles pipes, newlines, and ensures content doesn't break table formatting.
243
- */
244
- function escapeTableCell(content: string | undefined | null): string {
245
- if (content === undefined || content === null) {
246
- return '';
247
- }
248
-
249
- return String(content)
250
- .replace(/\|/g, '\\|') // Escape pipe characters
251
- .replace(/\n/g, ' ') // Replace newlines with spaces
252
- .replace(/\r/g, '') // Remove carriage returns
253
- .trim();
254
- }
255
-
256
- /**
257
- * Escapes backticks in inline code spans.
258
- */
259
- function escapeInlineCode(content: string | undefined | null): string {
260
- if (content === undefined || content === null) {
261
- return '';
262
- }
263
-
264
- // If content contains backticks, we need to use double backticks
265
- const str = String(content);
266
- if (str.includes('`')) {
267
- return str.replace(/`/g, '\\`');
268
- }
269
- return str;
270
- }
271
-
272
- // ============================================================================
273
- // Array Item Validators
274
- // ============================================================================
275
-
276
- interface FileEntry {
277
- path: string;
278
- purpose?: string;
279
- changes?: string;
280
- }
281
-
282
- interface DecisionEntry {
283
- decision: string;
284
- choice: string;
285
- rationale: string;
286
- }
287
-
288
- interface ImplementationDetail {
289
- title: string;
290
- content: string;
291
- }
292
-
293
- interface VerificationStep {
294
- title: string;
295
- command?: string;
296
- description: string;
297
- expected?: string;
298
- }
299
-
300
- function validateFileEntry(
301
- entry: unknown,
302
- index: number,
303
- fieldName: string,
304
- warnings: string[]
305
- ): FileEntry {
306
- if (!entry || typeof entry !== 'object') {
307
- warnings.push(
308
- `Warning: Invalid entry at index ${index} in '${fieldName}' - expected object, got ${typeof entry}. Skipping.`
309
- );
310
- return { path: '(invalid entry)' };
311
- }
312
-
313
- const obj = entry as Record<string, unknown>;
314
- const path =
315
- typeof obj.path === 'string' && obj.path.trim()
316
- ? obj.path.trim()
317
- : '(missing path)';
318
-
319
- if (path === '(missing path)') {
320
- warnings.push(
321
- `Warning: Entry at index ${index} in '${fieldName}' is missing 'path' property.`
322
- );
323
- }
324
-
325
- return {
326
- path,
327
- purpose: typeof obj.purpose === 'string' ? obj.purpose : undefined,
328
- changes: typeof obj.changes === 'string' ? obj.changes : undefined,
329
- };
330
- }
331
-
332
- function validateDecisionEntry(
333
- entry: unknown,
334
- index: number,
335
- warnings: string[]
336
- ): DecisionEntry {
337
- if (!entry || typeof entry !== 'object') {
338
- warnings.push(
339
- `Warning: Invalid decision at index ${index} - expected object, got ${typeof entry}. Using placeholder.`
340
- );
341
- return {
342
- decision: '(invalid)',
343
- choice: '(invalid)',
344
- rationale: '(invalid)',
345
- };
346
- }
347
-
348
- const obj = entry as Record<string, unknown>;
349
- return {
350
- decision:
351
- typeof obj.decision === 'string' ? obj.decision : '(missing decision)',
352
- choice: typeof obj.choice === 'string' ? obj.choice : '(missing choice)',
353
- rationale:
354
- typeof obj.rationale === 'string' ? obj.rationale : '(missing rationale)',
355
- };
356
- }
357
-
358
- function validateImplementationDetail(
359
- entry: unknown,
360
- index: number,
361
- warnings: string[]
362
- ): ImplementationDetail {
363
- if (!entry || typeof entry !== 'object') {
364
- warnings.push(
365
- `Warning: Invalid implementation detail at index ${index} - expected object. Using placeholder.`
366
- );
367
- return { title: '(invalid)', content: '(invalid)' };
368
- }
369
-
370
- const obj = entry as Record<string, unknown>;
371
- return {
372
- title: typeof obj.title === 'string' ? obj.title : `Detail ${index + 1}`,
373
- content:
374
- typeof obj.content === 'string' ? obj.content : '(no content provided)',
375
- };
376
- }
377
-
378
- function validateVerificationStep(
379
- entry: unknown,
380
- index: number,
381
- warnings: string[]
382
- ): VerificationStep {
383
- if (!entry || typeof entry !== 'object') {
384
- warnings.push(
385
- `Warning: Invalid verification step at index ${index} - expected object. Using placeholder.`
386
- );
387
- return { title: '(invalid)', description: '(invalid)' };
388
- }
389
-
390
- const obj = entry as Record<string, unknown>;
391
- return {
392
- title: typeof obj.title === 'string' ? obj.title : `Step ${index + 1}`,
393
- command: typeof obj.command === 'string' ? obj.command : undefined,
394
- description:
395
- typeof obj.description === 'string'
396
- ? obj.description
397
- : '(no description)',
398
- expected: typeof obj.expected === 'string' ? obj.expected : undefined,
399
- };
400
- }
401
-
402
- // ============================================================================
403
- // Main Function
404
- // ============================================================================
405
-
406
- /**
407
- * Renders a walkthrough document from run data.
408
- *
409
- * @param rootPath - The project root path containing .specs-fire directory
410
- * @param data - The walkthrough data to render
411
- * @returns Result object with success status, file path, and any warnings
412
- *
413
- * @throws {Error} FIREError with clear message if validation fails or write fails
414
- *
415
- * @example
416
- * ```typescript
417
- * const result = renderWalkthrough('/path/to/project', {
418
- * runId: 'run-001',
419
- * workItemId: 'WI-001',
420
- * // ... other fields
421
- * });
422
- *
423
- * if (result.warnings.length > 0) {
424
- * console.log('Warnings:', result.warnings);
425
- * }
426
- * console.log('Walkthrough written to:', result.walkthroughPath);
427
- * ```
428
- */
429
- export function renderWalkthrough(
430
- rootPath: string,
431
- data: WalkthroughData
432
- ): RenderWalkthroughResult {
433
- const context = 'Cannot generate walkthrough';
434
- const warnings: string[] = [];
435
-
436
- // =========================================================================
437
- // Input Validation: rootPath
438
- // =========================================================================
439
-
440
- if (rootPath === undefined || rootPath === null) {
441
- throw createFIREError(
442
- context,
443
- "rootPath parameter is required but was not provided",
444
- "Call renderWalkthrough with a valid project root path as the first argument."
445
- );
446
- }
447
-
448
- if (typeof rootPath !== 'string') {
449
- throw createFIREError(
450
- context,
451
- `rootPath must be a string, but got ${typeof rootPath}`,
452
- "Ensure rootPath is a string containing the absolute path to the project root."
453
- );
454
- }
455
-
456
- const trimmedRootPath = rootPath.trim();
457
- if (trimmedRootPath.length === 0) {
458
- throw createFIREError(
459
- context,
460
- "rootPath is an empty string",
461
- "Provide a non-empty path to the project root directory."
462
- );
463
- }
464
-
465
- // Validate rootPath exists
466
- validateDirectoryExists(
467
- trimmedRootPath,
468
- 'project root',
469
- context,
470
- "Ensure the project root path exists and is accessible."
471
- );
472
-
473
- // =========================================================================
474
- // Input Validation: data object
475
- // =========================================================================
476
-
477
- if (data === undefined || data === null) {
478
- throw createFIREError(
479
- context,
480
- "data parameter is required but was not provided",
481
- "Call renderWalkthrough with walkthrough data as the second argument."
482
- );
483
- }
484
-
485
- if (typeof data !== 'object' || Array.isArray(data)) {
486
- throw createFIREError(
487
- context,
488
- `data must be an object, but got ${Array.isArray(data) ? 'array' : typeof data}`,
489
- "Ensure data is a WalkthroughData object with required fields: runId, workItemId, workItemTitle, intentId, mode, summary."
490
- );
491
- }
492
-
493
- // =========================================================================
494
- // Validate Required String Fields
495
- // =========================================================================
496
-
497
- const runId = validateRequiredString(data.runId, 'runId', context);
498
- const workItemId = validateRequiredString(data.workItemId, 'workItemId', context);
499
- const workItemTitle = validateRequiredString(data.workItemTitle, 'workItemTitle', context);
500
- const intentId = validateRequiredString(data.intentId, 'intentId', context);
501
- const mode = validateRequiredString(data.mode, 'mode', context);
502
- const summary = validateRequiredString(data.summary, 'summary', context);
503
-
504
- // testStatus can have a default
505
- let testStatus: string;
506
- if (
507
- data.testStatus === undefined ||
508
- data.testStatus === null ||
509
- (typeof data.testStatus === 'string' && data.testStatus.trim() === '')
510
- ) {
511
- warnings.push("Warning: 'testStatus' was not provided, defaulting to 'unknown'.");
512
- testStatus = 'unknown';
513
- } else {
514
- testStatus = String(data.testStatus);
515
- }
516
-
517
- // =========================================================================
518
- // Validate Array Fields
519
- // =========================================================================
520
-
521
- const rawFilesCreated = validateArray<unknown>(
522
- data.filesCreated,
523
- 'filesCreated',
524
- context,
525
- warnings
526
- );
527
- const rawFilesModified = validateArray<unknown>(
528
- data.filesModified,
529
- 'filesModified',
530
- context,
531
- warnings
532
- );
533
- const rawImplementationDetails = validateArray<unknown>(
534
- data.implementationDetails,
535
- 'implementationDetails',
536
- context,
537
- warnings
538
- );
539
- const rawDecisions = validateArray<unknown>(
540
- data.decisions,
541
- 'decisions',
542
- context,
543
- warnings
544
- );
545
- const rawVerificationSteps = validateArray<unknown>(
546
- data.verificationSteps,
547
- 'verificationSteps',
548
- context,
549
- warnings
550
- );
551
-
552
- // =========================================================================
553
- // Validate Numeric Fields
554
- // =========================================================================
555
-
556
- const testsAdded = validateNumber(data.testsAdded, 'testsAdded', 0, warnings, 0);
557
- const coverage = validateNumber(data.coverage, 'coverage', 0, warnings, 0, 100);
558
-
559
- // =========================================================================
560
- // Validate Run Folder Exists
561
- // =========================================================================
562
-
563
- const specsFirePath = join(trimmedRootPath, '.specs-fire');
564
- validateDirectoryExists(
565
- specsFirePath,
566
- '.specs-fire directory',
567
- context,
568
- "Initialize FIRE in this project first. The .specs-fire directory should exist at the project root."
569
- );
570
-
571
- const runsPath = join(specsFirePath, 'runs');
572
- validateDirectoryExists(
573
- runsPath,
574
- 'runs directory',
575
- context,
576
- "The runs directory should exist within .specs-fire. This is created when FIRE is initialized."
577
- );
578
-
579
- const runPath = join(runsPath, runId);
580
- validateDirectoryExists(
581
- runPath,
582
- `run folder for '${runId}'`,
583
- context,
584
- `Ensure the run '${runId}' was initialized with 'run-start' before generating walkthrough. The run folder should exist at: ${runPath}`
585
- );
586
-
587
- const walkthroughPath = join(runPath, 'walkthrough.md');
588
-
589
- // =========================================================================
590
- // Process and Validate Array Items
591
- // =========================================================================
592
-
593
- const filesCreated = rawFilesCreated.map((entry, i) =>
594
- validateFileEntry(entry, i, 'filesCreated', warnings)
595
- );
596
-
597
- const filesModified = rawFilesModified.map((entry, i) =>
598
- validateFileEntry(entry, i, 'filesModified', warnings)
599
- );
600
-
601
- const implementationDetails = rawImplementationDetails.map((entry, i) =>
602
- validateImplementationDetail(entry, i, warnings)
603
- );
604
-
605
- const decisions = rawDecisions.map((entry, i) =>
606
- validateDecisionEntry(entry, i, warnings)
607
- );
608
-
609
- const verificationSteps = rawVerificationSteps.map((entry, i) =>
610
- validateVerificationStep(entry, i, warnings)
611
- );
612
-
613
- // =========================================================================
614
- // Build Markdown Content
615
- // =========================================================================
616
-
617
- const timestamp = new Date().toISOString();
618
-
619
- // Build files created table
620
- const filesCreatedTable =
621
- filesCreated.length > 0
622
- ? filesCreated
623
- .map(
624
- (f) =>
625
- `| \`${escapeInlineCode(f.path)}\` | ${escapeTableCell(f.purpose ?? '')} |`
626
- )
627
- .join('\n')
628
- : '| (none) | |';
629
-
630
- // Build files modified table
631
- const filesModifiedTable =
632
- filesModified.length > 0
633
- ? filesModified
634
- .map(
635
- (f) =>
636
- `| \`${escapeInlineCode(f.path)}\` | ${escapeTableCell(f.changes ?? '')} |`
637
- )
638
- .join('\n')
639
- : '| (none) | |';
640
-
641
- // Build implementation details sections
642
- const implementationDetailsSection =
643
- implementationDetails.length > 0
644
- ? implementationDetails
645
- .map((d, i) => `### ${i + 1}. ${escapeTableCell(d.title)}\n\n${d.content}`)
646
- .join('\n\n')
647
- : '(No implementation details provided)';
648
-
649
- // Build decisions table
650
- const decisionsTable =
651
- decisions.length > 0
652
- ? decisions
653
- .map(
654
- (d) =>
655
- `| ${escapeTableCell(d.decision)} | ${escapeTableCell(d.choice)} | ${escapeTableCell(d.rationale)} |`
656
- )
657
- .join('\n')
658
- : '| (none) | | |';
659
-
660
- // Build verification steps
661
- const verificationStepsSection =
662
- verificationSteps.length > 0
663
- ? verificationSteps
664
- .map((s, i) => {
665
- let step = `${i + 1}. **${escapeTableCell(s.title)}**\n`;
666
- if (s.command) {
667
- step += ` \`\`\`bash\n ${s.command}\n \`\`\`\n`;
668
- }
669
- step += ` ${s.description}\n`;
670
- if (s.expected) {
671
- step += ` Expected: ${s.expected}\n`;
672
- }
673
- return step;
674
- })
675
- .join('\n')
676
- : '(No verification steps provided)';
677
-
678
- // Assemble the full walkthrough document
679
- const walkthrough = `---
680
- run: ${runId}
681
- work_item: ${workItemId}
682
- intent: ${intentId}
683
- generated: ${timestamp}
684
- mode: ${mode}
685
- ---
686
-
687
- # Implementation Walkthrough: ${workItemTitle}
688
-
689
- ## Summary
690
-
691
- ${summary}
692
-
693
- ## Files Changed
694
-
695
- ### Created
696
-
697
- | File | Purpose |
698
- |------|---------|
699
- ${filesCreatedTable}
700
-
701
- ### Modified
702
-
703
- | File | Changes |
704
- |------|---------|
705
- ${filesModifiedTable}
706
-
707
- ## Key Implementation Details
708
-
709
- ${implementationDetailsSection}
710
-
711
- ## Decisions Made
712
-
713
- | Decision | Choice | Rationale |
714
- |----------|--------|-----------|
715
- ${decisionsTable}
716
-
717
- ## How to Verify
718
-
719
- ${verificationStepsSection}
720
-
721
- ## Test Coverage
722
-
723
- - Tests added: ${testsAdded}
724
- - Coverage: ${coverage}%
725
- - Status: ${testStatus}
726
-
727
- ---
728
- *Generated by specs.md - fabriqa.ai FIRE Flow Run ${runId}*
729
- `;
730
-
731
- // =========================================================================
732
- // Write File with Error Handling
733
- // =========================================================================
734
-
735
- try {
736
- writeFileSync(walkthroughPath, walkthrough, 'utf8');
737
- } catch (err) {
738
- const nodeErr = err as NodeJS.ErrnoException;
739
- throw createFIREError(
740
- 'Failed to write walkthrough',
741
- `could not write to '${walkthroughPath}'`,
742
- `${mapNodeErrorToMessage(nodeErr)} Verify you have write permissions and sufficient disk space.`
743
- );
744
- }
745
-
746
- // =========================================================================
747
- // Return Success Result
748
- // =========================================================================
749
-
750
- return {
751
- success: true,
752
- walkthroughPath,
753
- warnings,
754
- };
755
- }