agileflow 2.84.2 → 2.86.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/CHANGELOG.md +10 -0
- package/README.md +3 -3
- package/lib/colors.js +23 -0
- package/package.json +1 -1
- package/scripts/agileflow-statusline.sh +31 -44
- package/scripts/agileflow-welcome.js +378 -132
- package/scripts/batch-pmap-loop.js +528 -0
- package/scripts/lib/colors.sh +106 -0
- package/scripts/lib/file-tracking.js +5 -3
- package/scripts/obtain-context.js +132 -20
- package/scripts/session-boundary.js +138 -0
- package/scripts/session-manager.js +526 -7
- package/scripts/test-session-boundary.js +80 -0
- package/src/core/agents/mentor.md +40 -2
- package/src/core/agents/orchestrator.md +35 -2
- package/src/core/commands/babysit.md +198 -674
- package/src/core/commands/batch.md +117 -2
- package/src/core/commands/metrics.md +62 -9
- package/src/core/commands/rpi.md +500 -0
- package/src/core/commands/session/new.md +90 -51
- package/src/core/commands/session/resume.md +40 -16
- package/src/core/commands/session/status.md +35 -2
- package/src/core/templates/session-state.json +32 -3
- package/tools/cli/commands/config.js +43 -21
- package/tools/cli/commands/doctor.js +8 -5
- package/tools/cli/commands/setup.js +14 -7
- package/tools/cli/commands/uninstall.js +8 -5
- package/tools/cli/commands/update.js +20 -10
- package/tools/cli/lib/content-injector.js +80 -0
- package/tools/cli/lib/error-handler.js +173 -0
- package/tools/cli/lib/ui.js +3 -2
|
@@ -0,0 +1,528 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* batch-pmap-loop.js - Autonomous Batch Processing Loop
|
|
5
|
+
*
|
|
6
|
+
* Enables iterative batch processing with quality gates for pmap operations.
|
|
7
|
+
* Processes items one at a time, running quality gates after each iteration.
|
|
8
|
+
*
|
|
9
|
+
* Usage (as Stop hook):
|
|
10
|
+
* node scripts/batch-pmap-loop.js
|
|
11
|
+
*
|
|
12
|
+
* Manual control:
|
|
13
|
+
* node scripts/batch-pmap-loop.js --init --pattern="src/components/*.tsx" --action="add-tests"
|
|
14
|
+
* node scripts/batch-pmap-loop.js --status
|
|
15
|
+
* node scripts/batch-pmap-loop.js --stop
|
|
16
|
+
* node scripts/batch-pmap-loop.js --reset
|
|
17
|
+
*
|
|
18
|
+
* V1 Scope: Tests gate only, sequential processing
|
|
19
|
+
*/
|
|
20
|
+
|
|
21
|
+
const fs = require('fs');
|
|
22
|
+
const path = require('path');
|
|
23
|
+
const { execSync } = require('child_process');
|
|
24
|
+
|
|
25
|
+
// Shared utilities
|
|
26
|
+
const { c } = require('../lib/colors');
|
|
27
|
+
const { getProjectRoot } = require('../lib/paths');
|
|
28
|
+
const { safeReadJSON, safeWriteJSON } = require('../lib/errors');
|
|
29
|
+
const { parseIntBounded } = require('../lib/validate');
|
|
30
|
+
|
|
31
|
+
// ===== SESSION STATE HELPERS =====
|
|
32
|
+
|
|
33
|
+
function getSessionState(rootDir) {
|
|
34
|
+
const statePath = path.join(rootDir, 'docs/09-agents/session-state.json');
|
|
35
|
+
const result = safeReadJSON(statePath, { defaultValue: {} });
|
|
36
|
+
return result.ok ? result.data : {};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function saveSessionState(rootDir, state) {
|
|
40
|
+
const statePath = path.join(rootDir, 'docs/09-agents/session-state.json');
|
|
41
|
+
safeWriteJSON(statePath, state, { createDir: true });
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// ===== GLOB RESOLUTION =====
|
|
45
|
+
|
|
46
|
+
async function resolveGlob(pattern, rootDir) {
|
|
47
|
+
// Use bash globbing for pattern expansion
|
|
48
|
+
try {
|
|
49
|
+
const result = execSync(`bash -c 'shopt -s nullglob; for f in ${pattern}; do echo "$f"; done'`, {
|
|
50
|
+
cwd: rootDir,
|
|
51
|
+
encoding: 'utf8',
|
|
52
|
+
timeout: 10000,
|
|
53
|
+
});
|
|
54
|
+
const files = result
|
|
55
|
+
.split('\n')
|
|
56
|
+
.filter(f => f.trim())
|
|
57
|
+
.filter(f => !f.includes('node_modules') && !f.includes('.git'));
|
|
58
|
+
return files.sort();
|
|
59
|
+
} catch (e) {
|
|
60
|
+
// Fallback: use ls
|
|
61
|
+
try {
|
|
62
|
+
const result = execSync(`ls -1 ${pattern} 2>/dev/null | head -100`, {
|
|
63
|
+
cwd: rootDir,
|
|
64
|
+
encoding: 'utf8',
|
|
65
|
+
timeout: 10000,
|
|
66
|
+
});
|
|
67
|
+
const files = result
|
|
68
|
+
.split('\n')
|
|
69
|
+
.filter(f => f.trim())
|
|
70
|
+
.filter(f => !f.includes('node_modules') && !f.includes('.git'));
|
|
71
|
+
return files.sort();
|
|
72
|
+
} catch (e2) {
|
|
73
|
+
return [];
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// ===== TEST GATE =====
|
|
79
|
+
|
|
80
|
+
/**
|
|
81
|
+
* Run tests for a specific file
|
|
82
|
+
* Uses Jest's testPathPattern to run only relevant tests
|
|
83
|
+
*/
|
|
84
|
+
function runTestsForFile(rootDir, filePath) {
|
|
85
|
+
const result = { passed: false, output: '', duration: 0 };
|
|
86
|
+
const startTime = Date.now();
|
|
87
|
+
|
|
88
|
+
// Extract filename without extension for test pattern
|
|
89
|
+
const basename = path.basename(filePath, path.extname(filePath));
|
|
90
|
+
|
|
91
|
+
// Try to run tests matching this file
|
|
92
|
+
// Common patterns: Button.tsx -> Button.test.tsx, Button.spec.tsx
|
|
93
|
+
const testPattern = basename.replace(/\.(test|spec)$/, '');
|
|
94
|
+
|
|
95
|
+
try {
|
|
96
|
+
const output = execSync(`npm test -- --testPathPattern="${testPattern}" --passWithNoTests`, {
|
|
97
|
+
cwd: rootDir,
|
|
98
|
+
encoding: 'utf8',
|
|
99
|
+
stdio: ['pipe', 'pipe', 'pipe'],
|
|
100
|
+
timeout: 120000, // 2 minute timeout per file
|
|
101
|
+
});
|
|
102
|
+
result.passed = true;
|
|
103
|
+
result.output = output;
|
|
104
|
+
} catch (e) {
|
|
105
|
+
result.passed = false;
|
|
106
|
+
result.output = (e.stdout || '') + '\n' + (e.stderr || '');
|
|
107
|
+
if (e.message) {
|
|
108
|
+
result.output += '\n' + e.message;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
result.duration = Date.now() - startTime;
|
|
113
|
+
return result;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// ===== BATCH LOOP CORE =====
|
|
117
|
+
|
|
118
|
+
function getNextPendingItem(items) {
|
|
119
|
+
for (const [itemPath, itemState] of Object.entries(items)) {
|
|
120
|
+
if (itemState.status === 'pending') {
|
|
121
|
+
return itemPath;
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
return null;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function getCurrentItem(items) {
|
|
128
|
+
for (const [itemPath, itemState] of Object.entries(items)) {
|
|
129
|
+
if (itemState.status === 'in_progress') {
|
|
130
|
+
return itemPath;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
return null;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function updateSummary(items) {
|
|
137
|
+
const summary = {
|
|
138
|
+
total: Object.keys(items).length,
|
|
139
|
+
completed: 0,
|
|
140
|
+
in_progress: 0,
|
|
141
|
+
pending: 0,
|
|
142
|
+
failed: 0,
|
|
143
|
+
};
|
|
144
|
+
|
|
145
|
+
for (const item of Object.values(items)) {
|
|
146
|
+
summary[item.status] = (summary[item.status] || 0) + 1;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
return summary;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function generateBatchId() {
|
|
153
|
+
const now = new Date();
|
|
154
|
+
const timestamp = now.toISOString().replace(/[-:T]/g, '').slice(0, 14);
|
|
155
|
+
return `batch-${timestamp}`;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
// ===== MAIN LOOP HANDLER =====
|
|
159
|
+
|
|
160
|
+
function handleBatchLoop(rootDir) {
|
|
161
|
+
const state = getSessionState(rootDir);
|
|
162
|
+
const loop = state.batch_loop;
|
|
163
|
+
|
|
164
|
+
// Check if batch loop mode is enabled
|
|
165
|
+
if (!loop || !loop.enabled) {
|
|
166
|
+
return; // Silent exit - not in batch loop mode
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const iteration = (loop.iteration || 0) + 1;
|
|
170
|
+
const maxIterations = loop.max_iterations || 50;
|
|
171
|
+
|
|
172
|
+
console.log('');
|
|
173
|
+
console.log(
|
|
174
|
+
`${c.brand}${c.bold}======================================================${c.reset}`
|
|
175
|
+
);
|
|
176
|
+
console.log(
|
|
177
|
+
`${c.brand}${c.bold} BATCH LOOP - Iteration ${iteration}/${maxIterations}${c.reset}`
|
|
178
|
+
);
|
|
179
|
+
console.log(
|
|
180
|
+
`${c.brand}${c.bold}======================================================${c.reset}`
|
|
181
|
+
);
|
|
182
|
+
console.log('');
|
|
183
|
+
|
|
184
|
+
// State Narration: Loop iteration marker
|
|
185
|
+
console.log(`🔄 Iteration ${iteration}/${maxIterations}`);
|
|
186
|
+
|
|
187
|
+
// Check iteration limit
|
|
188
|
+
if (iteration > maxIterations) {
|
|
189
|
+
console.log(`${c.yellow}⚠ Max iterations (${maxIterations}) reached. Stopping loop.${c.reset}`);
|
|
190
|
+
state.batch_loop.enabled = false;
|
|
191
|
+
state.batch_loop.stopped_reason = 'max_iterations';
|
|
192
|
+
saveSessionState(rootDir, state);
|
|
193
|
+
return;
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const items = loop.items || {};
|
|
197
|
+
const currentItem = getCurrentItem(items);
|
|
198
|
+
|
|
199
|
+
if (!currentItem) {
|
|
200
|
+
// No item in progress - find next pending
|
|
201
|
+
const nextItem = getNextPendingItem(items);
|
|
202
|
+
if (!nextItem) {
|
|
203
|
+
// All items completed!
|
|
204
|
+
const summary = updateSummary(items);
|
|
205
|
+
state.batch_loop.enabled = false;
|
|
206
|
+
state.batch_loop.stopped_reason = 'batch_complete';
|
|
207
|
+
state.batch_loop.completed_at = new Date().toISOString();
|
|
208
|
+
state.batch_loop.summary = summary;
|
|
209
|
+
saveSessionState(rootDir, state);
|
|
210
|
+
|
|
211
|
+
console.log('');
|
|
212
|
+
console.log(
|
|
213
|
+
`${c.green}${c.bold}======================================================${c.reset}`
|
|
214
|
+
);
|
|
215
|
+
console.log(`${c.green}${c.bold} 🎉 BATCH COMPLETE!${c.reset}`);
|
|
216
|
+
console.log(
|
|
217
|
+
`${c.green}${c.bold}======================================================${c.reset}`
|
|
218
|
+
);
|
|
219
|
+
console.log('');
|
|
220
|
+
console.log(`${c.green}Pattern: ${loop.pattern}${c.reset}`);
|
|
221
|
+
console.log(`${c.dim}${summary.completed} items completed in ${iteration - 1} iterations${c.reset}`);
|
|
222
|
+
return;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Start next item
|
|
226
|
+
items[nextItem] = {
|
|
227
|
+
...items[nextItem],
|
|
228
|
+
status: 'in_progress',
|
|
229
|
+
iterations: 0,
|
|
230
|
+
started_at: new Date().toISOString(),
|
|
231
|
+
};
|
|
232
|
+
state.batch_loop.items = items;
|
|
233
|
+
state.batch_loop.current_item = nextItem;
|
|
234
|
+
state.batch_loop.summary = updateSummary(items);
|
|
235
|
+
saveSessionState(rootDir, state);
|
|
236
|
+
|
|
237
|
+
console.log(`📍 Starting: ${nextItem}`);
|
|
238
|
+
console.log('');
|
|
239
|
+
console.log(`${c.brand}▶ Implement "${loop.action}" for this file${c.reset}`);
|
|
240
|
+
console.log(`${c.dim} Run tests when ready. Loop will validate and continue.${c.reset}`);
|
|
241
|
+
return;
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
// Current item in progress - run quality gate
|
|
245
|
+
console.log(`📍 Working on: ${currentItem}`);
|
|
246
|
+
console.log('');
|
|
247
|
+
|
|
248
|
+
// Update item iterations
|
|
249
|
+
items[currentItem].iterations = (items[currentItem].iterations || 0) + 1;
|
|
250
|
+
|
|
251
|
+
// Run tests for this file
|
|
252
|
+
console.log(`${c.blue}Running tests for:${c.reset} ${currentItem}`);
|
|
253
|
+
console.log(`${c.dim}${'─'.repeat(50)}${c.reset}`);
|
|
254
|
+
|
|
255
|
+
const testResult = runTestsForFile(rootDir, currentItem);
|
|
256
|
+
|
|
257
|
+
if (testResult.passed) {
|
|
258
|
+
console.log(`${c.green}✓ Tests passed${c.reset} (${(testResult.duration / 1000).toFixed(1)}s)`);
|
|
259
|
+
|
|
260
|
+
// Mark item complete
|
|
261
|
+
items[currentItem].status = 'completed';
|
|
262
|
+
items[currentItem].completed_at = new Date().toISOString();
|
|
263
|
+
state.batch_loop.items = items;
|
|
264
|
+
state.batch_loop.summary = updateSummary(items);
|
|
265
|
+
state.batch_loop.iteration = iteration;
|
|
266
|
+
|
|
267
|
+
// Find next item
|
|
268
|
+
const nextItem = getNextPendingItem(items);
|
|
269
|
+
|
|
270
|
+
if (nextItem) {
|
|
271
|
+
// Start next item
|
|
272
|
+
items[nextItem] = {
|
|
273
|
+
...items[nextItem],
|
|
274
|
+
status: 'in_progress',
|
|
275
|
+
iterations: 0,
|
|
276
|
+
started_at: new Date().toISOString(),
|
|
277
|
+
};
|
|
278
|
+
state.batch_loop.items = items;
|
|
279
|
+
state.batch_loop.current_item = nextItem;
|
|
280
|
+
state.batch_loop.summary = updateSummary(items);
|
|
281
|
+
saveSessionState(rootDir, state);
|
|
282
|
+
|
|
283
|
+
const summary = state.batch_loop.summary;
|
|
284
|
+
console.log('');
|
|
285
|
+
console.log(`✅ Item complete: ${currentItem}`);
|
|
286
|
+
console.log('');
|
|
287
|
+
console.log(`${c.cyan}━━━ Next Item ━━━${c.reset}`);
|
|
288
|
+
console.log(`${c.bold}${nextItem}${c.reset}`);
|
|
289
|
+
console.log('');
|
|
290
|
+
console.log(`${c.dim}Progress: ${summary.completed}/${summary.total} items complete${c.reset}`);
|
|
291
|
+
console.log('');
|
|
292
|
+
console.log(`${c.brand}▶ Implement "${loop.action}" for this file${c.reset}`);
|
|
293
|
+
console.log(`${c.dim} Run tests when ready. Loop will validate and continue.${c.reset}`);
|
|
294
|
+
} else {
|
|
295
|
+
// Batch complete!
|
|
296
|
+
state.batch_loop.enabled = false;
|
|
297
|
+
state.batch_loop.stopped_reason = 'batch_complete';
|
|
298
|
+
state.batch_loop.completed_at = new Date().toISOString();
|
|
299
|
+
saveSessionState(rootDir, state);
|
|
300
|
+
|
|
301
|
+
const summary = state.batch_loop.summary;
|
|
302
|
+
console.log('');
|
|
303
|
+
console.log(
|
|
304
|
+
`${c.green}${c.bold}======================================================${c.reset}`
|
|
305
|
+
);
|
|
306
|
+
console.log(`${c.green}${c.bold} 🎉 BATCH COMPLETE!${c.reset}`);
|
|
307
|
+
console.log(
|
|
308
|
+
`${c.green}${c.bold}======================================================${c.reset}`
|
|
309
|
+
);
|
|
310
|
+
console.log('');
|
|
311
|
+
console.log(`${c.green}Pattern: ${loop.pattern}${c.reset}`);
|
|
312
|
+
console.log(`${c.green}Action: ${loop.action}${c.reset}`);
|
|
313
|
+
console.log(`${c.dim}${summary.completed} items completed in ${iteration} iterations${c.reset}`);
|
|
314
|
+
}
|
|
315
|
+
} else {
|
|
316
|
+
// Tests failed - continue iterating
|
|
317
|
+
console.log(`${c.red}✗ Tests failed${c.reset} (${(testResult.duration / 1000).toFixed(1)}s)`);
|
|
318
|
+
console.log('');
|
|
319
|
+
|
|
320
|
+
state.batch_loop.items = items;
|
|
321
|
+
state.batch_loop.iteration = iteration;
|
|
322
|
+
state.batch_loop.last_failure = new Date().toISOString();
|
|
323
|
+
saveSessionState(rootDir, state);
|
|
324
|
+
|
|
325
|
+
console.log(`${c.yellow}━━━ Test Failures ━━━${c.reset}`);
|
|
326
|
+
|
|
327
|
+
// Show truncated output (last 30 lines most relevant)
|
|
328
|
+
const outputLines = testResult.output.split('\n');
|
|
329
|
+
const relevantLines = outputLines.slice(-30);
|
|
330
|
+
console.log(relevantLines.join('\n'));
|
|
331
|
+
|
|
332
|
+
console.log('');
|
|
333
|
+
console.log(`${c.brand}▶ Fix the failing tests and continue${c.reset}`);
|
|
334
|
+
console.log(`${c.dim} Loop will re-run tests when you stop.${c.reset}`);
|
|
335
|
+
console.log(`${c.dim} Item iterations: ${items[currentItem].iterations}${c.reset}`);
|
|
336
|
+
console.log(`${c.dim} Batch iterations: ${iteration}/${maxIterations}${c.reset}`);
|
|
337
|
+
}
|
|
338
|
+
|
|
339
|
+
console.log('');
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// ===== CLI HANDLERS =====
|
|
343
|
+
|
|
344
|
+
async function handleInit(args, rootDir) {
|
|
345
|
+
const patternArg = args.find(a => a.startsWith('--pattern='));
|
|
346
|
+
const actionArg = args.find(a => a.startsWith('--action='));
|
|
347
|
+
const maxArg = args.find(a => a.startsWith('--max='));
|
|
348
|
+
|
|
349
|
+
if (!patternArg) {
|
|
350
|
+
console.log(`${c.red}Error: --pattern="glob" is required${c.reset}`);
|
|
351
|
+
console.log(`${c.dim}Example: --pattern="src/**/*.tsx"${c.reset}`);
|
|
352
|
+
return;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
const pattern = patternArg.split('=').slice(1).join('=').replace(/"/g, '');
|
|
356
|
+
const action = actionArg
|
|
357
|
+
? actionArg.split('=').slice(1).join('=').replace(/"/g, '')
|
|
358
|
+
: 'process';
|
|
359
|
+
const maxIterations = parseIntBounded(maxArg ? maxArg.split('=')[1] : null, 50, 1, 200);
|
|
360
|
+
|
|
361
|
+
// Resolve glob pattern
|
|
362
|
+
console.log(`${c.dim}Resolving pattern: ${pattern}${c.reset}`);
|
|
363
|
+
const files = await resolveGlob(pattern, rootDir);
|
|
364
|
+
|
|
365
|
+
if (files.length === 0) {
|
|
366
|
+
console.log(`${c.yellow}No files found matching: ${pattern}${c.reset}`);
|
|
367
|
+
return;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// Build items map
|
|
371
|
+
const items = {};
|
|
372
|
+
for (const file of files) {
|
|
373
|
+
items[file] = {
|
|
374
|
+
status: 'pending',
|
|
375
|
+
iterations: 0,
|
|
376
|
+
started_at: null,
|
|
377
|
+
completed_at: null,
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
// Mark first item as in_progress
|
|
382
|
+
const firstItem = files[0];
|
|
383
|
+
items[firstItem].status = 'in_progress';
|
|
384
|
+
items[firstItem].started_at = new Date().toISOString();
|
|
385
|
+
|
|
386
|
+
// Initialize batch loop state
|
|
387
|
+
const state = getSessionState(rootDir);
|
|
388
|
+
state.batch_loop = {
|
|
389
|
+
enabled: true,
|
|
390
|
+
batch_id: generateBatchId(),
|
|
391
|
+
pattern: pattern,
|
|
392
|
+
action: action,
|
|
393
|
+
quality_gate: 'tests',
|
|
394
|
+
current_item: firstItem,
|
|
395
|
+
items: items,
|
|
396
|
+
summary: updateSummary(items),
|
|
397
|
+
iteration: 0,
|
|
398
|
+
max_iterations: maxIterations,
|
|
399
|
+
started_at: new Date().toISOString(),
|
|
400
|
+
};
|
|
401
|
+
saveSessionState(rootDir, state);
|
|
402
|
+
|
|
403
|
+
console.log('');
|
|
404
|
+
console.log(`${c.green}${c.bold}Batch Loop Initialized${c.reset}`);
|
|
405
|
+
console.log(`${c.dim}${'─'.repeat(40)}${c.reset}`);
|
|
406
|
+
console.log(` Pattern: ${c.cyan}${pattern}${c.reset}`);
|
|
407
|
+
console.log(` Action: ${c.cyan}${action}${c.reset}`);
|
|
408
|
+
console.log(` Items: ${files.length} files`);
|
|
409
|
+
console.log(` Gate: tests (pass/fail)`);
|
|
410
|
+
console.log(` Max Iterations: ${maxIterations}`);
|
|
411
|
+
console.log(`${c.dim}${'─'.repeat(40)}${c.reset}`);
|
|
412
|
+
console.log('');
|
|
413
|
+
console.log(`${c.brand}▶ Starting Item:${c.reset} ${firstItem}`);
|
|
414
|
+
console.log('');
|
|
415
|
+
console.log(`${c.dim}Work on this file. When you stop, tests will run automatically.${c.reset}`);
|
|
416
|
+
console.log(`${c.dim}If tests pass, the next item will be loaded.${c.reset}`);
|
|
417
|
+
console.log('');
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
function handleStatus(rootDir) {
|
|
421
|
+
const state = getSessionState(rootDir);
|
|
422
|
+
const loop = state.batch_loop;
|
|
423
|
+
|
|
424
|
+
if (!loop || !loop.enabled) {
|
|
425
|
+
console.log(`${c.dim}Batch Loop: not active${c.reset}`);
|
|
426
|
+
return;
|
|
427
|
+
}
|
|
428
|
+
|
|
429
|
+
const summary = loop.summary || updateSummary(loop.items || {});
|
|
430
|
+
|
|
431
|
+
console.log(`${c.green}Batch Loop: active${c.reset}`);
|
|
432
|
+
console.log(` Pattern: ${loop.pattern}`);
|
|
433
|
+
console.log(` Action: ${loop.action}`);
|
|
434
|
+
console.log(` Current Item: ${loop.current_item || 'none'}`);
|
|
435
|
+
console.log(` Progress: ${summary.completed}/${summary.total} (${summary.in_progress} in progress)`);
|
|
436
|
+
console.log(` Iteration: ${loop.iteration || 0}/${loop.max_iterations || 50}`);
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
function handleStop(rootDir) {
|
|
440
|
+
const state = getSessionState(rootDir);
|
|
441
|
+
if (state.batch_loop) {
|
|
442
|
+
state.batch_loop.enabled = false;
|
|
443
|
+
state.batch_loop.stopped_reason = 'manual';
|
|
444
|
+
saveSessionState(rootDir, state);
|
|
445
|
+
console.log(`${c.yellow}Batch Loop stopped${c.reset}`);
|
|
446
|
+
} else {
|
|
447
|
+
console.log(`${c.dim}Batch Loop was not active${c.reset}`);
|
|
448
|
+
}
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
function handleReset(rootDir) {
|
|
452
|
+
const state = getSessionState(rootDir);
|
|
453
|
+
delete state.batch_loop;
|
|
454
|
+
saveSessionState(rootDir, state);
|
|
455
|
+
console.log(`${c.green}Batch Loop state reset${c.reset}`);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
function showHelp() {
|
|
459
|
+
console.log(`
|
|
460
|
+
${c.brand}${c.bold}batch-pmap-loop.js${c.reset} - Autonomous Batch Processing
|
|
461
|
+
|
|
462
|
+
${c.bold}Usage:${c.reset}
|
|
463
|
+
node scripts/batch-pmap-loop.js Run loop check (Stop hook)
|
|
464
|
+
node scripts/batch-pmap-loop.js --init --pattern="*.tsx" --action="add-tests"
|
|
465
|
+
node scripts/batch-pmap-loop.js --status Check loop status
|
|
466
|
+
node scripts/batch-pmap-loop.js --stop Stop the loop
|
|
467
|
+
node scripts/batch-pmap-loop.js --reset Reset loop state
|
|
468
|
+
|
|
469
|
+
${c.bold}Options:${c.reset}
|
|
470
|
+
--pattern="glob" Glob pattern for files (required for --init)
|
|
471
|
+
--action="action" Action to perform on each file (default: "process")
|
|
472
|
+
--max=N Max total iterations (default: 50)
|
|
473
|
+
|
|
474
|
+
${c.bold}Quality Gate:${c.reset}
|
|
475
|
+
V1 supports tests gate only:
|
|
476
|
+
- Runs: npm test -- --testPathPattern="filename"
|
|
477
|
+
- Passes if tests for this file pass (or no matching tests)
|
|
478
|
+
- Fails if tests for this file fail
|
|
479
|
+
|
|
480
|
+
${c.bold}How it works:${c.reset}
|
|
481
|
+
1. Initialize with: /agileflow:batch pmap "pattern" action MODE=loop
|
|
482
|
+
2. Work on current file
|
|
483
|
+
3. When you stop, tests run for that file
|
|
484
|
+
4. If pass: next file loaded
|
|
485
|
+
5. If fail: continue fixing
|
|
486
|
+
6. Repeats until all files done or max iterations
|
|
487
|
+
`);
|
|
488
|
+
}
|
|
489
|
+
|
|
490
|
+
// ===== MAIN =====
|
|
491
|
+
|
|
492
|
+
async function main() {
|
|
493
|
+
const args = process.argv.slice(2);
|
|
494
|
+
const rootDir = getProjectRoot();
|
|
495
|
+
|
|
496
|
+
if (args.includes('--help') || args.includes('-h')) {
|
|
497
|
+
showHelp();
|
|
498
|
+
return;
|
|
499
|
+
}
|
|
500
|
+
|
|
501
|
+
if (args.includes('--status')) {
|
|
502
|
+
handleStatus(rootDir);
|
|
503
|
+
return;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
if (args.includes('--stop')) {
|
|
507
|
+
handleStop(rootDir);
|
|
508
|
+
return;
|
|
509
|
+
}
|
|
510
|
+
|
|
511
|
+
if (args.includes('--reset')) {
|
|
512
|
+
handleReset(rootDir);
|
|
513
|
+
return;
|
|
514
|
+
}
|
|
515
|
+
|
|
516
|
+
if (args.some(a => a.startsWith('--init'))) {
|
|
517
|
+
await handleInit(args, rootDir);
|
|
518
|
+
return;
|
|
519
|
+
}
|
|
520
|
+
|
|
521
|
+
// Default: run as Stop hook
|
|
522
|
+
handleBatchLoop(rootDir);
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
main().catch(e => {
|
|
526
|
+
console.error(`${c.red}Error: ${e.message}${c.reset}`);
|
|
527
|
+
process.exit(1);
|
|
528
|
+
});
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
#!/bin/bash
|
|
2
|
+
# AgileFlow Color Palette - Bash Edition
|
|
3
|
+
#
|
|
4
|
+
# Source this file: source "$(dirname "${BASH_SOURCE[0]}")/lib/colors.sh"
|
|
5
|
+
#
|
|
6
|
+
# WCAG AA Contrast Ratios (verified against #1a1a1a dark terminal background):
|
|
7
|
+
# - Green (#32CD32): 4.5:1 ✓ (meets AA for normal text)
|
|
8
|
+
# - Red (#FF6B6B): 5.0:1 ✓ (meets AA for normal text)
|
|
9
|
+
# - Yellow (#FFD700): 4.5:1 ✓ (meets AA for normal text)
|
|
10
|
+
# - Cyan (#00CED1): 4.6:1 ✓ (meets AA for normal text)
|
|
11
|
+
# - Brand (#e8683a): 3.8:1 ✓ (meets AA for large text/UI elements)
|
|
12
|
+
#
|
|
13
|
+
# Note: Standard ANSI colors vary by terminal theme. The above ratios
|
|
14
|
+
# are for typical dark terminal configurations.
|
|
15
|
+
#
|
|
16
|
+
# Usage:
|
|
17
|
+
# echo -e "${GREEN}Success!${RESET}"
|
|
18
|
+
# echo -e "${BRAND}AgileFlow${RESET}"
|
|
19
|
+
|
|
20
|
+
# ============================================================================
|
|
21
|
+
# Reset and Modifiers
|
|
22
|
+
# ============================================================================
|
|
23
|
+
RESET="\033[0m"
|
|
24
|
+
BOLD="\033[1m"
|
|
25
|
+
DIM="\033[2m"
|
|
26
|
+
ITALIC="\033[3m"
|
|
27
|
+
UNDERLINE="\033[4m"
|
|
28
|
+
|
|
29
|
+
# ============================================================================
|
|
30
|
+
# Standard ANSI Colors (8 colors)
|
|
31
|
+
# ============================================================================
|
|
32
|
+
RED="\033[31m"
|
|
33
|
+
GREEN="\033[32m"
|
|
34
|
+
YELLOW="\033[33m"
|
|
35
|
+
BLUE="\033[34m"
|
|
36
|
+
MAGENTA="\033[35m"
|
|
37
|
+
CYAN="\033[36m"
|
|
38
|
+
WHITE="\033[37m"
|
|
39
|
+
BLACK="\033[30m"
|
|
40
|
+
|
|
41
|
+
# ============================================================================
|
|
42
|
+
# Bright Variants
|
|
43
|
+
# ============================================================================
|
|
44
|
+
BRIGHT_RED="\033[91m"
|
|
45
|
+
BRIGHT_GREEN="\033[92m"
|
|
46
|
+
BRIGHT_YELLOW="\033[93m"
|
|
47
|
+
BRIGHT_BLUE="\033[94m"
|
|
48
|
+
BRIGHT_MAGENTA="\033[95m"
|
|
49
|
+
BRIGHT_CYAN="\033[96m"
|
|
50
|
+
BRIGHT_WHITE="\033[97m"
|
|
51
|
+
BRIGHT_BLACK="\033[90m"
|
|
52
|
+
|
|
53
|
+
# ============================================================================
|
|
54
|
+
# Semantic Aliases (for consistent meaning across codebase)
|
|
55
|
+
# ============================================================================
|
|
56
|
+
SUCCESS="$GREEN"
|
|
57
|
+
ERROR="$RED"
|
|
58
|
+
WARNING="$YELLOW"
|
|
59
|
+
INFO="$CYAN"
|
|
60
|
+
|
|
61
|
+
# ============================================================================
|
|
62
|
+
# 256-Color Palette (vibrant, modern look)
|
|
63
|
+
# ============================================================================
|
|
64
|
+
MINT_GREEN="\033[38;5;158m" # Healthy/success states
|
|
65
|
+
PEACH="\033[38;5;215m" # Warning states
|
|
66
|
+
CORAL="\033[38;5;203m" # Critical/error states
|
|
67
|
+
LIGHT_GREEN="\033[38;5;194m" # Session healthy
|
|
68
|
+
LIGHT_YELLOW="\033[38;5;228m" # Session warning
|
|
69
|
+
LIGHT_PINK="\033[38;5;210m" # Session critical
|
|
70
|
+
SKY_BLUE="\033[38;5;117m" # Directories/paths, ready states
|
|
71
|
+
LAVENDER="\033[38;5;147m" # Model info, story IDs
|
|
72
|
+
SOFT_GOLD="\033[38;5;222m" # Cost/money
|
|
73
|
+
TEAL="\033[38;5;80m" # Pending states
|
|
74
|
+
SLATE="\033[38;5;103m" # Secondary info
|
|
75
|
+
ROSE="\033[38;5;211m" # Blocked/critical accent
|
|
76
|
+
AMBER="\033[38;5;214m" # WIP/in-progress accent
|
|
77
|
+
POWDER="\033[38;5;153m" # Labels/headers
|
|
78
|
+
|
|
79
|
+
# ============================================================================
|
|
80
|
+
# Context/Usage Colors (for status indicators)
|
|
81
|
+
# ============================================================================
|
|
82
|
+
CTX_GREEN="$MINT_GREEN" # Healthy context
|
|
83
|
+
CTX_YELLOW="$PEACH" # Moderate usage
|
|
84
|
+
CTX_ORANGE="$PEACH" # High usage (alias)
|
|
85
|
+
CTX_RED="$CORAL" # Critical
|
|
86
|
+
|
|
87
|
+
# ============================================================================
|
|
88
|
+
# Session Time Colors
|
|
89
|
+
# ============================================================================
|
|
90
|
+
SESSION_GREEN="$LIGHT_GREEN" # Plenty of time
|
|
91
|
+
SESSION_YELLOW="$LIGHT_YELLOW" # Getting low
|
|
92
|
+
SESSION_RED="$LIGHT_PINK" # Critical
|
|
93
|
+
|
|
94
|
+
# ============================================================================
|
|
95
|
+
# Brand Color (#e8683a - burnt orange/terracotta)
|
|
96
|
+
# ============================================================================
|
|
97
|
+
BRAND="\033[38;2;232;104;58m"
|
|
98
|
+
ORANGE="$BRAND" # Alias for brand color
|
|
99
|
+
|
|
100
|
+
# ============================================================================
|
|
101
|
+
# Background Colors
|
|
102
|
+
# ============================================================================
|
|
103
|
+
BG_RED="\033[41m"
|
|
104
|
+
BG_GREEN="\033[42m"
|
|
105
|
+
BG_YELLOW="\033[43m"
|
|
106
|
+
BG_BLUE="\033[44m"
|
|
@@ -376,7 +376,7 @@ function getMyFileOverlaps(options = {}) {
|
|
|
376
376
|
for (const file of sessionData.files || []) {
|
|
377
377
|
if (myFiles.has(file)) {
|
|
378
378
|
// Find or create overlap entry
|
|
379
|
-
let overlap = overlaps.find(
|
|
379
|
+
let overlap = overlaps.find(o => o.file === file);
|
|
380
380
|
if (!overlap) {
|
|
381
381
|
overlap = { file, otherSessions: [] };
|
|
382
382
|
overlaps.push(overlap);
|
|
@@ -499,13 +499,15 @@ function formatFileOverlaps(overlaps) {
|
|
|
499
499
|
|
|
500
500
|
// Format session info
|
|
501
501
|
const sessionInfo = overlap.otherSessions
|
|
502
|
-
.map(
|
|
502
|
+
.map(s => {
|
|
503
503
|
const dir = path.basename(s.path);
|
|
504
504
|
return `Session ${s.id} (${dir})`;
|
|
505
505
|
})
|
|
506
506
|
.join(', ');
|
|
507
507
|
|
|
508
|
-
lines.push(
|
|
508
|
+
lines.push(
|
|
509
|
+
` ${prefix} ${c.lavender}${overlap.file}${c.reset} ${c.dim}→ Also edited by ${sessionInfo}${c.reset}`
|
|
510
|
+
);
|
|
509
511
|
}
|
|
510
512
|
|
|
511
513
|
lines.push(` ${c.dim}Tip: Conflicts will be auto-resolved during session merge${c.reset}`);
|