agileflow 2.75.0 → 2.77.0
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/package.json +1 -1
- package/scripts/agileflow-configure.js +110 -32
- package/scripts/agileflow-statusline.sh +155 -9
- package/scripts/agileflow-welcome.js +171 -47
- package/scripts/auto-self-improve.js +344 -0
- package/scripts/check-update.js +1 -4
- package/scripts/get-env.js +15 -7
- package/scripts/lib/frontmatter-parser.js +4 -1
- package/scripts/obtain-context.js +59 -48
- package/scripts/ralph-loop.js +503 -0
- package/scripts/validate-expertise.sh +19 -15
- package/src/core/agents/design.md +1 -1
- package/src/core/agents/documentation.md +1 -1
- package/src/core/agents/integrations.md +1 -1
- package/src/core/agents/mobile.md +1 -1
- package/src/core/agents/monitoring.md +1 -1
- package/src/core/agents/performance.md +1 -1
- package/src/core/commands/babysit.md +73 -0
- package/src/core/commands/configure.md +50 -7
- package/src/core/experts/documentation/expertise.yaml +4 -0
- package/src/core/experts/research/expertise.yaml +2 -2
- package/tools/cli/commands/list.js +3 -1
- package/tools/cli/commands/uninstall.js +4 -5
- package/tools/cli/commands/update.js +11 -3
- package/tools/cli/lib/content-injector.js +6 -1
- package/tools/cli/lib/docs-setup.js +1 -1
|
@@ -0,0 +1,503 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* ralph-loop.js - Autonomous Story Processing Loop
|
|
5
|
+
*
|
|
6
|
+
* This script is the brain of AgileFlow's autonomous work mode.
|
|
7
|
+
* It runs as a Stop hook and handles:
|
|
8
|
+
* 1. Checking if loop mode is enabled
|
|
9
|
+
* 2. Running test validation
|
|
10
|
+
* 3. Updating story status on success
|
|
11
|
+
* 4. Feeding context back for next iteration
|
|
12
|
+
* 5. Tracking iterations and enforcing limits
|
|
13
|
+
*
|
|
14
|
+
* Named after the "Ralph Wiggum" pattern from Anthropic.
|
|
15
|
+
*
|
|
16
|
+
* Usage (as Stop hook):
|
|
17
|
+
* node scripts/ralph-loop.js
|
|
18
|
+
*
|
|
19
|
+
* Manual control:
|
|
20
|
+
* node scripts/ralph-loop.js --status # Check loop status
|
|
21
|
+
* node scripts/ralph-loop.js --stop # Stop the loop
|
|
22
|
+
* node scripts/ralph-loop.js --reset # Reset loop state
|
|
23
|
+
*/
|
|
24
|
+
|
|
25
|
+
const fs = require('fs');
|
|
26
|
+
const path = require('path');
|
|
27
|
+
const { execSync, spawnSync } = require('child_process');
|
|
28
|
+
|
|
29
|
+
// ANSI colors
|
|
30
|
+
const c = {
|
|
31
|
+
reset: '\x1b[0m',
|
|
32
|
+
bold: '\x1b[1m',
|
|
33
|
+
dim: '\x1b[2m',
|
|
34
|
+
red: '\x1b[31m',
|
|
35
|
+
green: '\x1b[32m',
|
|
36
|
+
yellow: '\x1b[33m',
|
|
37
|
+
blue: '\x1b[34m',
|
|
38
|
+
cyan: '\x1b[36m',
|
|
39
|
+
brand: '\x1b[38;2;232;104;58m',
|
|
40
|
+
};
|
|
41
|
+
|
|
42
|
+
// Find project root
|
|
43
|
+
function getProjectRoot() {
|
|
44
|
+
let dir = process.cwd();
|
|
45
|
+
while (!fs.existsSync(path.join(dir, '.agileflow')) && dir !== '/') {
|
|
46
|
+
dir = path.dirname(dir);
|
|
47
|
+
}
|
|
48
|
+
return dir !== '/' ? dir : process.cwd();
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// Read session state
|
|
52
|
+
function getSessionState(rootDir) {
|
|
53
|
+
const statePath = path.join(rootDir, 'docs/09-agents/session-state.json');
|
|
54
|
+
try {
|
|
55
|
+
if (fs.existsSync(statePath)) {
|
|
56
|
+
return JSON.parse(fs.readFileSync(statePath, 'utf8'));
|
|
57
|
+
}
|
|
58
|
+
} catch (e) {}
|
|
59
|
+
return {};
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Write session state
|
|
63
|
+
function saveSessionState(rootDir, state) {
|
|
64
|
+
const statePath = path.join(rootDir, 'docs/09-agents/session-state.json');
|
|
65
|
+
const dir = path.dirname(statePath);
|
|
66
|
+
if (!fs.existsSync(dir)) {
|
|
67
|
+
fs.mkdirSync(dir, { recursive: true });
|
|
68
|
+
}
|
|
69
|
+
fs.writeFileSync(statePath, JSON.stringify(state, null, 2) + '\n');
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Read status.json for stories
|
|
73
|
+
function getStatus(rootDir) {
|
|
74
|
+
const statusPath = path.join(rootDir, 'docs/09-agents/status.json');
|
|
75
|
+
try {
|
|
76
|
+
if (fs.existsSync(statusPath)) {
|
|
77
|
+
return JSON.parse(fs.readFileSync(statusPath, 'utf8'));
|
|
78
|
+
}
|
|
79
|
+
} catch (e) {}
|
|
80
|
+
return { stories: {}, epics: {} };
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// Save status.json
|
|
84
|
+
function saveStatus(rootDir, status) {
|
|
85
|
+
const statusPath = path.join(rootDir, 'docs/09-agents/status.json');
|
|
86
|
+
fs.writeFileSync(statusPath, JSON.stringify(status, null, 2) + '\n');
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
// Get test command from package.json or metadata
|
|
90
|
+
function getTestCommand(rootDir) {
|
|
91
|
+
// Check agileflow metadata first
|
|
92
|
+
try {
|
|
93
|
+
const metadataPath = path.join(rootDir, 'docs/00-meta/agileflow-metadata.json');
|
|
94
|
+
if (fs.existsSync(metadataPath)) {
|
|
95
|
+
const metadata = JSON.parse(fs.readFileSync(metadataPath, 'utf8'));
|
|
96
|
+
if (metadata.ralph_loop?.test_command) {
|
|
97
|
+
return metadata.ralph_loop.test_command;
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
} catch (e) {}
|
|
101
|
+
|
|
102
|
+
// Default to npm test
|
|
103
|
+
return 'npm test';
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Run tests and return result
|
|
107
|
+
function runTests(rootDir, testCommand) {
|
|
108
|
+
const result = { passed: false, output: '', duration: 0 };
|
|
109
|
+
const startTime = Date.now();
|
|
110
|
+
|
|
111
|
+
try {
|
|
112
|
+
const output = execSync(testCommand, {
|
|
113
|
+
cwd: rootDir,
|
|
114
|
+
encoding: 'utf8',
|
|
115
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
116
|
+
timeout: 300000, // 5 minute timeout
|
|
117
|
+
});
|
|
118
|
+
result.passed = true;
|
|
119
|
+
result.output = output;
|
|
120
|
+
} catch (e) {
|
|
121
|
+
result.passed = false;
|
|
122
|
+
result.output = e.stdout || '' + '\n' + (e.stderr || '');
|
|
123
|
+
if (e.message) {
|
|
124
|
+
result.output += '\n' + e.message;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
result.duration = Date.now() - startTime;
|
|
129
|
+
return result;
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
// Get next ready story in epic
|
|
133
|
+
function getNextStory(status, epicId, currentStoryId) {
|
|
134
|
+
const stories = status.stories || {};
|
|
135
|
+
|
|
136
|
+
// Get all stories in this epic that are ready
|
|
137
|
+
const readyStories = Object.entries(stories)
|
|
138
|
+
.filter(([id, story]) => {
|
|
139
|
+
return story.epic === epicId && story.status === 'ready' && id !== currentStoryId;
|
|
140
|
+
})
|
|
141
|
+
.sort((a, b) => {
|
|
142
|
+
// Sort by story number if possible
|
|
143
|
+
const numA = parseInt(a[0].replace(/\D/g, '')) || 0;
|
|
144
|
+
const numB = parseInt(b[0].replace(/\D/g, '')) || 0;
|
|
145
|
+
return numA - numB;
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
if (readyStories.length > 0) {
|
|
149
|
+
return { id: readyStories[0][0], ...readyStories[0][1] };
|
|
150
|
+
}
|
|
151
|
+
return null;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
// Mark story as completed
|
|
155
|
+
function markStoryComplete(rootDir, storyId) {
|
|
156
|
+
const status = getStatus(rootDir);
|
|
157
|
+
if (status.stories && status.stories[storyId]) {
|
|
158
|
+
status.stories[storyId].status = 'completed';
|
|
159
|
+
status.stories[storyId].completed_at = new Date().toISOString();
|
|
160
|
+
saveStatus(rootDir, status);
|
|
161
|
+
return true;
|
|
162
|
+
}
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
// Mark story as in_progress
|
|
167
|
+
function markStoryInProgress(rootDir, storyId) {
|
|
168
|
+
const status = getStatus(rootDir);
|
|
169
|
+
if (status.stories && status.stories[storyId]) {
|
|
170
|
+
status.stories[storyId].status = 'in_progress';
|
|
171
|
+
status.stories[storyId].started_at = new Date().toISOString();
|
|
172
|
+
saveStatus(rootDir, status);
|
|
173
|
+
return true;
|
|
174
|
+
}
|
|
175
|
+
return false;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
// Get story details
|
|
179
|
+
function getStoryDetails(rootDir, storyId) {
|
|
180
|
+
const status = getStatus(rootDir);
|
|
181
|
+
if (status.stories && status.stories[storyId]) {
|
|
182
|
+
return { id: storyId, ...status.stories[storyId] };
|
|
183
|
+
}
|
|
184
|
+
return null;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// Count stories in epic by status
|
|
188
|
+
function getEpicProgress(status, epicId) {
|
|
189
|
+
const stories = status.stories || {};
|
|
190
|
+
const epicStories = Object.entries(stories).filter(([_, s]) => s.epic === epicId);
|
|
191
|
+
|
|
192
|
+
return {
|
|
193
|
+
total: epicStories.length,
|
|
194
|
+
completed: epicStories.filter(([_, s]) => s.status === 'completed').length,
|
|
195
|
+
in_progress: epicStories.filter(([_, s]) => s.status === 'in_progress').length,
|
|
196
|
+
ready: epicStories.filter(([_, s]) => s.status === 'ready').length,
|
|
197
|
+
blocked: epicStories.filter(([_, s]) => s.status === 'blocked').length,
|
|
198
|
+
};
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
// Main loop logic
|
|
202
|
+
function handleLoop(rootDir) {
|
|
203
|
+
const state = getSessionState(rootDir);
|
|
204
|
+
const loop = state.ralph_loop;
|
|
205
|
+
|
|
206
|
+
// Check if loop mode is enabled
|
|
207
|
+
if (!loop || !loop.enabled) {
|
|
208
|
+
return; // Silent exit - not in loop mode
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
const status = getStatus(rootDir);
|
|
212
|
+
const iteration = (loop.iteration || 0) + 1;
|
|
213
|
+
const maxIterations = loop.max_iterations || 20;
|
|
214
|
+
|
|
215
|
+
console.log('');
|
|
216
|
+
console.log(
|
|
217
|
+
`${c.brand}${c.bold}══════════════════════════════════════════════════════════${c.reset}`
|
|
218
|
+
);
|
|
219
|
+
console.log(
|
|
220
|
+
`${c.brand}${c.bold} RALPH LOOP - Iteration ${iteration}/${maxIterations}${c.reset}`
|
|
221
|
+
);
|
|
222
|
+
console.log(
|
|
223
|
+
`${c.brand}${c.bold}══════════════════════════════════════════════════════════${c.reset}`
|
|
224
|
+
);
|
|
225
|
+
console.log('');
|
|
226
|
+
|
|
227
|
+
// Check iteration limit
|
|
228
|
+
if (iteration > maxIterations) {
|
|
229
|
+
console.log(`${c.yellow}⚠ Max iterations (${maxIterations}) reached. Stopping loop.${c.reset}`);
|
|
230
|
+
console.log(`${c.dim}Run /agileflow:babysit MODE=loop to restart${c.reset}`);
|
|
231
|
+
state.ralph_loop.enabled = false;
|
|
232
|
+
state.ralph_loop.stopped_reason = 'max_iterations';
|
|
233
|
+
saveSessionState(rootDir, state);
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Get current story
|
|
238
|
+
const currentStoryId = loop.current_story;
|
|
239
|
+
const currentStory = getStoryDetails(rootDir, currentStoryId);
|
|
240
|
+
const epicId = loop.epic;
|
|
241
|
+
|
|
242
|
+
if (!currentStory) {
|
|
243
|
+
console.log(`${c.red}✗ Current story ${currentStoryId} not found${c.reset}`);
|
|
244
|
+
state.ralph_loop.enabled = false;
|
|
245
|
+
state.ralph_loop.stopped_reason = 'story_not_found';
|
|
246
|
+
saveSessionState(rootDir, state);
|
|
247
|
+
return;
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
console.log(
|
|
251
|
+
`${c.cyan}Current Story:${c.reset} ${currentStoryId} - ${currentStory.title || 'Untitled'}`
|
|
252
|
+
);
|
|
253
|
+
console.log('');
|
|
254
|
+
|
|
255
|
+
// Run tests
|
|
256
|
+
const testCommand = getTestCommand(rootDir);
|
|
257
|
+
console.log(`${c.blue}Running:${c.reset} ${testCommand}`);
|
|
258
|
+
console.log(`${c.dim}${'─'.repeat(58)}${c.reset}`);
|
|
259
|
+
|
|
260
|
+
const testResult = runTests(rootDir, testCommand);
|
|
261
|
+
|
|
262
|
+
if (testResult.passed) {
|
|
263
|
+
console.log(`${c.green}✓ Tests passed${c.reset} (${(testResult.duration / 1000).toFixed(1)}s)`);
|
|
264
|
+
console.log('');
|
|
265
|
+
|
|
266
|
+
// Mark story complete
|
|
267
|
+
markStoryComplete(rootDir, currentStoryId);
|
|
268
|
+
console.log(`${c.green}✓ Marked ${currentStoryId} as completed${c.reset}`);
|
|
269
|
+
|
|
270
|
+
// Get next story
|
|
271
|
+
const nextStory = getNextStory(status, epicId, currentStoryId);
|
|
272
|
+
|
|
273
|
+
if (nextStory) {
|
|
274
|
+
// Move to next story
|
|
275
|
+
markStoryInProgress(rootDir, nextStory.id);
|
|
276
|
+
state.ralph_loop.current_story = nextStory.id;
|
|
277
|
+
state.ralph_loop.iteration = iteration;
|
|
278
|
+
saveSessionState(rootDir, state);
|
|
279
|
+
|
|
280
|
+
const progress = getEpicProgress(getStatus(rootDir), epicId);
|
|
281
|
+
console.log('');
|
|
282
|
+
console.log(`${c.cyan}━━━ Next Story ━━━${c.reset}`);
|
|
283
|
+
console.log(`${c.bold}${nextStory.id}:${c.reset} ${nextStory.title || 'Untitled'}`);
|
|
284
|
+
if (nextStory.acceptance_criteria) {
|
|
285
|
+
console.log(`${c.dim}Acceptance Criteria:${c.reset}`);
|
|
286
|
+
const criteria = Array.isArray(nextStory.acceptance_criteria)
|
|
287
|
+
? nextStory.acceptance_criteria
|
|
288
|
+
: [nextStory.acceptance_criteria];
|
|
289
|
+
criteria.slice(0, 3).forEach(ac => console.log(` • ${ac}`));
|
|
290
|
+
}
|
|
291
|
+
console.log('');
|
|
292
|
+
console.log(
|
|
293
|
+
`${c.dim}Epic Progress: ${progress.completed}/${progress.total} stories complete${c.reset}`
|
|
294
|
+
);
|
|
295
|
+
console.log('');
|
|
296
|
+
console.log(`${c.brand}▶ Continue implementing ${nextStory.id}${c.reset}`);
|
|
297
|
+
console.log(`${c.dim} Run tests when ready. Loop will validate and continue.${c.reset}`);
|
|
298
|
+
} else {
|
|
299
|
+
// No more stories - epic complete!
|
|
300
|
+
const progress = getEpicProgress(getStatus(rootDir), epicId);
|
|
301
|
+
state.ralph_loop.enabled = false;
|
|
302
|
+
state.ralph_loop.stopped_reason = 'epic_complete';
|
|
303
|
+
state.ralph_loop.completed_at = new Date().toISOString();
|
|
304
|
+
saveSessionState(rootDir, state);
|
|
305
|
+
|
|
306
|
+
console.log('');
|
|
307
|
+
console.log(
|
|
308
|
+
`${c.green}${c.bold}════════════════════════════════════════════════════════${c.reset}`
|
|
309
|
+
);
|
|
310
|
+
console.log(`${c.green}${c.bold} 🎉 EPIC COMPLETE!${c.reset}`);
|
|
311
|
+
console.log(
|
|
312
|
+
`${c.green}${c.bold}════════════════════════════════════════════════════════${c.reset}`
|
|
313
|
+
);
|
|
314
|
+
console.log('');
|
|
315
|
+
console.log(`${c.green}Epic ${epicId} finished in ${iteration} iterations${c.reset}`);
|
|
316
|
+
console.log(`${c.dim}${progress.completed} stories completed${c.reset}`);
|
|
317
|
+
console.log('');
|
|
318
|
+
}
|
|
319
|
+
} else {
|
|
320
|
+
// Tests failed - feed back to Claude
|
|
321
|
+
console.log(`${c.red}✗ Tests failed${c.reset} (${(testResult.duration / 1000).toFixed(1)}s)`);
|
|
322
|
+
console.log('');
|
|
323
|
+
|
|
324
|
+
state.ralph_loop.iteration = iteration;
|
|
325
|
+
state.ralph_loop.last_failure = new Date().toISOString();
|
|
326
|
+
saveSessionState(rootDir, state);
|
|
327
|
+
|
|
328
|
+
console.log(`${c.yellow}━━━ Test Failures ━━━${c.reset}`);
|
|
329
|
+
|
|
330
|
+
// Show truncated output (last 50 lines most relevant)
|
|
331
|
+
const outputLines = testResult.output.split('\n');
|
|
332
|
+
const relevantLines = outputLines.slice(-50);
|
|
333
|
+
console.log(relevantLines.join('\n'));
|
|
334
|
+
|
|
335
|
+
console.log('');
|
|
336
|
+
console.log(`${c.brand}▶ Fix the failing tests and continue${c.reset}`);
|
|
337
|
+
console.log(`${c.dim} Loop will re-run tests when you stop.${c.reset}`);
|
|
338
|
+
console.log(`${c.dim} Iteration ${iteration}/${maxIterations}${c.reset}`);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
console.log('');
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
// Handle CLI arguments
|
|
345
|
+
function handleCLI() {
|
|
346
|
+
const args = process.argv.slice(2);
|
|
347
|
+
const rootDir = getProjectRoot();
|
|
348
|
+
|
|
349
|
+
if (args.includes('--status')) {
|
|
350
|
+
const state = getSessionState(rootDir);
|
|
351
|
+
const loop = state.ralph_loop;
|
|
352
|
+
|
|
353
|
+
if (!loop || !loop.enabled) {
|
|
354
|
+
console.log(`${c.dim}Ralph Loop: not active${c.reset}`);
|
|
355
|
+
} else {
|
|
356
|
+
console.log(`${c.green}Ralph Loop: active${c.reset}`);
|
|
357
|
+
console.log(` Epic: ${loop.epic}`);
|
|
358
|
+
console.log(` Current Story: ${loop.current_story}`);
|
|
359
|
+
console.log(` Iteration: ${loop.iteration || 0}/${loop.max_iterations || 20}`);
|
|
360
|
+
}
|
|
361
|
+
return true;
|
|
362
|
+
}
|
|
363
|
+
|
|
364
|
+
if (args.includes('--stop')) {
|
|
365
|
+
const state = getSessionState(rootDir);
|
|
366
|
+
if (state.ralph_loop) {
|
|
367
|
+
state.ralph_loop.enabled = false;
|
|
368
|
+
state.ralph_loop.stopped_reason = 'manual';
|
|
369
|
+
saveSessionState(rootDir, state);
|
|
370
|
+
console.log(`${c.yellow}Ralph Loop stopped${c.reset}`);
|
|
371
|
+
} else {
|
|
372
|
+
console.log(`${c.dim}Ralph Loop was not active${c.reset}`);
|
|
373
|
+
}
|
|
374
|
+
return true;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
if (args.includes('--reset')) {
|
|
378
|
+
const state = getSessionState(rootDir);
|
|
379
|
+
delete state.ralph_loop;
|
|
380
|
+
saveSessionState(rootDir, state);
|
|
381
|
+
console.log(`${c.green}Ralph Loop state reset${c.reset}`);
|
|
382
|
+
return true;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
// Handle --init
|
|
386
|
+
if (args.some(a => a.startsWith('--init'))) {
|
|
387
|
+
const epicArg = args.find(a => a.startsWith('--epic='));
|
|
388
|
+
const maxArg = args.find(a => a.startsWith('--max='));
|
|
389
|
+
|
|
390
|
+
if (!epicArg) {
|
|
391
|
+
console.log(`${c.red}Error: --epic=EP-XXXX is required${c.reset}`);
|
|
392
|
+
return true;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
const epicId = epicArg.split('=')[1];
|
|
396
|
+
const maxIterations = maxArg ? parseInt(maxArg.split('=')[1]) : 20;
|
|
397
|
+
|
|
398
|
+
// Find first ready story in epic
|
|
399
|
+
const status = getStatus(rootDir);
|
|
400
|
+
const stories = status.stories || {};
|
|
401
|
+
const readyStories = Object.entries(stories)
|
|
402
|
+
.filter(([_, s]) => s.epic === epicId && s.status === 'ready')
|
|
403
|
+
.sort((a, b) => {
|
|
404
|
+
const numA = parseInt(a[0].replace(/\D/g, '')) || 0;
|
|
405
|
+
const numB = parseInt(b[0].replace(/\D/g, '')) || 0;
|
|
406
|
+
return numA - numB;
|
|
407
|
+
});
|
|
408
|
+
|
|
409
|
+
if (readyStories.length === 0) {
|
|
410
|
+
console.log(`${c.yellow}No ready stories found in ${epicId}${c.reset}`);
|
|
411
|
+
console.log(`${c.dim}Create stories with status "ready" first${c.reset}`);
|
|
412
|
+
return true;
|
|
413
|
+
}
|
|
414
|
+
|
|
415
|
+
const firstStory = readyStories[0];
|
|
416
|
+
const storyId = firstStory[0];
|
|
417
|
+
|
|
418
|
+
// Mark first story as in_progress
|
|
419
|
+
markStoryInProgress(rootDir, storyId);
|
|
420
|
+
|
|
421
|
+
// Initialize loop state
|
|
422
|
+
const state = getSessionState(rootDir);
|
|
423
|
+
state.ralph_loop = {
|
|
424
|
+
enabled: true,
|
|
425
|
+
epic: epicId,
|
|
426
|
+
current_story: storyId,
|
|
427
|
+
iteration: 0,
|
|
428
|
+
max_iterations: maxIterations,
|
|
429
|
+
started_at: new Date().toISOString(),
|
|
430
|
+
};
|
|
431
|
+
saveSessionState(rootDir, state);
|
|
432
|
+
|
|
433
|
+
const progress = getEpicProgress(status, epicId);
|
|
434
|
+
|
|
435
|
+
console.log('');
|
|
436
|
+
console.log(`${c.green}${c.bold}Ralph Loop Initialized${c.reset}`);
|
|
437
|
+
console.log(`${c.dim}${'─'.repeat(40)}${c.reset}`);
|
|
438
|
+
console.log(` Epic: ${c.cyan}${epicId}${c.reset}`);
|
|
439
|
+
console.log(` Stories: ${progress.ready} ready, ${progress.total} total`);
|
|
440
|
+
console.log(` Max Iterations: ${maxIterations}`);
|
|
441
|
+
console.log(`${c.dim}${'─'.repeat(40)}${c.reset}`);
|
|
442
|
+
console.log('');
|
|
443
|
+
console.log(`${c.brand}▶ Starting Story:${c.reset} ${storyId}`);
|
|
444
|
+
console.log(` ${firstStory[1].title || 'Untitled'}`);
|
|
445
|
+
if (firstStory[1].acceptance_criteria) {
|
|
446
|
+
const criteria = Array.isArray(firstStory[1].acceptance_criteria)
|
|
447
|
+
? firstStory[1].acceptance_criteria
|
|
448
|
+
: [firstStory[1].acceptance_criteria];
|
|
449
|
+
console.log(`${c.dim} Acceptance Criteria:${c.reset}`);
|
|
450
|
+
criteria.slice(0, 3).forEach(ac => console.log(` • ${ac}`));
|
|
451
|
+
}
|
|
452
|
+
console.log('');
|
|
453
|
+
console.log(
|
|
454
|
+
`${c.dim}Work on this story. When you stop, tests will run automatically.${c.reset}`
|
|
455
|
+
);
|
|
456
|
+
console.log(`${c.dim}If tests pass, the next story will be loaded.${c.reset}`);
|
|
457
|
+
console.log('');
|
|
458
|
+
|
|
459
|
+
return true;
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
if (args.includes('--help')) {
|
|
463
|
+
console.log(`
|
|
464
|
+
${c.brand}${c.bold}ralph-loop.js${c.reset} - Autonomous Story Processing
|
|
465
|
+
|
|
466
|
+
${c.bold}Usage:${c.reset}
|
|
467
|
+
node scripts/ralph-loop.js Run loop check (Stop hook)
|
|
468
|
+
node scripts/ralph-loop.js --init --epic=EP-XXX Initialize loop for epic
|
|
469
|
+
node scripts/ralph-loop.js --status Check loop status
|
|
470
|
+
node scripts/ralph-loop.js --stop Stop the loop
|
|
471
|
+
node scripts/ralph-loop.js --reset Reset loop state
|
|
472
|
+
|
|
473
|
+
${c.bold}Options:${c.reset}
|
|
474
|
+
--epic=EP-XXXX Epic ID to process (required for --init)
|
|
475
|
+
--max=N Max iterations (default: 20)
|
|
476
|
+
|
|
477
|
+
${c.bold}How it works:${c.reset}
|
|
478
|
+
1. Start loop with /agileflow:babysit EPIC=EP-XXX MODE=loop
|
|
479
|
+
2. Work on the current story
|
|
480
|
+
3. When you stop, this hook runs tests
|
|
481
|
+
4. If tests pass → story marked complete, next story loaded
|
|
482
|
+
5. If tests fail → failures shown, you continue fixing
|
|
483
|
+
6. Loop repeats until epic done or max iterations
|
|
484
|
+
`);
|
|
485
|
+
return true;
|
|
486
|
+
}
|
|
487
|
+
|
|
488
|
+
return false;
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
// Main
|
|
492
|
+
function main() {
|
|
493
|
+
// Handle CLI commands first
|
|
494
|
+
if (handleCLI()) {
|
|
495
|
+
return;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
// Otherwise run the loop handler
|
|
499
|
+
const rootDir = getProjectRoot();
|
|
500
|
+
handleLoop(rootDir);
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
main();
|
|
@@ -22,12 +22,16 @@
|
|
|
22
22
|
|
|
23
23
|
set -e
|
|
24
24
|
|
|
25
|
-
# Colors
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
GREEN='\033[0;32m'
|
|
29
|
-
|
|
30
|
-
|
|
25
|
+
# Colors (using vibrant 256-color palette)
|
|
26
|
+
NC='\033[0m' # No Color / Reset
|
|
27
|
+
RED='\033[0;31m' # Standard red (fallback)
|
|
28
|
+
GREEN='\033[0;32m' # Standard green (fallback)
|
|
29
|
+
|
|
30
|
+
# Vibrant 256-color palette
|
|
31
|
+
MINT_GREEN='\033[38;5;158m' # Healthy/success states
|
|
32
|
+
PEACH='\033[38;5;215m' # Warning states
|
|
33
|
+
CORAL='\033[38;5;203m' # Critical/error states
|
|
34
|
+
SKY_BLUE='\033[38;5;117m' # Headers/titles
|
|
31
35
|
|
|
32
36
|
# Configuration
|
|
33
37
|
EXPERTS_DIR="packages/cli/src/core/experts"
|
|
@@ -180,18 +184,18 @@ validate_expertise() {
|
|
|
180
184
|
fi
|
|
181
185
|
fi
|
|
182
186
|
|
|
183
|
-
# Output result
|
|
187
|
+
# Output result (using vibrant 256-color palette)
|
|
184
188
|
case "$status" in
|
|
185
189
|
PASS)
|
|
186
|
-
echo -e "${
|
|
190
|
+
echo -e "${MINT_GREEN}PASS${NC} $domain"
|
|
187
191
|
PASSED=$((PASSED + 1))
|
|
188
192
|
;;
|
|
189
193
|
WARN)
|
|
190
|
-
echo -e "${
|
|
194
|
+
echo -e "${PEACH}WARN${NC} $domain - ${issues[*]}"
|
|
191
195
|
WARNINGS=$((WARNINGS + 1))
|
|
192
196
|
;;
|
|
193
197
|
FAIL)
|
|
194
|
-
echo -e "${
|
|
198
|
+
echo -e "${CORAL}FAIL${NC} $domain - ${issues[*]}"
|
|
195
199
|
FAILED=$((FAILED + 1))
|
|
196
200
|
;;
|
|
197
201
|
esac
|
|
@@ -212,12 +216,12 @@ main() {
|
|
|
212
216
|
|
|
213
217
|
# Check experts directory exists
|
|
214
218
|
if [ ! -d "$EXPERTS_DIR" ]; then
|
|
215
|
-
echo -e "${
|
|
219
|
+
echo -e "${CORAL}Error:${NC} Experts directory not found: $EXPERTS_DIR"
|
|
216
220
|
echo "Are you running this from the repository root?"
|
|
217
221
|
exit 1
|
|
218
222
|
fi
|
|
219
223
|
|
|
220
|
-
echo -e "${
|
|
224
|
+
echo -e "${SKY_BLUE}Validating Agent Expert Files${NC}"
|
|
221
225
|
echo "================================"
|
|
222
226
|
echo ""
|
|
223
227
|
|
|
@@ -225,7 +229,7 @@ main() {
|
|
|
225
229
|
if [ -n "$1" ]; then
|
|
226
230
|
# Single domain
|
|
227
231
|
if [ ! -d "$EXPERTS_DIR/$1" ]; then
|
|
228
|
-
echo -e "${
|
|
232
|
+
echo -e "${CORAL}Error:${NC} Domain not found: $1"
|
|
229
233
|
echo "Available domains:"
|
|
230
234
|
ls -1 "$EXPERTS_DIR" | grep -v templates | grep -v README
|
|
231
235
|
exit 1
|
|
@@ -244,10 +248,10 @@ main() {
|
|
|
244
248
|
done
|
|
245
249
|
fi
|
|
246
250
|
|
|
247
|
-
# Summary
|
|
251
|
+
# Summary (using vibrant 256-color palette)
|
|
248
252
|
echo ""
|
|
249
253
|
echo "================================"
|
|
250
|
-
echo -e "Total: $TOTAL | ${
|
|
254
|
+
echo -e "Total: $TOTAL | ${MINT_GREEN}Passed: $PASSED${NC} | ${PEACH}Warnings: $WARNINGS${NC} | ${CORAL}Failed: $FAILED${NC}"
|
|
251
255
|
|
|
252
256
|
# Exit code
|
|
253
257
|
if [ "$FAILED" -gt 0 ]; then
|
|
@@ -70,7 +70,7 @@ FIRST ACTION PROTOCOL:
|
|
|
70
70
|
4. For complete features: Use workflow.md (Plan → Build → Self-Improve)
|
|
71
71
|
5. After work: Run self-improve.md to update expertise
|
|
72
72
|
|
|
73
|
-
SLASH COMMANDS: /agileflow:context, /agileflow:ai-code-review, /agileflow:adr-new, /agileflow:status
|
|
73
|
+
SLASH COMMANDS: /agileflow:context:full, /agileflow:ai-code-review, /agileflow:adr-new, /agileflow:status
|
|
74
74
|
<!-- COMPACT_SUMMARY_END -->
|
|
75
75
|
|
|
76
76
|
You are AG-DESIGN, the Design Specialist for AgileFlow projects.
|
|
@@ -76,7 +76,7 @@ DOCUMENTATION PRINCIPLES:
|
|
|
76
76
|
- Include troubleshooting (users will have problems)
|
|
77
77
|
- Document breaking changes (critical for users)
|
|
78
78
|
|
|
79
|
-
SLASH COMMANDS: /agileflow:context, /agileflow:ai-code-review, /agileflow:adr-new, /agileflow:status
|
|
79
|
+
SLASH COMMANDS: /agileflow:context:full, /agileflow:ai-code-review, /agileflow:adr-new, /agileflow:status
|
|
80
80
|
<!-- COMPACT_SUMMARY_END -->
|
|
81
81
|
|
|
82
82
|
You are AG-DOCUMENTATION, the Documentation Specialist for AgileFlow projects.
|
|
@@ -82,7 +82,7 @@ FIRST ACTION PROTOCOL:
|
|
|
82
82
|
4. For complete features: Use workflow.md (Plan → Build → Self-Improve)
|
|
83
83
|
5. After work: Run self-improve.md to update expertise
|
|
84
84
|
|
|
85
|
-
SLASH COMMANDS: /agileflow:context, /agileflow:ai-code-review, /agileflow:adr-new, /agileflow:tech-debt, /agileflow:status
|
|
85
|
+
SLASH COMMANDS: /agileflow:context:full, /agileflow:ai-code-review, /agileflow:adr-new, /agileflow:tech-debt, /agileflow:status
|
|
86
86
|
<!-- COMPACT_SUMMARY_END -->
|
|
87
87
|
|
|
88
88
|
You are AG-INTEGRATIONS, the Integration Specialist for AgileFlow projects.
|
|
@@ -84,7 +84,7 @@ FIRST ACTION PROTOCOL:
|
|
|
84
84
|
4. For complete features: Use workflow.md (Plan → Build → Self-Improve)
|
|
85
85
|
5. After work: Run self-improve.md to update expertise
|
|
86
86
|
|
|
87
|
-
SLASH COMMANDS: /agileflow:context, /agileflow:ai-code-review, /agileflow:adr-new, /agileflow:tech-debt, /agileflow:status
|
|
87
|
+
SLASH COMMANDS: /agileflow:context:full, /agileflow:ai-code-review, /agileflow:adr-new, /agileflow:tech-debt, /agileflow:status
|
|
88
88
|
<!-- COMPACT_SUMMARY_END -->
|
|
89
89
|
|
|
90
90
|
You are AG-MOBILE, the Mobile Specialist for AgileFlow projects.
|
|
@@ -84,7 +84,7 @@ FIRST ACTION PROTOCOL:
|
|
|
84
84
|
4. For complete features: Use workflow.md (Plan → Build → Self-Improve)
|
|
85
85
|
5. After work: Run self-improve.md to update expertise
|
|
86
86
|
|
|
87
|
-
SLASH COMMANDS: /agileflow:context, /agileflow:ai-code-review, /agileflow:adr-new, /agileflow:status
|
|
87
|
+
SLASH COMMANDS: /agileflow:context:full, /agileflow:ai-code-review, /agileflow:adr-new, /agileflow:status
|
|
88
88
|
<!-- COMPACT_SUMMARY_END -->
|
|
89
89
|
|
|
90
90
|
You are AG-MONITORING, the Monitoring & Observability Specialist for AgileFlow projects.
|
|
@@ -91,7 +91,7 @@ FIRST ACTION PROTOCOL:
|
|
|
91
91
|
|
|
92
92
|
PLAN MODE REQUIRED: Performance work requires measurement first. Always use EnterPlanMode to profile before optimizing.
|
|
93
93
|
|
|
94
|
-
SLASH COMMANDS: /agileflow:context, /agileflow:ai-code-review, /agileflow:adr-new, /agileflow:tech-debt, /agileflow:impact-analysis, /agileflow:status
|
|
94
|
+
SLASH COMMANDS: /agileflow:context:full, /agileflow:ai-code-review, /agileflow:adr-new, /agileflow:tech-debt, /agileflow:impact-analysis, /agileflow:status
|
|
95
95
|
<!-- COMPACT_SUMMARY_END -->
|
|
96
96
|
|
|
97
97
|
You are AG-PERFORMANCE, the Performance Specialist for AgileFlow projects.
|