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.
- package/flows/fire/agents/builder/skills/walkthrough-generate/SKILL.md +40 -0
- package/flows/fire/agents/builder/skills/walkthrough-generate/templates/walkthrough.md.hbs +31 -0
- package/flows/fire/quick-start.md +12 -0
- package/package.json +1 -1
- package/flows/fire/agents/builder/skills/walkthrough-generate/scripts/render-walkthrough.ts +0 -755
|
@@ -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.
|
|
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
|
-
}
|