agileflow 2.77.0 → 2.79.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/README.md +6 -6
- package/package.json +6 -1
- package/scripts/agileflow-configure.js +174 -2
- package/scripts/agileflow-statusline.sh +171 -78
- package/scripts/agileflow-welcome.js +88 -64
- package/scripts/auto-self-improve.js +23 -45
- package/scripts/check-update.js +35 -42
- package/scripts/damage-control/bash-tool-damage-control.js +257 -0
- package/scripts/damage-control/edit-tool-damage-control.js +279 -0
- package/scripts/damage-control/patterns.yaml +227 -0
- package/scripts/damage-control/write-tool-damage-control.js +274 -0
- package/scripts/damage-control-bash.js +232 -0
- package/scripts/damage-control-edit.js +243 -0
- package/scripts/damage-control-write.js +243 -0
- package/scripts/obtain-context.js +22 -3
- package/scripts/ralph-loop.js +191 -63
- package/scripts/screenshot-verifier.js +213 -0
- package/scripts/session-manager.js +12 -33
- package/src/core/agents/accessibility.md +124 -53
- package/src/core/agents/adr-writer.md +192 -52
- package/src/core/agents/analytics.md +139 -60
- package/src/core/agents/api.md +173 -63
- package/src/core/agents/ci.md +139 -57
- package/src/core/agents/compliance.md +159 -68
- package/src/core/agents/configuration/damage-control.md +356 -0
- package/src/core/agents/configuration-damage-control.md +248 -0
- package/src/core/agents/database.md +162 -61
- package/src/core/agents/datamigration.md +179 -66
- package/src/core/agents/design.md +179 -57
- package/src/core/agents/devops.md +160 -3
- package/src/core/agents/documentation.md +204 -60
- package/src/core/agents/epic-planner.md +147 -55
- package/src/core/agents/integrations.md +197 -69
- package/src/core/agents/mentor.md +158 -57
- package/src/core/agents/mobile.md +159 -67
- package/src/core/agents/monitoring.md +154 -65
- package/src/core/agents/multi-expert.md +115 -43
- package/src/core/agents/orchestrator.md +77 -24
- package/src/core/agents/performance.md +130 -75
- package/src/core/agents/product.md +151 -55
- package/src/core/agents/qa.md +162 -74
- package/src/core/agents/readme-updater.md +178 -76
- package/src/core/agents/refactor.md +148 -95
- package/src/core/agents/research.md +143 -72
- package/src/core/agents/security.md +154 -65
- package/src/core/agents/testing.md +176 -97
- package/src/core/agents/ui.md +170 -79
- package/src/core/commands/adr/list.md +171 -0
- package/src/core/commands/adr/update.md +235 -0
- package/src/core/commands/adr/view.md +252 -0
- package/src/core/commands/adr.md +207 -50
- package/src/core/commands/agent.md +16 -0
- package/src/core/commands/assign.md +148 -44
- package/src/core/commands/auto.md +18 -1
- package/src/core/commands/babysit.md +391 -38
- package/src/core/commands/baseline.md +14 -0
- package/src/core/commands/blockers.md +170 -51
- package/src/core/commands/board.md +144 -66
- package/src/core/commands/changelog.md +15 -0
- package/src/core/commands/ci.md +179 -69
- package/src/core/commands/compress.md +18 -0
- package/src/core/commands/configure.md +16 -0
- package/src/core/commands/context/export.md +193 -4
- package/src/core/commands/context/full.md +191 -18
- package/src/core/commands/context/note.md +248 -4
- package/src/core/commands/debt.md +17 -0
- package/src/core/commands/deploy.md +208 -65
- package/src/core/commands/deps.md +15 -0
- package/src/core/commands/diagnose.md +16 -0
- package/src/core/commands/docs.md +196 -64
- package/src/core/commands/epic/list.md +170 -0
- package/src/core/commands/epic/view.md +242 -0
- package/src/core/commands/epic.md +192 -69
- package/src/core/commands/feedback.md +191 -71
- package/src/core/commands/handoff.md +162 -48
- package/src/core/commands/help.md +9 -0
- package/src/core/commands/ideate.md +446 -0
- package/src/core/commands/impact.md +16 -0
- package/src/core/commands/metrics.md +141 -37
- package/src/core/commands/multi-expert.md +77 -0
- package/src/core/commands/packages.md +16 -0
- package/src/core/commands/pr.md +161 -67
- package/src/core/commands/readme-sync.md +16 -0
- package/src/core/commands/research/analyze.md +568 -0
- package/src/core/commands/research/ask.md +345 -20
- package/src/core/commands/research/import.md +562 -19
- package/src/core/commands/research/list.md +173 -5
- package/src/core/commands/research/view.md +181 -8
- package/src/core/commands/retro.md +135 -48
- package/src/core/commands/review.md +219 -47
- package/src/core/commands/session/end.md +209 -0
- package/src/core/commands/session/history.md +210 -0
- package/src/core/commands/session/init.md +116 -0
- package/src/core/commands/session/new.md +296 -0
- package/src/core/commands/session/resume.md +166 -0
- package/src/core/commands/session/status.md +166 -0
- package/src/core/commands/setup/visual-e2e.md +462 -0
- package/src/core/commands/skill/create.md +115 -17
- package/src/core/commands/skill/delete.md +117 -0
- package/src/core/commands/skill/edit.md +104 -0
- package/src/core/commands/skill/list.md +128 -0
- package/src/core/commands/skill/test.md +135 -0
- package/src/core/commands/skill/upgrade.md +542 -0
- package/src/core/commands/sprint.md +17 -1
- package/src/core/commands/status.md +133 -21
- package/src/core/commands/story/list.md +176 -0
- package/src/core/commands/story/view.md +265 -0
- package/src/core/commands/story-validate.md +101 -1
- package/src/core/commands/story.md +204 -51
- package/src/core/commands/template.md +16 -1
- package/src/core/commands/tests.md +226 -64
- package/src/core/commands/update.md +17 -1
- package/src/core/commands/validate-expertise.md +16 -0
- package/src/core/commands/velocity.md +140 -36
- package/src/core/commands/verify.md +14 -0
- package/src/core/commands/whats-new.md +30 -0
- package/src/core/skills/_learnings/README.md +91 -0
- package/src/core/skills/_learnings/_template.yaml +106 -0
- package/src/core/skills/_learnings/code-review.yaml +118 -0
- package/src/core/skills/_learnings/commit.yaml +69 -0
- package/src/core/skills/_learnings/story-writer.yaml +71 -0
- package/src/core/templates/damage-control-patterns.yaml +234 -0
- package/src/core/templates/skill-template.md +53 -11
- package/tools/cli/commands/start.js +180 -0
- package/tools/cli/installers/ide/claude-code.js +127 -0
- package/tools/cli/tui/Dashboard.js +66 -0
- package/tools/cli/tui/StoryList.js +69 -0
- package/tools/cli/tui/index.js +16 -0
package/scripts/ralph-loop.js
CHANGED
|
@@ -7,12 +7,18 @@
|
|
|
7
7
|
* It runs as a Stop hook and handles:
|
|
8
8
|
* 1. Checking if loop mode is enabled
|
|
9
9
|
* 2. Running test validation
|
|
10
|
-
* 3.
|
|
11
|
-
* 4.
|
|
12
|
-
* 5.
|
|
10
|
+
* 3. Running screenshot verification (Visual Mode)
|
|
11
|
+
* 4. Updating story status on success
|
|
12
|
+
* 5. Feeding context back for next iteration
|
|
13
|
+
* 6. Tracking iterations and enforcing limits
|
|
13
14
|
*
|
|
14
15
|
* Named after the "Ralph Wiggum" pattern from Anthropic.
|
|
15
16
|
*
|
|
17
|
+
* Visual Mode:
|
|
18
|
+
* When visual_mode is enabled, the loop also verifies that all
|
|
19
|
+
* screenshots have been reviewed (verified- prefix). This ensures
|
|
20
|
+
* Claude actually looks at UI screenshots before declaring completion.
|
|
21
|
+
*
|
|
16
22
|
* Usage (as Stop hook):
|
|
17
23
|
* node scripts/ralph-loop.js
|
|
18
24
|
*
|
|
@@ -26,78 +32,46 @@ const fs = require('fs');
|
|
|
26
32
|
const path = require('path');
|
|
27
33
|
const { execSync, spawnSync } = require('child_process');
|
|
28
34
|
|
|
29
|
-
//
|
|
30
|
-
const c =
|
|
31
|
-
|
|
32
|
-
|
|
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
|
-
}
|
|
35
|
+
// Shared utilities
|
|
36
|
+
const { c } = require('../lib/colors');
|
|
37
|
+
const { getProjectRoot } = require('../lib/paths');
|
|
38
|
+
const { safeReadJSON, safeWriteJSON } = require('../lib/errors');
|
|
50
39
|
|
|
51
40
|
// Read session state
|
|
52
41
|
function getSessionState(rootDir) {
|
|
53
42
|
const statePath = path.join(rootDir, 'docs/09-agents/session-state.json');
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
return JSON.parse(fs.readFileSync(statePath, 'utf8'));
|
|
57
|
-
}
|
|
58
|
-
} catch (e) {}
|
|
59
|
-
return {};
|
|
43
|
+
const result = safeReadJSON(statePath, { defaultValue: {} });
|
|
44
|
+
return result.ok ? result.data : {};
|
|
60
45
|
}
|
|
61
46
|
|
|
62
47
|
// Write session state
|
|
63
48
|
function saveSessionState(rootDir, state) {
|
|
64
49
|
const statePath = path.join(rootDir, 'docs/09-agents/session-state.json');
|
|
65
|
-
|
|
66
|
-
if (!fs.existsSync(dir)) {
|
|
67
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
68
|
-
}
|
|
69
|
-
fs.writeFileSync(statePath, JSON.stringify(state, null, 2) + '\n');
|
|
50
|
+
safeWriteJSON(statePath, state, { createDir: true });
|
|
70
51
|
}
|
|
71
52
|
|
|
72
53
|
// Read status.json for stories
|
|
73
54
|
function getStatus(rootDir) {
|
|
74
55
|
const statusPath = path.join(rootDir, 'docs/09-agents/status.json');
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
return JSON.parse(fs.readFileSync(statusPath, 'utf8'));
|
|
78
|
-
}
|
|
79
|
-
} catch (e) {}
|
|
80
|
-
return { stories: {}, epics: {} };
|
|
56
|
+
const result = safeReadJSON(statusPath, { defaultValue: { stories: {}, epics: {} } });
|
|
57
|
+
return result.ok ? result.data : { stories: {}, epics: {} };
|
|
81
58
|
}
|
|
82
59
|
|
|
83
60
|
// Save status.json
|
|
84
61
|
function saveStatus(rootDir, status) {
|
|
85
62
|
const statusPath = path.join(rootDir, 'docs/09-agents/status.json');
|
|
86
|
-
|
|
63
|
+
safeWriteJSON(statusPath, status);
|
|
87
64
|
}
|
|
88
65
|
|
|
89
66
|
// Get test command from package.json or metadata
|
|
90
67
|
function getTestCommand(rootDir) {
|
|
91
68
|
// Check agileflow metadata first
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
}
|
|
99
|
-
}
|
|
100
|
-
} catch (e) {}
|
|
69
|
+
const metadataPath = path.join(rootDir, 'docs/00-meta/agileflow-metadata.json');
|
|
70
|
+
const result = safeReadJSON(metadataPath, { defaultValue: {} });
|
|
71
|
+
|
|
72
|
+
if (result.ok && result.data?.ralph_loop?.test_command) {
|
|
73
|
+
return result.data.ralph_loop.test_command;
|
|
74
|
+
}
|
|
101
75
|
|
|
102
76
|
// Default to npm test
|
|
103
77
|
return 'npm test';
|
|
@@ -129,6 +103,73 @@ function runTests(rootDir, testCommand) {
|
|
|
129
103
|
return result;
|
|
130
104
|
}
|
|
131
105
|
|
|
106
|
+
// Get screenshots directory from metadata or default
|
|
107
|
+
function getScreenshotsDir(rootDir) {
|
|
108
|
+
try {
|
|
109
|
+
const metadataPath = path.join(rootDir, 'docs/00-meta/agileflow-metadata.json');
|
|
110
|
+
if (fs.existsSync(metadataPath)) {
|
|
111
|
+
const metadata = JSON.parse(fs.readFileSync(metadataPath, 'utf8'));
|
|
112
|
+
if (metadata.ralph_loop?.screenshots_dir) {
|
|
113
|
+
return metadata.ralph_loop.screenshots_dir;
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
} catch (e) {}
|
|
117
|
+
return './screenshots';
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Run screenshot verification (Visual Mode)
|
|
121
|
+
function verifyScreenshots(rootDir) {
|
|
122
|
+
const result = { passed: false, output: '', total: 0, verified: 0, unverified: [] };
|
|
123
|
+
const screenshotsDir = getScreenshotsDir(rootDir);
|
|
124
|
+
const fullPath = path.resolve(rootDir, screenshotsDir);
|
|
125
|
+
|
|
126
|
+
// Check if directory exists
|
|
127
|
+
if (!fs.existsSync(fullPath)) {
|
|
128
|
+
result.passed = true; // No screenshots = nothing to verify
|
|
129
|
+
result.output = 'No screenshots directory found';
|
|
130
|
+
return result;
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Get all image files
|
|
134
|
+
const imageExtensions = ['.png', '.jpg', '.jpeg', '.webp', '.gif'];
|
|
135
|
+
let files;
|
|
136
|
+
try {
|
|
137
|
+
files = fs.readdirSync(fullPath).filter((file) => {
|
|
138
|
+
const ext = path.extname(file).toLowerCase();
|
|
139
|
+
return imageExtensions.includes(ext);
|
|
140
|
+
});
|
|
141
|
+
} catch (e) {
|
|
142
|
+
result.output = `Error reading screenshots directory: ${e.message}`;
|
|
143
|
+
return result;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (files.length === 0) {
|
|
147
|
+
result.passed = true;
|
|
148
|
+
result.output = 'No screenshots found in directory';
|
|
149
|
+
return result;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
// Check each file for verified- prefix
|
|
153
|
+
for (const file of files) {
|
|
154
|
+
if (file.startsWith('verified-')) {
|
|
155
|
+
result.verified++;
|
|
156
|
+
} else {
|
|
157
|
+
result.unverified.push(file);
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
result.total = files.length;
|
|
162
|
+
result.passed = result.unverified.length === 0;
|
|
163
|
+
|
|
164
|
+
if (result.passed) {
|
|
165
|
+
result.output = `All ${result.total} screenshots verified`;
|
|
166
|
+
} else {
|
|
167
|
+
result.output = `${result.unverified.length} screenshots missing 'verified-' prefix`;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
return result;
|
|
171
|
+
}
|
|
172
|
+
|
|
132
173
|
// Get next ready story in epic
|
|
133
174
|
function getNextStory(status, epicId, currentStoryId) {
|
|
134
175
|
const stories = status.stories || {};
|
|
@@ -211,13 +252,16 @@ function handleLoop(rootDir) {
|
|
|
211
252
|
const status = getStatus(rootDir);
|
|
212
253
|
const iteration = (loop.iteration || 0) + 1;
|
|
213
254
|
const maxIterations = loop.max_iterations || 20;
|
|
255
|
+
const visualMode = loop.visual_mode || false;
|
|
256
|
+
const minIterations = visualMode ? 2 : 1; // Visual mode requires at least 2 iterations
|
|
214
257
|
|
|
215
258
|
console.log('');
|
|
216
259
|
console.log(
|
|
217
260
|
`${c.brand}${c.bold}══════════════════════════════════════════════════════════${c.reset}`
|
|
218
261
|
);
|
|
262
|
+
const modeLabel = visualMode ? ' [VISUAL MODE]' : '';
|
|
219
263
|
console.log(
|
|
220
|
-
`${c.brand}${c.bold} RALPH LOOP - Iteration ${iteration}/${maxIterations}${c.reset}`
|
|
264
|
+
`${c.brand}${c.bold} RALPH LOOP - Iteration ${iteration}/${maxIterations}${modeLabel}${c.reset}`
|
|
221
265
|
);
|
|
222
266
|
console.log(
|
|
223
267
|
`${c.brand}${c.bold}══════════════════════════════════════════════════════════${c.reset}`
|
|
@@ -261,6 +305,61 @@ function handleLoop(rootDir) {
|
|
|
261
305
|
|
|
262
306
|
if (testResult.passed) {
|
|
263
307
|
console.log(`${c.green}✓ Tests passed${c.reset} (${(testResult.duration / 1000).toFixed(1)}s)`);
|
|
308
|
+
|
|
309
|
+
// Visual Mode: Also verify screenshots
|
|
310
|
+
let screenshotResult = { passed: true };
|
|
311
|
+
if (visualMode) {
|
|
312
|
+
console.log('');
|
|
313
|
+
console.log(`${c.blue}Verifying screenshots...${c.reset}`);
|
|
314
|
+
screenshotResult = verifyScreenshots(rootDir);
|
|
315
|
+
|
|
316
|
+
if (screenshotResult.passed) {
|
|
317
|
+
console.log(`${c.green}✓ ${screenshotResult.output}${c.reset}`);
|
|
318
|
+
state.ralph_loop.screenshots_verified = true;
|
|
319
|
+
} else {
|
|
320
|
+
console.log(`${c.yellow}⚠ ${screenshotResult.output}${c.reset}`);
|
|
321
|
+
if (screenshotResult.unverified.length > 0) {
|
|
322
|
+
console.log(`${c.dim}Unverified screenshots:${c.reset}`);
|
|
323
|
+
screenshotResult.unverified.slice(0, 5).forEach((file) => {
|
|
324
|
+
console.log(` ${c.yellow}- ${file}${c.reset}`);
|
|
325
|
+
});
|
|
326
|
+
if (screenshotResult.unverified.length > 5) {
|
|
327
|
+
console.log(` ${c.dim}... and ${screenshotResult.unverified.length - 5} more${c.reset}`);
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
state.ralph_loop.screenshots_verified = false;
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// Visual Mode: Enforce minimum iterations
|
|
335
|
+
if (visualMode && iteration < minIterations) {
|
|
336
|
+
console.log('');
|
|
337
|
+
console.log(`${c.yellow}⚠ Visual Mode requires ${minIterations}+ iterations for confirmation${c.reset}`);
|
|
338
|
+
console.log(`${c.dim}Current: iteration ${iteration}. Let loop run once more to confirm.${c.reset}`);
|
|
339
|
+
|
|
340
|
+
state.ralph_loop.iteration = iteration;
|
|
341
|
+
saveSessionState(rootDir, state);
|
|
342
|
+
|
|
343
|
+
console.log('');
|
|
344
|
+
console.log(`${c.brand}▶ Continue reviewing. Loop will verify again.${c.reset}`);
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
// Check if both tests AND screenshots (in visual mode) passed
|
|
349
|
+
const canComplete = testResult.passed && (!visualMode || screenshotResult.passed);
|
|
350
|
+
|
|
351
|
+
if (!canComplete) {
|
|
352
|
+
// Screenshots not verified yet
|
|
353
|
+
state.ralph_loop.iteration = iteration;
|
|
354
|
+
saveSessionState(rootDir, state);
|
|
355
|
+
|
|
356
|
+
console.log('');
|
|
357
|
+
console.log(`${c.cyan}▶ Review unverified screenshots:${c.reset}`);
|
|
358
|
+
console.log(`${c.dim} 1. View each screenshot in screenshots/ directory${c.reset}`);
|
|
359
|
+
console.log(`${c.dim} 2. Rename verified files with 'verified-' prefix${c.reset}`);
|
|
360
|
+
console.log(`${c.dim} 3. Loop will re-verify when you stop${c.reset}`);
|
|
361
|
+
return;
|
|
362
|
+
}
|
|
264
363
|
console.log('');
|
|
265
364
|
|
|
266
365
|
// Mark story complete
|
|
@@ -353,10 +452,15 @@ function handleCLI() {
|
|
|
353
452
|
if (!loop || !loop.enabled) {
|
|
354
453
|
console.log(`${c.dim}Ralph Loop: not active${c.reset}`);
|
|
355
454
|
} else {
|
|
356
|
-
|
|
455
|
+
const modeLabel = loop.visual_mode ? ` ${c.cyan}[VISUAL]${c.reset}` : '';
|
|
456
|
+
console.log(`${c.green}Ralph Loop: active${c.reset}${modeLabel}`);
|
|
357
457
|
console.log(` Epic: ${loop.epic}`);
|
|
358
458
|
console.log(` Current Story: ${loop.current_story}`);
|
|
359
459
|
console.log(` Iteration: ${loop.iteration || 0}/${loop.max_iterations || 20}`);
|
|
460
|
+
if (loop.visual_mode) {
|
|
461
|
+
const verified = loop.screenshots_verified ? `${c.green}yes${c.reset}` : `${c.yellow}no${c.reset}`;
|
|
462
|
+
console.log(` Screenshots Verified: ${verified}`);
|
|
463
|
+
}
|
|
360
464
|
}
|
|
361
465
|
return true;
|
|
362
466
|
}
|
|
@@ -386,6 +490,7 @@ function handleCLI() {
|
|
|
386
490
|
if (args.some(a => a.startsWith('--init'))) {
|
|
387
491
|
const epicArg = args.find(a => a.startsWith('--epic='));
|
|
388
492
|
const maxArg = args.find(a => a.startsWith('--max='));
|
|
493
|
+
const visualArg = args.includes('--visual') || args.includes('-v');
|
|
389
494
|
|
|
390
495
|
if (!epicArg) {
|
|
391
496
|
console.log(`${c.red}Error: --epic=EP-XXXX is required${c.reset}`);
|
|
@@ -394,6 +499,7 @@ function handleCLI() {
|
|
|
394
499
|
|
|
395
500
|
const epicId = epicArg.split('=')[1];
|
|
396
501
|
const maxIterations = maxArg ? parseInt(maxArg.split('=')[1]) : 20;
|
|
502
|
+
const visualMode = visualArg;
|
|
397
503
|
|
|
398
504
|
// Find first ready story in epic
|
|
399
505
|
const status = getStatus(rootDir);
|
|
@@ -426,6 +532,8 @@ function handleCLI() {
|
|
|
426
532
|
current_story: storyId,
|
|
427
533
|
iteration: 0,
|
|
428
534
|
max_iterations: maxIterations,
|
|
535
|
+
visual_mode: visualMode,
|
|
536
|
+
screenshots_verified: false,
|
|
429
537
|
started_at: new Date().toISOString(),
|
|
430
538
|
};
|
|
431
539
|
saveSessionState(rootDir, state);
|
|
@@ -433,11 +541,16 @@ function handleCLI() {
|
|
|
433
541
|
const progress = getEpicProgress(status, epicId);
|
|
434
542
|
|
|
435
543
|
console.log('');
|
|
436
|
-
|
|
544
|
+
const modeLabel = visualMode ? ` ${c.cyan}[VISUAL MODE]${c.reset}` : '';
|
|
545
|
+
console.log(`${c.green}${c.bold}Ralph Loop Initialized${c.reset}${modeLabel}`);
|
|
437
546
|
console.log(`${c.dim}${'─'.repeat(40)}${c.reset}`);
|
|
438
547
|
console.log(` Epic: ${c.cyan}${epicId}${c.reset}`);
|
|
439
548
|
console.log(` Stories: ${progress.ready} ready, ${progress.total} total`);
|
|
440
549
|
console.log(` Max Iterations: ${maxIterations}`);
|
|
550
|
+
if (visualMode) {
|
|
551
|
+
console.log(` Visual Mode: ${c.cyan}enabled${c.reset} (screenshot verification)`);
|
|
552
|
+
console.log(` Min Iterations: 2 (for confirmation)`);
|
|
553
|
+
}
|
|
441
554
|
console.log(`${c.dim}${'─'.repeat(40)}${c.reset}`);
|
|
442
555
|
console.log('');
|
|
443
556
|
console.log(`${c.brand}▶ Starting Story:${c.reset} ${storyId}`);
|
|
@@ -464,22 +577,37 @@ function handleCLI() {
|
|
|
464
577
|
${c.brand}${c.bold}ralph-loop.js${c.reset} - Autonomous Story Processing
|
|
465
578
|
|
|
466
579
|
${c.bold}Usage:${c.reset}
|
|
467
|
-
node scripts/ralph-loop.js
|
|
468
|
-
node scripts/ralph-loop.js --init --epic=EP-XXX
|
|
469
|
-
node scripts/ralph-loop.js --
|
|
470
|
-
node scripts/ralph-loop.js --
|
|
471
|
-
node scripts/ralph-loop.js --
|
|
580
|
+
node scripts/ralph-loop.js Run loop check (Stop hook)
|
|
581
|
+
node scripts/ralph-loop.js --init --epic=EP-XXX Initialize loop for epic
|
|
582
|
+
node scripts/ralph-loop.js --init --epic=EP-XXX --visual Initialize with Visual Mode
|
|
583
|
+
node scripts/ralph-loop.js --status Check loop status
|
|
584
|
+
node scripts/ralph-loop.js --stop Stop the loop
|
|
585
|
+
node scripts/ralph-loop.js --reset Reset loop state
|
|
472
586
|
|
|
473
587
|
${c.bold}Options:${c.reset}
|
|
474
588
|
--epic=EP-XXXX Epic ID to process (required for --init)
|
|
475
589
|
--max=N Max iterations (default: 20)
|
|
590
|
+
--visual, -v Enable Visual Mode (screenshot verification)
|
|
591
|
+
|
|
592
|
+
${c.bold}Visual Mode:${c.reset}
|
|
593
|
+
When --visual is enabled, the loop also verifies that all screenshots
|
|
594
|
+
in the screenshots/ directory have been reviewed (verified- prefix).
|
|
595
|
+
|
|
596
|
+
This ensures Claude actually looks at UI screenshots before declaring
|
|
597
|
+
completion. Requires minimum 2 iterations for confirmation.
|
|
598
|
+
|
|
599
|
+
Workflow:
|
|
600
|
+
1. Tests run → must pass
|
|
601
|
+
2. Screenshots verified → all must have 'verified-' prefix
|
|
602
|
+
3. Minimum 2 iterations → prevents premature completion
|
|
603
|
+
4. Only then → story marked complete
|
|
476
604
|
|
|
477
605
|
${c.bold}How it works:${c.reset}
|
|
478
|
-
1. Start loop with /agileflow:babysit EPIC=EP-XXX MODE=loop
|
|
606
|
+
1. Start loop with /agileflow:babysit EPIC=EP-XXX MODE=loop VISUAL=true
|
|
479
607
|
2. Work on the current story
|
|
480
|
-
3. When you stop, this hook runs tests
|
|
481
|
-
4. If
|
|
482
|
-
5. If
|
|
608
|
+
3. When you stop, this hook runs tests (and screenshot verification in Visual Mode)
|
|
609
|
+
4. If all pass → story marked complete, next story loaded
|
|
610
|
+
5. If any fail → failures shown, you continue fixing
|
|
483
611
|
6. Loop repeats until epic done or max iterations
|
|
484
612
|
`);
|
|
485
613
|
return true;
|
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* screenshot-verifier.js - Verify all screenshots have been reviewed
|
|
5
|
+
*
|
|
6
|
+
* Part of Visual Mode for UI development. This script checks that all
|
|
7
|
+
* screenshots in a directory have been prefixed with "verified-" to
|
|
8
|
+
* confirm Claude has visually reviewed each one.
|
|
9
|
+
*
|
|
10
|
+
* Usage:
|
|
11
|
+
* node scripts/screenshot-verifier.js # Default: ./screenshots
|
|
12
|
+
* node scripts/screenshot-verifier.js --path ./e2e/shots # Custom path
|
|
13
|
+
* node scripts/screenshot-verifier.js --help # Show help
|
|
14
|
+
*
|
|
15
|
+
* Exit codes:
|
|
16
|
+
* 0 - All screenshots verified (or no screenshots found)
|
|
17
|
+
* 1 - Some screenshots missing verified- prefix
|
|
18
|
+
* 2 - Error (directory not found, etc.)
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
const fs = require('fs');
|
|
22
|
+
const path = require('path');
|
|
23
|
+
|
|
24
|
+
// Shared utilities
|
|
25
|
+
const { c } = require('../lib/colors');
|
|
26
|
+
|
|
27
|
+
// Parse command line arguments
|
|
28
|
+
function parseArgs() {
|
|
29
|
+
const args = process.argv.slice(2);
|
|
30
|
+
const options = {
|
|
31
|
+
path: './screenshots',
|
|
32
|
+
help: false,
|
|
33
|
+
quiet: false,
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
for (let i = 0; i < args.length; i++) {
|
|
37
|
+
const arg = args[i];
|
|
38
|
+
|
|
39
|
+
if (arg === '--help' || arg === '-h') {
|
|
40
|
+
options.help = true;
|
|
41
|
+
} else if (arg === '--quiet' || arg === '-q') {
|
|
42
|
+
options.quiet = true;
|
|
43
|
+
} else if (arg === '--path' || arg === '-p') {
|
|
44
|
+
if (args[i + 1]) {
|
|
45
|
+
options.path = args[i + 1];
|
|
46
|
+
i++;
|
|
47
|
+
}
|
|
48
|
+
} else if (!arg.startsWith('-')) {
|
|
49
|
+
// Positional argument - treat as path
|
|
50
|
+
options.path = arg;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return options;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Show help message
|
|
58
|
+
function showHelp() {
|
|
59
|
+
console.log(`
|
|
60
|
+
${c.brand}${c.bold}screenshot-verifier${c.reset} - Verify all screenshots have been reviewed
|
|
61
|
+
|
|
62
|
+
${c.bold}USAGE${c.reset}
|
|
63
|
+
node screenshot-verifier.js [OPTIONS] [PATH]
|
|
64
|
+
|
|
65
|
+
${c.bold}OPTIONS${c.reset}
|
|
66
|
+
--path, -p <dir> Directory containing screenshots (default: ./screenshots)
|
|
67
|
+
--quiet, -q Only output on failure
|
|
68
|
+
--help, -h Show this help message
|
|
69
|
+
|
|
70
|
+
${c.bold}EXAMPLES${c.reset}
|
|
71
|
+
node screenshot-verifier.js # Check ./screenshots
|
|
72
|
+
node screenshot-verifier.js ./tests/e2e/shots # Check custom path
|
|
73
|
+
node screenshot-verifier.js --path ./shots -q # Quiet mode
|
|
74
|
+
|
|
75
|
+
${c.bold}EXIT CODES${c.reset}
|
|
76
|
+
0 - All screenshots verified (or no screenshots found)
|
|
77
|
+
1 - Some screenshots missing 'verified-' prefix
|
|
78
|
+
2 - Error (directory not found, etc.)
|
|
79
|
+
|
|
80
|
+
${c.bold}VISUAL MODE${c.reset}
|
|
81
|
+
This script is part of AgileFlow's Visual Mode for UI development.
|
|
82
|
+
|
|
83
|
+
Workflow:
|
|
84
|
+
1. Playwright tests create screenshots in the target directory
|
|
85
|
+
2. Claude reviews each screenshot visually
|
|
86
|
+
3. Claude renames verified screenshots with 'verified-' prefix
|
|
87
|
+
4. This script confirms all screenshots have been reviewed
|
|
88
|
+
|
|
89
|
+
The 'verified-' prefix ensures Claude actually looked at each image
|
|
90
|
+
rather than declaring completion prematurely.
|
|
91
|
+
`);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Get all image files in directory
|
|
95
|
+
function getImageFiles(dir) {
|
|
96
|
+
const imageExtensions = ['.png', '.jpg', '.jpeg', '.webp', '.gif'];
|
|
97
|
+
|
|
98
|
+
try {
|
|
99
|
+
const files = fs.readdirSync(dir);
|
|
100
|
+
return files.filter((file) => {
|
|
101
|
+
const ext = path.extname(file).toLowerCase();
|
|
102
|
+
return imageExtensions.includes(ext);
|
|
103
|
+
});
|
|
104
|
+
} catch (err) {
|
|
105
|
+
if (err.code === 'ENOENT') {
|
|
106
|
+
return null; // Directory doesn't exist
|
|
107
|
+
}
|
|
108
|
+
throw err;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
// Main verification logic
|
|
113
|
+
function verifyScreenshots(options) {
|
|
114
|
+
const dir = path.resolve(options.path);
|
|
115
|
+
|
|
116
|
+
// Check if directory exists
|
|
117
|
+
if (!fs.existsSync(dir)) {
|
|
118
|
+
if (!options.quiet) {
|
|
119
|
+
console.log(`${c.yellow}No screenshots directory found at: ${dir}${c.reset}`);
|
|
120
|
+
console.log(`${c.dim}Create the directory or run tests to generate screenshots.${c.reset}`);
|
|
121
|
+
}
|
|
122
|
+
return { success: true, total: 0, verified: 0, unverified: [] };
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
// Get all image files
|
|
126
|
+
const files = getImageFiles(dir);
|
|
127
|
+
|
|
128
|
+
if (files === null) {
|
|
129
|
+
console.error(`${c.red}Error: Could not read directory: ${dir}${c.reset}`);
|
|
130
|
+
process.exit(2);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
if (files.length === 0) {
|
|
134
|
+
if (!options.quiet) {
|
|
135
|
+
console.log(`${c.yellow}No screenshots found in: ${dir}${c.reset}`);
|
|
136
|
+
}
|
|
137
|
+
return { success: true, total: 0, verified: 0, unverified: [] };
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Check each file for verified- prefix
|
|
141
|
+
const verified = [];
|
|
142
|
+
const unverified = [];
|
|
143
|
+
|
|
144
|
+
for (const file of files) {
|
|
145
|
+
if (file.startsWith('verified-')) {
|
|
146
|
+
verified.push(file);
|
|
147
|
+
} else {
|
|
148
|
+
unverified.push(file);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
return {
|
|
153
|
+
success: unverified.length === 0,
|
|
154
|
+
total: files.length,
|
|
155
|
+
verified: verified.length,
|
|
156
|
+
unverified,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
// Format result output
|
|
161
|
+
function formatResult(result, options) {
|
|
162
|
+
if (options.quiet && result.success) {
|
|
163
|
+
return;
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
console.log('');
|
|
167
|
+
|
|
168
|
+
if (result.total === 0) {
|
|
169
|
+
console.log(`${c.yellow}No screenshots to verify${c.reset}`);
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (result.success) {
|
|
174
|
+
console.log(`${c.green}${c.bold}All screenshots verified${c.reset}`);
|
|
175
|
+
console.log(`${c.dim}${result.verified}/${result.total} screenshots have 'verified-' prefix${c.reset}`);
|
|
176
|
+
} else {
|
|
177
|
+
console.log(`${c.red}${c.bold}Unverified screenshots found${c.reset}`);
|
|
178
|
+
console.log(`${c.dim}${result.verified}/${result.total} verified${c.reset}`);
|
|
179
|
+
console.log('');
|
|
180
|
+
console.log(`${c.yellow}Missing 'verified-' prefix:${c.reset}`);
|
|
181
|
+
for (const file of result.unverified) {
|
|
182
|
+
console.log(` ${c.red}- ${file}${c.reset}`);
|
|
183
|
+
}
|
|
184
|
+
console.log('');
|
|
185
|
+
console.log(`${c.cyan}To verify: Review each screenshot visually, then rename:${c.reset}`);
|
|
186
|
+
console.log(`${c.dim} mv screenshots/example.png screenshots/verified-example.png${c.reset}`);
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
console.log('');
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Main entry point
|
|
193
|
+
function main() {
|
|
194
|
+
const options = parseArgs();
|
|
195
|
+
|
|
196
|
+
if (options.help) {
|
|
197
|
+
showHelp();
|
|
198
|
+
process.exit(0);
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const result = verifyScreenshots(options);
|
|
202
|
+
formatResult(result, options);
|
|
203
|
+
|
|
204
|
+
process.exit(result.success ? 0 : 1);
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// Run if called directly
|
|
208
|
+
if (require.main === module) {
|
|
209
|
+
main();
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
// Export for testing
|
|
213
|
+
module.exports = { verifyScreenshots, getImageFiles };
|
|
@@ -13,30 +13,10 @@ const fs = require('fs');
|
|
|
13
13
|
const path = require('path');
|
|
14
14
|
const { execSync, spawnSync } = require('child_process');
|
|
15
15
|
|
|
16
|
-
//
|
|
17
|
-
const c =
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
dim: '\x1b[2m',
|
|
21
|
-
red: '\x1b[31m',
|
|
22
|
-
green: '\x1b[32m',
|
|
23
|
-
yellow: '\x1b[33m',
|
|
24
|
-
blue: '\x1b[34m',
|
|
25
|
-
cyan: '\x1b[36m',
|
|
26
|
-
brand: '\x1b[38;2;232;104;58m',
|
|
27
|
-
};
|
|
28
|
-
|
|
29
|
-
// Find project root (has .agileflow or .git)
|
|
30
|
-
function getProjectRoot() {
|
|
31
|
-
let dir = process.cwd();
|
|
32
|
-
while (dir !== '/') {
|
|
33
|
-
if (fs.existsSync(path.join(dir, '.agileflow')) || fs.existsSync(path.join(dir, '.git'))) {
|
|
34
|
-
return dir;
|
|
35
|
-
}
|
|
36
|
-
dir = path.dirname(dir);
|
|
37
|
-
}
|
|
38
|
-
return process.cwd();
|
|
39
|
-
}
|
|
16
|
+
// Shared utilities
|
|
17
|
+
const { c } = require('../lib/colors');
|
|
18
|
+
const { getProjectRoot } = require('../lib/paths');
|
|
19
|
+
const { safeReadJSON } = require('../lib/errors');
|
|
40
20
|
|
|
41
21
|
const ROOT = getProjectRoot();
|
|
42
22
|
const SESSIONS_DIR = path.join(ROOT, '.agileflow', 'sessions');
|
|
@@ -162,17 +142,16 @@ function getCurrentBranch() {
|
|
|
162
142
|
|
|
163
143
|
// Get current story from status.json
|
|
164
144
|
function getCurrentStory() {
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
if (!fs.existsSync(statusPath)) return null;
|
|
145
|
+
const statusPath = path.join(ROOT, 'docs', '09-agents', 'status.json');
|
|
146
|
+
const result = safeReadJSON(statusPath, { defaultValue: null });
|
|
168
147
|
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
}
|
|
148
|
+
if (!result.ok || !result.data) return null;
|
|
149
|
+
|
|
150
|
+
for (const [id, story] of Object.entries(result.data.stories || {})) {
|
|
151
|
+
if (story.status === 'in_progress') {
|
|
152
|
+
return { id, title: story.title };
|
|
174
153
|
}
|
|
175
|
-
}
|
|
154
|
+
}
|
|
176
155
|
return null;
|
|
177
156
|
}
|
|
178
157
|
|