specsmd 0.1.19 → 0.1.20
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.
|
@@ -445,6 +445,7 @@ function completeCurrentItem(rootPath, runId, params = {}) {
|
|
|
445
445
|
for (let i = currentItemIndex + 1; i < workItems.length; i++) {
|
|
446
446
|
if (workItems[i].status === 'pending') {
|
|
447
447
|
workItems[i].status = 'in_progress';
|
|
448
|
+
workItems[i].current_phase = 'plan';
|
|
448
449
|
nextItem = workItems[i];
|
|
449
450
|
break;
|
|
450
451
|
}
|
|
@@ -339,12 +339,13 @@ function initRun(rootPath, workItems, scope) {
|
|
|
339
339
|
const startTime = new Date().toISOString();
|
|
340
340
|
createRunLog(runPath, runId, workItems, detectedScope, startTime);
|
|
341
341
|
|
|
342
|
-
// Prepare work items for state with status tracking
|
|
342
|
+
// Prepare work items for state with status and phase tracking
|
|
343
343
|
const stateWorkItems = workItems.map((item, index) => ({
|
|
344
344
|
id: item.id,
|
|
345
345
|
intent: item.intent,
|
|
346
346
|
mode: item.mode,
|
|
347
347
|
status: index === 0 ? 'in_progress' : 'pending',
|
|
348
|
+
current_phase: index === 0 ? 'plan' : null,
|
|
348
349
|
}));
|
|
349
350
|
|
|
350
351
|
// Add to active runs list (supports multiple parallel runs)
|
|
@@ -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 };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "specsmd",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.20",
|
|
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": {
|