agileflow 2.85.0 → 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.
@@ -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((o) => o.file === file);
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((s) => {
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(` ${prefix} ${c.lavender}${overlap.file}${c.reset} ${c.dim}→ Also edited by ${sessionInfo}${c.reset}`);
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}`);