specsmd 0.0.0-dev.6 → 0.0.0-dev.60

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.
Files changed (73) hide show
  1. package/README.md +10 -2
  2. package/flows/aidlc/commands/construction-agent.md +5 -1
  3. package/flows/aidlc/commands/inception-agent.md +4 -0
  4. package/flows/aidlc/commands/master-agent.md +4 -0
  5. package/flows/aidlc/commands/operations-agent.md +4 -0
  6. package/flows/aidlc/memory-bank.yaml +2 -1
  7. package/{scripts → flows/aidlc/scripts}/artifact-validator.js +3 -3
  8. package/{scripts → flows/aidlc/scripts}/bolt-complete.js +35 -4
  9. package/{scripts → flows/aidlc/scripts}/status-integrity.js +4 -4
  10. package/flows/aidlc/skills/construction/bolt-list.md +1 -1
  11. package/flows/aidlc/skills/construction/bolt-start.md +2 -2
  12. package/flows/aidlc/skills/construction/bolt-status.md +1 -1
  13. package/flows/aidlc/skills/construction/prototype-apply.md +305 -0
  14. package/flows/aidlc/skills/inception/bolt-plan.md +15 -2
  15. package/flows/aidlc/skills/inception/vibe-to-spec.md +406 -0
  16. package/flows/aidlc/skills/master/analyze-context.md +1 -1
  17. package/flows/aidlc/templates/construction/bolt-template.md +22 -1
  18. package/flows/aidlc/templates/construction/bolt-types/ddd-construction-bolt.md +73 -11
  19. package/flows/aidlc/templates/construction/bolt-types/simple-construction-bolt.md +5 -0
  20. package/flows/aidlc/templates/standards/decision-index-template.md +32 -0
  21. package/flows/fire/README.md +19 -0
  22. package/flows/fire/agents/builder/agent.md +272 -0
  23. package/flows/fire/agents/builder/skills/run-execute/SKILL.md +455 -0
  24. package/flows/fire/agents/builder/skills/run-execute/scripts/complete-run.js +549 -0
  25. package/flows/fire/agents/builder/skills/run-execute/scripts/init-run.js +454 -0
  26. package/flows/fire/agents/builder/skills/run-execute/templates/plan.md.hbs +61 -0
  27. package/flows/fire/agents/builder/skills/run-execute/templates/test-report.md.hbs +81 -0
  28. package/flows/fire/agents/builder/skills/run-plan/SKILL.md +376 -0
  29. package/flows/fire/agents/builder/skills/run-status/SKILL.md +94 -0
  30. package/flows/fire/agents/builder/skills/walkthrough-generate/SKILL.md +140 -0
  31. package/flows/fire/agents/builder/skills/walkthrough-generate/scripts/render-walkthrough.ts +755 -0
  32. package/flows/fire/agents/builder/skills/walkthrough-generate/templates/walkthrough.md.hbs +77 -0
  33. package/flows/fire/agents/orchestrator/agent.md +113 -0
  34. package/flows/fire/agents/orchestrator/skills/project-init/SKILL.md +150 -0
  35. package/flows/fire/agents/orchestrator/skills/project-init/templates/coding-standards.md.hbs +149 -0
  36. package/flows/fire/agents/orchestrator/skills/project-init/templates/system-architecture.md.hbs +101 -0
  37. package/flows/fire/agents/orchestrator/skills/project-init/templates/tech-stack.md.hbs +136 -0
  38. package/flows/fire/agents/orchestrator/skills/project-init/templates/testing-standards.md.hbs +94 -0
  39. package/flows/fire/agents/orchestrator/skills/route/SKILL.md +123 -0
  40. package/flows/fire/agents/orchestrator/skills/status/SKILL.md +99 -0
  41. package/flows/fire/agents/planner/agent.md +122 -0
  42. package/flows/fire/agents/planner/skills/design-doc-generate/SKILL.md +213 -0
  43. package/flows/fire/agents/planner/skills/design-doc-generate/templates/design.md.hbs +76 -0
  44. package/flows/fire/agents/planner/skills/intent-capture/SKILL.md +155 -0
  45. package/flows/fire/agents/planner/skills/intent-capture/templates/brief.md.hbs +40 -0
  46. package/flows/fire/agents/planner/skills/work-item-decompose/SKILL.md +194 -0
  47. package/flows/fire/agents/planner/skills/work-item-decompose/templates/work-item.md.hbs +40 -0
  48. package/flows/fire/commands/fire-builder.md +56 -0
  49. package/flows/fire/commands/fire-planner.md +48 -0
  50. package/flows/fire/commands/fire.md +46 -0
  51. package/flows/fire/memory-bank.yaml +164 -0
  52. package/flows/fire/quick-start.md +130 -0
  53. package/flows/simple/README.md +190 -0
  54. package/flows/simple/agents/agent.md +404 -0
  55. package/flows/simple/commands/agent.md +60 -0
  56. package/flows/simple/context-config.yaml +34 -0
  57. package/flows/simple/memory-bank.yaml +66 -0
  58. package/flows/simple/quick-start.md +231 -0
  59. package/flows/simple/skills/design.md +96 -0
  60. package/flows/simple/skills/execute.md +190 -0
  61. package/flows/simple/skills/requirements.md +94 -0
  62. package/flows/simple/skills/tasks.md +136 -0
  63. package/flows/simple/templates/design-template.md +138 -0
  64. package/flows/simple/templates/requirements-template.md +85 -0
  65. package/flows/simple/templates/tasks-template.md +104 -0
  66. package/lib/analytics/tracker.js +6 -2
  67. package/lib/constants.js +17 -8
  68. package/lib/installer.js +5 -15
  69. package/lib/installers/KiroInstaller.js +55 -0
  70. package/lib/installers/OpenCodeInstaller.js +9 -1
  71. package/lib/installers/ToolInstaller.js +4 -1
  72. package/lib/installers/WindsurfInstaller.js +0 -54
  73. package/package.json +3 -52
@@ -0,0 +1,755 @@
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 FIRE 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
+ }