@ydtb/specsmd 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/README.md +322 -0
- package/bin/cli.js +21 -0
- package/flows/aidlc/README.md +372 -0
- package/flows/aidlc/agents/construction-agent.md +80 -0
- package/flows/aidlc/agents/inception-agent.md +97 -0
- package/flows/aidlc/agents/master-agent.md +61 -0
- package/flows/aidlc/agents/operations-agent.md +89 -0
- package/flows/aidlc/commands/construction-agent.md +67 -0
- package/flows/aidlc/commands/inception-agent.md +59 -0
- package/flows/aidlc/commands/master-agent.md +51 -0
- package/flows/aidlc/commands/operations-agent.md +81 -0
- package/flows/aidlc/context-config.yaml +67 -0
- package/flows/aidlc/memory-bank.yaml +105 -0
- package/flows/aidlc/quick-start.md +322 -0
- package/flows/aidlc/scripts/artifact-validator.cjs +594 -0
- package/flows/aidlc/scripts/bolt-complete.cjs +606 -0
- package/flows/aidlc/scripts/status-integrity.cjs +598 -0
- package/flows/aidlc/skills/construction/bolt-list.md +163 -0
- package/flows/aidlc/skills/construction/bolt-replan.md +345 -0
- package/flows/aidlc/skills/construction/bolt-start.md +442 -0
- package/flows/aidlc/skills/construction/bolt-status.md +185 -0
- package/flows/aidlc/skills/construction/navigator.md +196 -0
- package/flows/aidlc/skills/construction/prototype-apply.md +311 -0
- package/flows/aidlc/skills/inception/bolt-plan.md +372 -0
- package/flows/aidlc/skills/inception/context.md +171 -0
- package/flows/aidlc/skills/inception/intent-create.md +211 -0
- package/flows/aidlc/skills/inception/intent-list.md +124 -0
- package/flows/aidlc/skills/inception/navigator.md +207 -0
- package/flows/aidlc/skills/inception/requirements.md +227 -0
- package/flows/aidlc/skills/inception/review.md +248 -0
- package/flows/aidlc/skills/inception/story-create.md +304 -0
- package/flows/aidlc/skills/inception/units.md +278 -0
- package/flows/aidlc/skills/inception/vibe-to-spec.md +410 -0
- package/flows/aidlc/skills/master/analyze-context.md +239 -0
- package/flows/aidlc/skills/master/answer-question.md +141 -0
- package/flows/aidlc/skills/master/explain-flow.md +158 -0
- package/flows/aidlc/skills/master/project-init.md +281 -0
- package/flows/aidlc/skills/master/route-request.md +126 -0
- package/flows/aidlc/skills/operations/build.md +237 -0
- package/flows/aidlc/skills/operations/deploy.md +259 -0
- package/flows/aidlc/skills/operations/monitor.md +265 -0
- package/flows/aidlc/skills/operations/navigator.md +209 -0
- package/flows/aidlc/skills/operations/verify.md +224 -0
- package/flows/aidlc/templates/construction/bolt-template.md +226 -0
- package/flows/aidlc/templates/construction/bolt-types/ddd-construction-bolt/adr-template.md +49 -0
- package/flows/aidlc/templates/construction/bolt-types/ddd-construction-bolt/ddd-01-domain-model-template.md +55 -0
- package/flows/aidlc/templates/construction/bolt-types/ddd-construction-bolt/ddd-02-technical-design-template.md +67 -0
- package/flows/aidlc/templates/construction/bolt-types/ddd-construction-bolt/ddd-03-test-report-template.md +62 -0
- package/flows/aidlc/templates/construction/bolt-types/ddd-construction-bolt.md +590 -0
- package/flows/aidlc/templates/construction/bolt-types/simple-construction-bolt.md +347 -0
- package/flows/aidlc/templates/construction/bolt-types/spike-bolt.md +240 -0
- package/flows/aidlc/templates/construction/construction-log-template.md +129 -0
- package/flows/aidlc/templates/construction/standards/coding-standards.md +29 -0
- package/flows/aidlc/templates/construction/standards/system-architecture.md +22 -0
- package/flows/aidlc/templates/construction/standards/tech-stack.md +19 -0
- package/flows/aidlc/templates/inception/inception-log-template.md +134 -0
- package/flows/aidlc/templates/inception/project/README.md +55 -0
- package/flows/aidlc/templates/inception/requirements-template.md +144 -0
- package/flows/aidlc/templates/inception/stories-template.md +38 -0
- package/flows/aidlc/templates/inception/story-template.md +147 -0
- package/flows/aidlc/templates/inception/system-context-template.md +29 -0
- package/flows/aidlc/templates/inception/unit-brief-template.md +177 -0
- package/flows/aidlc/templates/inception/units-template.md +52 -0
- package/flows/aidlc/templates/standards/catalog.yaml +345 -0
- package/flows/aidlc/templates/standards/coding-standards.guide.md +553 -0
- package/flows/aidlc/templates/standards/data-stack.guide.md +162 -0
- package/flows/aidlc/templates/standards/decision-index-template.md +32 -0
- package/flows/aidlc/templates/standards/tech-stack.guide.md +280 -0
- package/flows/fire/README.md +19 -0
- package/flows/fire/agents/builder/agent.md +254 -0
- package/flows/fire/agents/builder/skills/code-review/SKILL.md +257 -0
- package/flows/fire/agents/builder/skills/code-review/references/auto-fix-rules.md +218 -0
- package/flows/fire/agents/builder/skills/code-review/references/review-categories.md +154 -0
- package/flows/fire/agents/builder/skills/code-review/templates/review-report.md.hbs +120 -0
- package/flows/fire/agents/builder/skills/commit-changes/SKILL.md +232 -0
- package/flows/fire/agents/builder/skills/commit-changes/scripts/commit-changes.cjs +447 -0
- package/flows/fire/agents/builder/skills/run-execute/SKILL.md +700 -0
- package/flows/fire/agents/builder/skills/run-execute/scripts/complete-run.cjs +748 -0
- package/flows/fire/agents/builder/skills/run-execute/scripts/init-run.cjs +457 -0
- package/flows/fire/agents/builder/skills/run-execute/scripts/update-phase.cjs +239 -0
- package/flows/fire/agents/builder/skills/run-execute/templates/plan.md.hbs +61 -0
- package/flows/fire/agents/builder/skills/run-execute/templates/test-report.md.hbs +81 -0
- package/flows/fire/agents/builder/skills/run-plan/SKILL.md +366 -0
- package/flows/fire/agents/builder/skills/run-status/SKILL.md +96 -0
- package/flows/fire/agents/builder/skills/walkthrough-generate/SKILL.md +181 -0
- package/flows/fire/agents/builder/skills/walkthrough-generate/templates/walkthrough.md.hbs +108 -0
- package/flows/fire/agents/orchestrator/agent.md +144 -0
- package/flows/fire/agents/orchestrator/skills/project-init/SKILL.md +226 -0
- package/flows/fire/agents/orchestrator/skills/project-init/templates/coding-standards.md.hbs +149 -0
- package/flows/fire/agents/orchestrator/skills/project-init/templates/constitution.md.hbs +43 -0
- package/flows/fire/agents/orchestrator/skills/project-init/templates/system-architecture.md.hbs +101 -0
- package/flows/fire/agents/orchestrator/skills/project-init/templates/tech-stack.md.hbs +136 -0
- package/flows/fire/agents/orchestrator/skills/project-init/templates/testing-standards.md.hbs +94 -0
- package/flows/fire/agents/orchestrator/skills/route/SKILL.md +146 -0
- package/flows/fire/agents/orchestrator/skills/status/SKILL.md +696 -0
- package/flows/fire/agents/planner/agent.md +143 -0
- package/flows/fire/agents/planner/skills/design-doc-generate/SKILL.md +156 -0
- package/flows/fire/agents/planner/skills/design-doc-generate/templates/design.md.hbs +124 -0
- package/flows/fire/agents/planner/skills/intent-capture/SKILL.md +125 -0
- package/flows/fire/agents/planner/skills/intent-capture/templates/brief.md.hbs +40 -0
- package/flows/fire/agents/planner/skills/work-item-decompose/SKILL.md +166 -0
- package/flows/fire/agents/planner/skills/work-item-decompose/templates/work-item.md.hbs +40 -0
- package/flows/fire/commands/fire-builder.md +56 -0
- package/flows/fire/commands/fire-planner.md +48 -0
- package/flows/fire/commands/fire.md +46 -0
- package/flows/fire/memory-bank.yaml +240 -0
- package/flows/fire/quick-start.md +146 -0
- package/flows/simple/README.md +190 -0
- package/flows/simple/agents/agent.md +404 -0
- package/flows/simple/commands/agent.md +60 -0
- package/flows/simple/context-config.yaml +34 -0
- package/flows/simple/memory-bank.yaml +66 -0
- package/flows/simple/quick-start.md +231 -0
- package/flows/simple/skills/design.md +96 -0
- package/flows/simple/skills/execute.md +190 -0
- package/flows/simple/skills/requirements.md +94 -0
- package/flows/simple/skills/tasks.md +136 -0
- package/flows/simple/templates/design-template.md +138 -0
- package/flows/simple/templates/requirements-template.md +85 -0
- package/flows/simple/templates/tasks-template.md +104 -0
- package/lib/InstallerFactory.js +36 -0
- package/lib/analytics/env-detector.js +92 -0
- package/lib/analytics/index.js +22 -0
- package/lib/analytics/machine-id.js +33 -0
- package/lib/analytics/tracker.js +232 -0
- package/lib/cli-utils.js +342 -0
- package/lib/constants.js +44 -0
- package/lib/installer.js +406 -0
- package/lib/installers/AntigravityInstaller.js +22 -0
- package/lib/installers/ClaudeInstaller.js +85 -0
- package/lib/installers/ClineInstaller.js +21 -0
- package/lib/installers/CodexInstaller.js +21 -0
- package/lib/installers/CopilotInstaller.js +113 -0
- package/lib/installers/CursorInstaller.js +63 -0
- package/lib/installers/GeminiInstaller.js +75 -0
- package/lib/installers/KiroInstaller.js +77 -0
- package/lib/installers/OpenCodeInstaller.js +30 -0
- package/lib/installers/RooInstaller.js +22 -0
- package/lib/installers/ToolInstaller.js +76 -0
- package/lib/installers/WindsurfInstaller.js +22 -0
- package/lib/markdown-validator.ts +175 -0
- package/lib/yaml-validator.ts +99 -0
- package/package.json +69 -0
|
@@ -0,0 +1,457 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* FIRE Run Initialization Script
|
|
5
|
+
*
|
|
6
|
+
* Creates run record in state.yaml and run folder structure.
|
|
7
|
+
* Supports both single work item and batch/wide runs with multiple items.
|
|
8
|
+
*
|
|
9
|
+
* Ensures deterministic run ID generation by checking BOTH:
|
|
10
|
+
* - runs.completed history in state.yaml
|
|
11
|
+
* - existing run folders in .specs-fire/runs/
|
|
12
|
+
*
|
|
13
|
+
* Usage:
|
|
14
|
+
* Single item: node init-run.cjs <rootPath> <workItemId> <intentId> <mode>
|
|
15
|
+
* Batch/Wide: node init-run.cjs <rootPath> --batch '<workItemsJson>'
|
|
16
|
+
*
|
|
17
|
+
* Examples:
|
|
18
|
+
* node init-run.cjs /project login-endpoint user-auth confirm
|
|
19
|
+
* node init-run.cjs /project --batch '[{"id":"wi-1","intent":"int-1","mode":"autopilot"}]'
|
|
20
|
+
*/
|
|
21
|
+
|
|
22
|
+
const fs = require('fs');
|
|
23
|
+
const path = require('path');
|
|
24
|
+
const yaml = require('yaml');
|
|
25
|
+
|
|
26
|
+
// =============================================================================
|
|
27
|
+
// Error Helper
|
|
28
|
+
// =============================================================================
|
|
29
|
+
|
|
30
|
+
function fireError(message, code, suggestion) {
|
|
31
|
+
const err = new Error(`FIRE Error [${code}]: ${message} ${suggestion}`);
|
|
32
|
+
err.code = code;
|
|
33
|
+
err.suggestion = suggestion;
|
|
34
|
+
return err;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// =============================================================================
|
|
38
|
+
// Validation
|
|
39
|
+
// =============================================================================
|
|
40
|
+
|
|
41
|
+
const VALID_MODES = ['autopilot', 'confirm', 'validate'];
|
|
42
|
+
const VALID_SCOPES = ['single', 'batch', 'wide'];
|
|
43
|
+
|
|
44
|
+
function validateRootPath(rootPath) {
|
|
45
|
+
if (!rootPath || typeof rootPath !== 'string' || rootPath.trim() === '') {
|
|
46
|
+
throw fireError('rootPath is required.', 'INIT_001', 'Provide a valid project root path.');
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (!fs.existsSync(rootPath)) {
|
|
50
|
+
throw fireError(
|
|
51
|
+
`Project root not found: "${rootPath}".`,
|
|
52
|
+
'INIT_040',
|
|
53
|
+
'Ensure the path exists and is accessible.'
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
function validateWorkItem(item, index) {
|
|
59
|
+
if (!item.id || typeof item.id !== 'string' || item.id.trim() === '') {
|
|
60
|
+
throw fireError(
|
|
61
|
+
`Work item at index ${index} missing 'id'.`,
|
|
62
|
+
'INIT_010',
|
|
63
|
+
'Each work item must have an id.'
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (!item.intent || typeof item.intent !== 'string' || item.intent.trim() === '') {
|
|
68
|
+
throw fireError(
|
|
69
|
+
`Work item "${item.id}" missing 'intent'.`,
|
|
70
|
+
'INIT_020',
|
|
71
|
+
'Each work item must have an intent.'
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
if (!item.mode || !VALID_MODES.includes(item.mode)) {
|
|
76
|
+
throw fireError(
|
|
77
|
+
`Work item "${item.id}" has invalid mode: "${item.mode}".`,
|
|
78
|
+
'INIT_030',
|
|
79
|
+
`Valid modes are: ${VALID_MODES.join(', ')}`
|
|
80
|
+
);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
function validateWorkItems(workItems) {
|
|
85
|
+
if (!Array.isArray(workItems) || workItems.length === 0) {
|
|
86
|
+
throw fireError(
|
|
87
|
+
'Work items array is empty or invalid.',
|
|
88
|
+
'INIT_011',
|
|
89
|
+
'Provide at least one work item.'
|
|
90
|
+
);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
workItems.forEach((item, index) => validateWorkItem(item, index));
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function validateFireProject(rootPath) {
|
|
97
|
+
const fireDir = path.join(rootPath, '.specs-fire');
|
|
98
|
+
const statePath = path.join(fireDir, 'state.yaml');
|
|
99
|
+
const runsPath = path.join(fireDir, 'runs');
|
|
100
|
+
|
|
101
|
+
if (!fs.existsSync(fireDir)) {
|
|
102
|
+
throw fireError(
|
|
103
|
+
`FIRE project not initialized at: "${rootPath}".`,
|
|
104
|
+
'INIT_041',
|
|
105
|
+
'Run fire-init first to initialize the project.'
|
|
106
|
+
);
|
|
107
|
+
}
|
|
108
|
+
|
|
109
|
+
if (!fs.existsSync(statePath)) {
|
|
110
|
+
throw fireError(
|
|
111
|
+
`State file not found at: "${statePath}".`,
|
|
112
|
+
'INIT_042',
|
|
113
|
+
'The project may be corrupted. Try re-initializing.'
|
|
114
|
+
);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
return { fireDir, statePath, runsPath };
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// =============================================================================
|
|
121
|
+
// State Operations
|
|
122
|
+
// =============================================================================
|
|
123
|
+
|
|
124
|
+
function readState(statePath) {
|
|
125
|
+
try {
|
|
126
|
+
const content = fs.readFileSync(statePath, 'utf8');
|
|
127
|
+
const state = yaml.parse(content);
|
|
128
|
+
if (!state || typeof state !== 'object') {
|
|
129
|
+
throw fireError('State file is empty or invalid.', 'INIT_050', 'Check state.yaml format.');
|
|
130
|
+
}
|
|
131
|
+
return state;
|
|
132
|
+
} catch (err) {
|
|
133
|
+
if (err.code && err.code.startsWith('INIT_')) throw err;
|
|
134
|
+
throw fireError(
|
|
135
|
+
`Failed to read state file: ${err.message}`,
|
|
136
|
+
'INIT_051',
|
|
137
|
+
'Check file permissions and YAML syntax.'
|
|
138
|
+
);
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function writeState(statePath, state) {
|
|
143
|
+
try {
|
|
144
|
+
fs.writeFileSync(statePath, yaml.stringify(state));
|
|
145
|
+
} catch (err) {
|
|
146
|
+
throw fireError(
|
|
147
|
+
`Failed to write state file: ${err.message}`,
|
|
148
|
+
'INIT_052',
|
|
149
|
+
'Check file permissions and disk space.'
|
|
150
|
+
);
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// =============================================================================
|
|
155
|
+
// Run ID Generation (CRITICAL - checks both history and file system)
|
|
156
|
+
// =============================================================================
|
|
157
|
+
|
|
158
|
+
function generateRunId(runsPath, state) {
|
|
159
|
+
// Ensure runs directory exists
|
|
160
|
+
if (!fs.existsSync(runsPath)) {
|
|
161
|
+
fs.mkdirSync(runsPath, { recursive: true });
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
// Source 1: Get max from state.yaml runs.completed history
|
|
165
|
+
let maxFromHistory = 0;
|
|
166
|
+
if (state.runs && Array.isArray(state.runs.completed)) {
|
|
167
|
+
for (const run of state.runs.completed) {
|
|
168
|
+
if (run.id) {
|
|
169
|
+
const match = run.id.match(/^run-(\d+)$/);
|
|
170
|
+
if (match) {
|
|
171
|
+
const num = parseInt(match[1], 10);
|
|
172
|
+
if (num > maxFromHistory) maxFromHistory = num;
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Source 2: Get max from file system (defensive)
|
|
179
|
+
let maxFromFileSystem = 0;
|
|
180
|
+
try {
|
|
181
|
+
const entries = fs.readdirSync(runsPath);
|
|
182
|
+
for (const entry of entries) {
|
|
183
|
+
if (/^run-\d{3,}$/.test(entry)) {
|
|
184
|
+
const num = parseInt(entry.replace('run-', ''), 10);
|
|
185
|
+
if (num > maxFromFileSystem) maxFromFileSystem = num;
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
} catch (err) {
|
|
189
|
+
throw fireError(
|
|
190
|
+
`Failed to read runs directory: ${err.message}`,
|
|
191
|
+
'INIT_060',
|
|
192
|
+
'Check directory permissions.'
|
|
193
|
+
);
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// Use MAX of both to ensure no duplicates
|
|
197
|
+
const maxNum = Math.max(maxFromHistory, maxFromFileSystem);
|
|
198
|
+
const nextNum = maxNum + 1;
|
|
199
|
+
|
|
200
|
+
return `run-${String(nextNum).padStart(3, '0')}`;
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
// =============================================================================
|
|
204
|
+
// Scope Detection
|
|
205
|
+
// =============================================================================
|
|
206
|
+
|
|
207
|
+
function detectScope(workItems) {
|
|
208
|
+
if (workItems.length === 1) {
|
|
209
|
+
return 'single';
|
|
210
|
+
}
|
|
211
|
+
// For multiple items, default to batch
|
|
212
|
+
// (wide would be explicitly set by the caller if all compatible items are included)
|
|
213
|
+
return 'batch';
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// =============================================================================
|
|
217
|
+
// Run Folder Creation
|
|
218
|
+
// =============================================================================
|
|
219
|
+
|
|
220
|
+
function createRunFolder(runPath) {
|
|
221
|
+
try {
|
|
222
|
+
fs.mkdirSync(runPath, { recursive: true });
|
|
223
|
+
} catch (err) {
|
|
224
|
+
throw fireError(
|
|
225
|
+
`Failed to create run folder: ${err.message}`,
|
|
226
|
+
'INIT_070',
|
|
227
|
+
'Check directory permissions and disk space.'
|
|
228
|
+
);
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
function createRunLog(runPath, runId, workItems, scope, startTime) {
|
|
233
|
+
// Format work items for run.md
|
|
234
|
+
const workItemsList = workItems.map((item, index) => {
|
|
235
|
+
const status = index === 0 ? 'in_progress' : 'pending';
|
|
236
|
+
return ` - id: ${item.id}\n intent: ${item.intent}\n mode: ${item.mode}\n status: ${status}`;
|
|
237
|
+
}).join('\n');
|
|
238
|
+
|
|
239
|
+
const currentItem = workItems[0];
|
|
240
|
+
|
|
241
|
+
const runLog = `---
|
|
242
|
+
id: ${runId}
|
|
243
|
+
scope: ${scope}
|
|
244
|
+
work_items:
|
|
245
|
+
${workItemsList}
|
|
246
|
+
current_item: ${currentItem.id}
|
|
247
|
+
status: in_progress
|
|
248
|
+
started: ${startTime}
|
|
249
|
+
completed: null
|
|
250
|
+
---
|
|
251
|
+
|
|
252
|
+
# Run: ${runId}
|
|
253
|
+
|
|
254
|
+
## Scope
|
|
255
|
+
${scope} (${workItems.length} work item${workItems.length > 1 ? 's' : ''})
|
|
256
|
+
|
|
257
|
+
## Work Items
|
|
258
|
+
${workItems.map((item, i) => `${i + 1}. **${item.id}** (${item.mode}) — ${i === 0 ? 'in_progress' : 'pending'}`).join('\n')}
|
|
259
|
+
|
|
260
|
+
## Current Item
|
|
261
|
+
${currentItem.id} (${currentItem.mode})
|
|
262
|
+
|
|
263
|
+
## Files Created
|
|
264
|
+
(none yet)
|
|
265
|
+
|
|
266
|
+
## Files Modified
|
|
267
|
+
(none yet)
|
|
268
|
+
|
|
269
|
+
## Decisions
|
|
270
|
+
(none yet)
|
|
271
|
+
`;
|
|
272
|
+
|
|
273
|
+
const runLogPath = path.join(runPath, 'run.md');
|
|
274
|
+
try {
|
|
275
|
+
fs.writeFileSync(runLogPath, runLog);
|
|
276
|
+
} catch (err) {
|
|
277
|
+
throw fireError(
|
|
278
|
+
`Failed to create run log: ${err.message}`,
|
|
279
|
+
'INIT_071',
|
|
280
|
+
'Check file permissions.'
|
|
281
|
+
);
|
|
282
|
+
}
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
// =============================================================================
|
|
286
|
+
// Main Function
|
|
287
|
+
// =============================================================================
|
|
288
|
+
|
|
289
|
+
/**
|
|
290
|
+
* Initialize a run with one or more work items.
|
|
291
|
+
*
|
|
292
|
+
* @param {string} rootPath - Project root directory
|
|
293
|
+
* @param {Array<{id: string, intent: string, mode: string}>} workItems - Work items to include in run
|
|
294
|
+
* @param {string} [scope] - Optional scope override ('single', 'batch', 'wide')
|
|
295
|
+
* @returns {object} Result with runId, runPath, workItems, scope, started
|
|
296
|
+
*/
|
|
297
|
+
function initRun(rootPath, workItems, scope) {
|
|
298
|
+
// Validate root path
|
|
299
|
+
validateRootPath(rootPath);
|
|
300
|
+
|
|
301
|
+
// Validate work items
|
|
302
|
+
validateWorkItems(workItems);
|
|
303
|
+
|
|
304
|
+
// Detect or validate scope
|
|
305
|
+
const detectedScope = scope || detectScope(workItems);
|
|
306
|
+
if (scope && !VALID_SCOPES.includes(scope)) {
|
|
307
|
+
throw fireError(
|
|
308
|
+
`Invalid scope: "${scope}".`,
|
|
309
|
+
'INIT_035',
|
|
310
|
+
`Valid scopes are: ${VALID_SCOPES.join(', ')}`
|
|
311
|
+
);
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Validate FIRE project structure
|
|
315
|
+
const { statePath, runsPath } = validateFireProject(rootPath);
|
|
316
|
+
|
|
317
|
+
// Read state
|
|
318
|
+
const state = readState(statePath);
|
|
319
|
+
|
|
320
|
+
// Initialize runs structure if needed
|
|
321
|
+
if (!state.runs) {
|
|
322
|
+
state.runs = { active: [], completed: [] };
|
|
323
|
+
}
|
|
324
|
+
if (!Array.isArray(state.runs.active)) {
|
|
325
|
+
state.runs.active = [];
|
|
326
|
+
}
|
|
327
|
+
if (!Array.isArray(state.runs.completed)) {
|
|
328
|
+
state.runs.completed = [];
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
// Generate run ID (checks both history AND file system)
|
|
332
|
+
const runId = generateRunId(runsPath, state);
|
|
333
|
+
const runPath = path.join(runsPath, runId);
|
|
334
|
+
|
|
335
|
+
// Create run folder
|
|
336
|
+
createRunFolder(runPath);
|
|
337
|
+
|
|
338
|
+
// Create run log
|
|
339
|
+
const startTime = new Date().toISOString();
|
|
340
|
+
createRunLog(runPath, runId, workItems, detectedScope, startTime);
|
|
341
|
+
|
|
342
|
+
// Prepare work items for state with status and phase tracking
|
|
343
|
+
const stateWorkItems = workItems.map((item, index) => ({
|
|
344
|
+
id: item.id,
|
|
345
|
+
intent: item.intent,
|
|
346
|
+
mode: item.mode,
|
|
347
|
+
status: index === 0 ? 'in_progress' : 'pending',
|
|
348
|
+
current_phase: index === 0 ? 'plan' : null,
|
|
349
|
+
}));
|
|
350
|
+
|
|
351
|
+
// Add to active runs list (supports multiple parallel runs)
|
|
352
|
+
state.runs.active.push({
|
|
353
|
+
id: runId,
|
|
354
|
+
scope: detectedScope,
|
|
355
|
+
work_items: stateWorkItems,
|
|
356
|
+
current_item: workItems[0].id,
|
|
357
|
+
started: startTime,
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
// Save state
|
|
361
|
+
writeState(statePath, state);
|
|
362
|
+
|
|
363
|
+
// Return result
|
|
364
|
+
return {
|
|
365
|
+
success: true,
|
|
366
|
+
runId: runId,
|
|
367
|
+
runPath: runPath,
|
|
368
|
+
scope: detectedScope,
|
|
369
|
+
workItems: stateWorkItems,
|
|
370
|
+
currentItem: workItems[0].id,
|
|
371
|
+
started: startTime,
|
|
372
|
+
};
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// =============================================================================
|
|
376
|
+
// CLI Interface
|
|
377
|
+
// =============================================================================
|
|
378
|
+
|
|
379
|
+
function printUsage() {
|
|
380
|
+
console.error('Usage:');
|
|
381
|
+
console.error(' Single item: node init-run.cjs <rootPath> <workItemId> <intentId> <mode>');
|
|
382
|
+
console.error(' Batch/Wide: node init-run.cjs <rootPath> --batch \'<workItemsJson>\' [--scope=<scope>]');
|
|
383
|
+
console.error('');
|
|
384
|
+
console.error('Arguments:');
|
|
385
|
+
console.error(' rootPath - Project root directory');
|
|
386
|
+
console.error(' workItemId - Work item ID (single mode)');
|
|
387
|
+
console.error(' intentId - Intent ID (single mode)');
|
|
388
|
+
console.error(' mode - Execution mode: autopilot, confirm, validate');
|
|
389
|
+
console.error('');
|
|
390
|
+
console.error('Options:');
|
|
391
|
+
console.error(' --batch - JSON array of work items');
|
|
392
|
+
console.error(' --scope - Override scope: single, batch, wide');
|
|
393
|
+
console.error('');
|
|
394
|
+
console.error('Work item JSON format:');
|
|
395
|
+
console.error(' [{"id": "wi-1", "intent": "int-1", "mode": "autopilot"}, ...]');
|
|
396
|
+
console.error('');
|
|
397
|
+
console.error('Examples:');
|
|
398
|
+
console.error(' node init-run.cjs /project login-endpoint user-auth confirm');
|
|
399
|
+
console.error(' node init-run.cjs /project --batch \'[{"id":"wi-1","intent":"int-1","mode":"autopilot"}]\'');
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
if (require.main === module) {
|
|
403
|
+
const args = process.argv.slice(2);
|
|
404
|
+
|
|
405
|
+
if (args.length < 2) {
|
|
406
|
+
printUsage();
|
|
407
|
+
process.exit(1);
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
const rootPath = args[0];
|
|
411
|
+
let workItems = [];
|
|
412
|
+
let scope = null;
|
|
413
|
+
|
|
414
|
+
// Check if batch mode
|
|
415
|
+
if (args[1] === '--batch') {
|
|
416
|
+
if (args.length < 3) {
|
|
417
|
+
console.error('Error: --batch requires a JSON array of work items');
|
|
418
|
+
printUsage();
|
|
419
|
+
process.exit(1);
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
try {
|
|
423
|
+
workItems = JSON.parse(args[2]);
|
|
424
|
+
} catch (err) {
|
|
425
|
+
console.error(`Error: Failed to parse work items JSON: ${err.message}`);
|
|
426
|
+
process.exit(1);
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
// Check for --scope option
|
|
430
|
+
for (let i = 3; i < args.length; i++) {
|
|
431
|
+
if (args[i].startsWith('--scope=')) {
|
|
432
|
+
scope = args[i].substring('--scope='.length);
|
|
433
|
+
}
|
|
434
|
+
}
|
|
435
|
+
} else {
|
|
436
|
+
// Single item mode (backwards compatible)
|
|
437
|
+
if (args.length < 4) {
|
|
438
|
+
printUsage();
|
|
439
|
+
process.exit(1);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
const [, workItemId, intentId, mode] = args;
|
|
443
|
+
workItems = [{ id: workItemId, intent: intentId, mode: mode }];
|
|
444
|
+
scope = 'single';
|
|
445
|
+
}
|
|
446
|
+
|
|
447
|
+
try {
|
|
448
|
+
const result = initRun(rootPath, workItems, scope);
|
|
449
|
+
console.log(JSON.stringify(result, null, 2));
|
|
450
|
+
process.exit(0);
|
|
451
|
+
} catch (err) {
|
|
452
|
+
console.error(err.message);
|
|
453
|
+
process.exit(1);
|
|
454
|
+
}
|
|
455
|
+
}
|
|
456
|
+
|
|
457
|
+
module.exports = { initRun };
|
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* FIRE Phase Update Script
|
|
5
|
+
*
|
|
6
|
+
* Updates the current phase for a work item in an active run.
|
|
7
|
+
* Phases: plan → execute → test → review
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* node update-phase.cjs <rootPath> <runId> <phase>
|
|
11
|
+
*
|
|
12
|
+
* Examples:
|
|
13
|
+
* node update-phase.cjs /project run-001 execute
|
|
14
|
+
* node update-phase.cjs /project run-001 test
|
|
15
|
+
* node update-phase.cjs /project run-001 review
|
|
16
|
+
*/
|
|
17
|
+
|
|
18
|
+
const fs = require('fs');
|
|
19
|
+
const path = require('path');
|
|
20
|
+
const yaml = require('yaml');
|
|
21
|
+
|
|
22
|
+
// =============================================================================
|
|
23
|
+
// Constants
|
|
24
|
+
// =============================================================================
|
|
25
|
+
|
|
26
|
+
const VALID_PHASES = ['plan', 'execute', 'test', 'review'];
|
|
27
|
+
|
|
28
|
+
// =============================================================================
|
|
29
|
+
// Error Helper
|
|
30
|
+
// =============================================================================
|
|
31
|
+
|
|
32
|
+
function fireError(message, code, suggestion) {
|
|
33
|
+
const err = new Error(`FIRE Error [${code}]: ${message} ${suggestion}`);
|
|
34
|
+
err.code = code;
|
|
35
|
+
err.suggestion = suggestion;
|
|
36
|
+
return err;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// =============================================================================
|
|
40
|
+
// Validation
|
|
41
|
+
// =============================================================================
|
|
42
|
+
|
|
43
|
+
function validateInputs(rootPath, runId, phase) {
|
|
44
|
+
if (!rootPath || typeof rootPath !== 'string' || rootPath.trim() === '') {
|
|
45
|
+
throw fireError('rootPath is required.', 'PHASE_001', 'Provide a valid project root path.');
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (!runId || typeof runId !== 'string' || runId.trim() === '') {
|
|
49
|
+
throw fireError('runId is required.', 'PHASE_002', 'Provide the run ID.');
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
if (!phase || !VALID_PHASES.includes(phase)) {
|
|
53
|
+
throw fireError(
|
|
54
|
+
`Invalid phase: "${phase}".`,
|
|
55
|
+
'PHASE_003',
|
|
56
|
+
`Valid phases are: ${VALID_PHASES.join(', ')}`
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
if (!fs.existsSync(rootPath)) {
|
|
61
|
+
throw fireError(
|
|
62
|
+
`Project root not found: "${rootPath}".`,
|
|
63
|
+
'PHASE_004',
|
|
64
|
+
'Ensure the path exists and is accessible.'
|
|
65
|
+
);
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function validateFireProject(rootPath, runId) {
|
|
70
|
+
const fireDir = path.join(rootPath, '.specs-fire');
|
|
71
|
+
const statePath = path.join(fireDir, 'state.yaml');
|
|
72
|
+
|
|
73
|
+
if (!fs.existsSync(fireDir)) {
|
|
74
|
+
throw fireError(
|
|
75
|
+
`FIRE project not initialized at: "${rootPath}".`,
|
|
76
|
+
'PHASE_010',
|
|
77
|
+
'Run fire-init first to initialize the project.'
|
|
78
|
+
);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (!fs.existsSync(statePath)) {
|
|
82
|
+
throw fireError(
|
|
83
|
+
`State file not found at: "${statePath}".`,
|
|
84
|
+
'PHASE_011',
|
|
85
|
+
'The project may be corrupted. Try re-initializing.'
|
|
86
|
+
);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return { statePath };
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// =============================================================================
|
|
93
|
+
// State Operations
|
|
94
|
+
// =============================================================================
|
|
95
|
+
|
|
96
|
+
function readState(statePath) {
|
|
97
|
+
try {
|
|
98
|
+
const content = fs.readFileSync(statePath, 'utf8');
|
|
99
|
+
const state = yaml.parse(content);
|
|
100
|
+
if (!state || typeof state !== 'object') {
|
|
101
|
+
throw fireError('State file is empty or invalid.', 'PHASE_020', 'Check state.yaml format.');
|
|
102
|
+
}
|
|
103
|
+
return state;
|
|
104
|
+
} catch (err) {
|
|
105
|
+
if (err.code && err.code.startsWith('PHASE_')) throw err;
|
|
106
|
+
throw fireError(
|
|
107
|
+
`Failed to read state file: ${err.message}`,
|
|
108
|
+
'PHASE_021',
|
|
109
|
+
'Check file permissions and YAML syntax.'
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function writeState(statePath, state) {
|
|
115
|
+
try {
|
|
116
|
+
fs.writeFileSync(statePath, yaml.stringify(state));
|
|
117
|
+
} catch (err) {
|
|
118
|
+
throw fireError(
|
|
119
|
+
`Failed to write state file: ${err.message}`,
|
|
120
|
+
'PHASE_022',
|
|
121
|
+
'Check file permissions and disk space.'
|
|
122
|
+
);
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// =============================================================================
|
|
127
|
+
// Main Function
|
|
128
|
+
// =============================================================================
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Update the current phase for the active work item in a run.
|
|
132
|
+
*
|
|
133
|
+
* @param {string} rootPath - Project root directory
|
|
134
|
+
* @param {string} runId - Run ID
|
|
135
|
+
* @param {string} phase - New phase (plan, execute, test, review)
|
|
136
|
+
* @returns {object} Result with updated phase info
|
|
137
|
+
*/
|
|
138
|
+
function updatePhase(rootPath, runId, phase) {
|
|
139
|
+
validateInputs(rootPath, runId, phase);
|
|
140
|
+
const { statePath } = validateFireProject(rootPath, runId);
|
|
141
|
+
const state = readState(statePath);
|
|
142
|
+
|
|
143
|
+
// Find run in active runs list
|
|
144
|
+
const activeRuns = state.runs?.active || [];
|
|
145
|
+
const runIndex = activeRuns.findIndex(r => r.id === runId);
|
|
146
|
+
|
|
147
|
+
if (runIndex === -1) {
|
|
148
|
+
throw fireError(
|
|
149
|
+
`Run "${runId}" not found in active runs.`,
|
|
150
|
+
'PHASE_030',
|
|
151
|
+
'The run may have been completed or was never started.'
|
|
152
|
+
);
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
const activeRun = activeRuns[runIndex];
|
|
156
|
+
const workItems = activeRun.work_items || [];
|
|
157
|
+
const currentItemId = activeRun.current_item;
|
|
158
|
+
|
|
159
|
+
if (!currentItemId) {
|
|
160
|
+
throw fireError(
|
|
161
|
+
`No current item in run "${runId}".`,
|
|
162
|
+
'PHASE_031',
|
|
163
|
+
'The run may have completed all work items.'
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Find and update current item's phase
|
|
168
|
+
let updated = false;
|
|
169
|
+
let previousPhase = null;
|
|
170
|
+
for (const item of workItems) {
|
|
171
|
+
if (item.id === currentItemId) {
|
|
172
|
+
previousPhase = item.current_phase || 'plan';
|
|
173
|
+
item.current_phase = phase;
|
|
174
|
+
updated = true;
|
|
175
|
+
break;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
if (!updated) {
|
|
180
|
+
throw fireError(
|
|
181
|
+
`Current item "${currentItemId}" not found in work items.`,
|
|
182
|
+
'PHASE_032',
|
|
183
|
+
'The run state may be corrupted.'
|
|
184
|
+
);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Update state
|
|
188
|
+
activeRun.work_items = workItems;
|
|
189
|
+
state.runs.active[runIndex] = activeRun;
|
|
190
|
+
writeState(statePath, state);
|
|
191
|
+
|
|
192
|
+
return {
|
|
193
|
+
success: true,
|
|
194
|
+
runId: runId,
|
|
195
|
+
workItemId: currentItemId,
|
|
196
|
+
previousPhase: previousPhase,
|
|
197
|
+
currentPhase: phase,
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// =============================================================================
|
|
202
|
+
// CLI Interface
|
|
203
|
+
// =============================================================================
|
|
204
|
+
|
|
205
|
+
function printUsage() {
|
|
206
|
+
console.error('Usage:');
|
|
207
|
+
console.error(' node update-phase.cjs <rootPath> <runId> <phase>');
|
|
208
|
+
console.error('');
|
|
209
|
+
console.error('Arguments:');
|
|
210
|
+
console.error(' rootPath - Project root directory');
|
|
211
|
+
console.error(' runId - Run ID (e.g., run-001)');
|
|
212
|
+
console.error(' phase - New phase: plan, execute, test, review');
|
|
213
|
+
console.error('');
|
|
214
|
+
console.error('Examples:');
|
|
215
|
+
console.error(' node update-phase.cjs /project run-001 execute');
|
|
216
|
+
console.error(' node update-phase.cjs /project run-001 test');
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
if (require.main === module) {
|
|
220
|
+
const args = process.argv.slice(2);
|
|
221
|
+
|
|
222
|
+
if (args.length < 3) {
|
|
223
|
+
printUsage();
|
|
224
|
+
process.exit(1);
|
|
225
|
+
}
|
|
226
|
+
|
|
227
|
+
const [rootPath, runId, phase] = args;
|
|
228
|
+
|
|
229
|
+
try {
|
|
230
|
+
const result = updatePhase(rootPath, runId, phase);
|
|
231
|
+
console.log(JSON.stringify(result, null, 2));
|
|
232
|
+
process.exit(0);
|
|
233
|
+
} catch (err) {
|
|
234
|
+
console.error(err.message);
|
|
235
|
+
process.exit(1);
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
module.exports = { updatePhase, VALID_PHASES };
|