agileflow 2.85.0 → 2.87.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 +53 -50
- package/scripts/agileflow-welcome.js +25 -8
- 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 +206 -85
- package/scripts/session-boundary.js +3 -3
- package/scripts/session-manager.js +339 -18
- 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 +273 -689
- 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 +30 -22
- 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
|
@@ -26,8 +26,67 @@ const { isValidCommandName } = require('../lib/validate');
|
|
|
26
26
|
// Summary table should be the LAST thing visible before truncation.
|
|
27
27
|
const DISPLAY_LIMIT = 29200;
|
|
28
28
|
|
|
29
|
-
//
|
|
29
|
+
// =============================================================================
|
|
30
|
+
// Progressive Disclosure: Section Activation
|
|
31
|
+
// =============================================================================
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Parse command-line arguments and determine which sections to activate.
|
|
35
|
+
* Sections are conditionally loaded based on parameters like MODE=loop.
|
|
36
|
+
*
|
|
37
|
+
* Section mapping:
|
|
38
|
+
* - MODE=loop → activates: loop-mode
|
|
39
|
+
* - Multi-session env → activates: multi-session
|
|
40
|
+
* - (Other triggers detected at runtime by the agent)
|
|
41
|
+
*
|
|
42
|
+
* @param {string[]} args - Command-line arguments after command name
|
|
43
|
+
* @returns {Object} { activeSections: string[], params: Object }
|
|
44
|
+
*/
|
|
45
|
+
function parseCommandArgs(args) {
|
|
46
|
+
const activeSections = [];
|
|
47
|
+
const params = {};
|
|
48
|
+
|
|
49
|
+
for (const arg of args) {
|
|
50
|
+
// Parse KEY=VALUE arguments
|
|
51
|
+
const match = arg.match(/^([A-Z_]+)=(.+)$/i);
|
|
52
|
+
if (match) {
|
|
53
|
+
const [, key, value] = match;
|
|
54
|
+
params[key.toUpperCase()] = value;
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Activate sections based on parameters
|
|
59
|
+
if (params.MODE === 'loop') {
|
|
60
|
+
activeSections.push('loop-mode');
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (params.VISUAL === 'true') {
|
|
64
|
+
activeSections.push('visual-e2e');
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Check for multi-session environment
|
|
68
|
+
const registryPath = '.agileflow/sessions/registry.json';
|
|
69
|
+
if (fs.existsSync(registryPath)) {
|
|
70
|
+
try {
|
|
71
|
+
const registry = JSON.parse(fs.readFileSync(registryPath, 'utf8'));
|
|
72
|
+
const sessionCount = Object.keys(registry.sessions || {}).length;
|
|
73
|
+
if (sessionCount > 1) {
|
|
74
|
+
activeSections.push('multi-session');
|
|
75
|
+
}
|
|
76
|
+
} catch {
|
|
77
|
+
// Silently ignore registry read errors
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
return { activeSections, params };
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Parse arguments
|
|
30
85
|
const commandName = process.argv[2];
|
|
86
|
+
const commandArgs = process.argv.slice(3);
|
|
87
|
+
const { activeSections, params: commandParams } = parseCommandArgs(commandArgs);
|
|
88
|
+
|
|
89
|
+
// Register command for PreCompact context preservation
|
|
31
90
|
if (commandName && isValidCommandName(commandName)) {
|
|
32
91
|
const sessionStatePath = 'docs/09-agents/session-state.json';
|
|
33
92
|
if (fs.existsSync(sessionStatePath)) {
|
|
@@ -42,11 +101,13 @@ if (commandName && isValidCommandName(commandName)) {
|
|
|
42
101
|
// Remove any existing entry for this command (avoid duplicates)
|
|
43
102
|
state.active_commands = state.active_commands.filter(c => c.name !== commandName);
|
|
44
103
|
|
|
45
|
-
// Add the new command
|
|
104
|
+
// Add the new command with active sections for progressive disclosure
|
|
46
105
|
state.active_commands.push({
|
|
47
106
|
name: commandName,
|
|
48
107
|
activated_at: new Date().toISOString(),
|
|
49
108
|
state: {},
|
|
109
|
+
active_sections: activeSections,
|
|
110
|
+
params: commandParams,
|
|
50
111
|
});
|
|
51
112
|
|
|
52
113
|
// Remove legacy active_command field (only use active_commands array now)
|
|
@@ -262,6 +323,13 @@ function generateSummary() {
|
|
|
262
323
|
summary += row('⭐ Up Next', readyStories.slice(0, 3).join(', '), C.skyBlue, C.skyBlue);
|
|
263
324
|
}
|
|
264
325
|
|
|
326
|
+
// Progressive disclosure: Show active sections
|
|
327
|
+
if (activeSections.length > 0) {
|
|
328
|
+
summary += divider();
|
|
329
|
+
const sectionList = activeSections.join(', ');
|
|
330
|
+
summary += row('📖 Sections', sectionList, C.cyan, C.mintGreen);
|
|
331
|
+
}
|
|
332
|
+
|
|
265
333
|
summary += divider();
|
|
266
334
|
|
|
267
335
|
// Key files (using vibrant 256-color palette)
|
|
@@ -318,6 +386,96 @@ function generateFullContent() {
|
|
|
318
386
|
content += `${C.lavender}${C.bold}${title}${C.reset}\n`;
|
|
319
387
|
content += `${C.dim}Generated: ${new Date().toISOString()}${C.reset}\n`;
|
|
320
388
|
|
|
389
|
+
// 0.5 SESSION CONTEXT BANNER (FIRST - before everything else)
|
|
390
|
+
// This is critical for multi-session awareness - agents need to know which session they're in
|
|
391
|
+
const sessionManagerPath = path.join(__dirname, 'session-manager.js');
|
|
392
|
+
const altSessionManagerPath = '.agileflow/scripts/session-manager.js';
|
|
393
|
+
|
|
394
|
+
if (fs.existsSync(sessionManagerPath) || fs.existsSync(altSessionManagerPath)) {
|
|
395
|
+
const managerPath = fs.existsSync(sessionManagerPath)
|
|
396
|
+
? sessionManagerPath
|
|
397
|
+
: altSessionManagerPath;
|
|
398
|
+
const sessionStatus = safeExec(`node "${managerPath}" status`);
|
|
399
|
+
|
|
400
|
+
if (sessionStatus) {
|
|
401
|
+
try {
|
|
402
|
+
const statusData = JSON.parse(sessionStatus);
|
|
403
|
+
if (statusData.current) {
|
|
404
|
+
const session = statusData.current;
|
|
405
|
+
const isMain = session.is_main === true;
|
|
406
|
+
const sessionName = session.nickname
|
|
407
|
+
? `Session ${session.id} "${session.nickname}"`
|
|
408
|
+
: `Session ${session.id}`;
|
|
409
|
+
|
|
410
|
+
content += `\n${C.teal}${C.bold}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${C.reset}\n`;
|
|
411
|
+
content += `${C.teal}${C.bold}📍 SESSION CONTEXT${C.reset}\n`;
|
|
412
|
+
content += `${C.teal}${C.bold}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${C.reset}\n`;
|
|
413
|
+
|
|
414
|
+
if (isMain) {
|
|
415
|
+
content += `${C.mintGreen}${C.bold}${sessionName}${C.reset} ${C.dim}(main project)${C.reset}\n`;
|
|
416
|
+
} else {
|
|
417
|
+
content += `${C.peach}${C.bold}🔀 ${sessionName}${C.reset} ${C.dim}(worktree)${C.reset}\n`;
|
|
418
|
+
content += `Branch: ${C.skyBlue}${session.branch || 'unknown'}${C.reset}\n`;
|
|
419
|
+
content += `${C.dim}Path: ${session.path || process.cwd()}${C.reset}\n`;
|
|
420
|
+
}
|
|
421
|
+
|
|
422
|
+
// Show other active sessions prominently
|
|
423
|
+
if (statusData.otherActive > 0) {
|
|
424
|
+
content += `${C.amber}⚠️ ${statusData.otherActive} other active session(s)${C.reset} - check story claims below\n`;
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
content += `${C.teal}${C.bold}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${C.reset}\n\n`;
|
|
428
|
+
}
|
|
429
|
+
} catch (e) {
|
|
430
|
+
// Silently ignore session parse errors - will still show detailed session context later
|
|
431
|
+
}
|
|
432
|
+
}
|
|
433
|
+
}
|
|
434
|
+
|
|
435
|
+
// 0.7 INTERACTION MODE (AskUserQuestion) - EARLY for visibility
|
|
436
|
+
// This MUST appear before other content to ensure Claude sees it
|
|
437
|
+
const earlyMetadata = safeReadJSON('docs/00-meta/agileflow-metadata.json');
|
|
438
|
+
const askUserQuestionConfig = earlyMetadata?.features?.askUserQuestion;
|
|
439
|
+
|
|
440
|
+
if (askUserQuestionConfig?.enabled) {
|
|
441
|
+
content += `${C.coral}${C.bold}┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓${C.reset}\n`;
|
|
442
|
+
content += `${C.coral}${C.bold}┃ 🔔 MANDATORY: AskUserQuestion After EVERY Response ┃${C.reset}\n`;
|
|
443
|
+
content += `${C.coral}${C.bold}┗━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┛${C.reset}\n`;
|
|
444
|
+
content += `${C.bold}After completing ANY task${C.reset} (implementation, fix, etc.):\n`;
|
|
445
|
+
content += `${C.mintGreen}→ ALWAYS${C.reset} call ${C.skyBlue}AskUserQuestion${C.reset} tool to offer next steps\n`;
|
|
446
|
+
content += `${C.coral}→ NEVER${C.reset} end with text like "Done!" or "What's next?"\n\n`;
|
|
447
|
+
content += `${C.dim}Balance: Use at natural pause points. Don't ask permission for routine work.${C.reset}\n\n`;
|
|
448
|
+
}
|
|
449
|
+
|
|
450
|
+
// 0. PROGRESSIVE DISCLOSURE (section activation)
|
|
451
|
+
if (activeSections.length > 0) {
|
|
452
|
+
content += `\n${C.cyan}${C.bold}═══ 📖 Progressive Disclosure: Active Sections ═══${C.reset}\n`;
|
|
453
|
+
content += `${C.dim}The following sections are activated based on command parameters.${C.reset}\n`;
|
|
454
|
+
content += `${C.dim}Look for <!-- SECTION: name --> markers in the command file.${C.reset}\n\n`;
|
|
455
|
+
|
|
456
|
+
activeSections.forEach(section => {
|
|
457
|
+
content += ` ${C.mintGreen}✓${C.reset} ${C.bold}${section}${C.reset}\n`;
|
|
458
|
+
});
|
|
459
|
+
|
|
460
|
+
// Map sections to their triggers for context
|
|
461
|
+
const sectionDescriptions = {
|
|
462
|
+
'loop-mode': 'Autonomous epic execution (MODE=loop)',
|
|
463
|
+
'multi-session': 'Multi-session coordination detected',
|
|
464
|
+
'visual-e2e': 'Visual screenshot verification (VISUAL=true)',
|
|
465
|
+
'delegation': 'Expert spawning patterns (load when spawning)',
|
|
466
|
+
'stuck': 'Research prompt guidance (load after 2 failures)',
|
|
467
|
+
'plan-mode': 'Planning workflow details (load when entering plan mode)',
|
|
468
|
+
'tools': 'Tool usage guidance (load when needed)',
|
|
469
|
+
};
|
|
470
|
+
|
|
471
|
+
content += `\n${C.dim}Section meanings:${C.reset}\n`;
|
|
472
|
+
activeSections.forEach(section => {
|
|
473
|
+
const desc = sectionDescriptions[section] || 'Conditional content';
|
|
474
|
+
content += ` ${C.dim}• ${section}: ${desc}${C.reset}\n`;
|
|
475
|
+
});
|
|
476
|
+
content += '\n';
|
|
477
|
+
}
|
|
478
|
+
|
|
321
479
|
// 1. GIT STATUS (using vibrant 256-color palette)
|
|
322
480
|
content += `\n${C.skyBlue}${C.bold}═══ Git Status ═══${C.reset}\n`;
|
|
323
481
|
const branch = safeExec('git branch --show-current') || 'unknown';
|
|
@@ -376,62 +534,52 @@ function generateFullContent() {
|
|
|
376
534
|
// Backwards compatibility for old format
|
|
377
535
|
content += `Active command: ${C.skyBlue}${sessionState.active_command.name}${C.reset}\n`;
|
|
378
536
|
}
|
|
537
|
+
|
|
538
|
+
// Show batch loop status if active
|
|
539
|
+
const batchLoop = sessionState.batch_loop;
|
|
540
|
+
if (batchLoop && batchLoop.enabled) {
|
|
541
|
+
content += `\n${C.skyBlue}${C.bold}── Batch Loop Active ──${C.reset}\n`;
|
|
542
|
+
content += `Pattern: ${C.cyan}${batchLoop.pattern}${C.reset}\n`;
|
|
543
|
+
content += `Action: ${C.cyan}${batchLoop.action}${C.reset}\n`;
|
|
544
|
+
content += `Current: ${C.lightYellow}${batchLoop.current_item || 'none'}${C.reset}\n`;
|
|
545
|
+
const summary = batchLoop.summary || {};
|
|
546
|
+
content += `Progress: ${C.lightGreen}${summary.completed || 0}${C.reset}/${summary.total || 0} `;
|
|
547
|
+
content += `(${C.lightYellow}${summary.in_progress || 0}${C.reset} in progress)\n`;
|
|
548
|
+
content += `Iteration: ${batchLoop.iteration || 0}/${batchLoop.max_iterations || 50}\n`;
|
|
549
|
+
}
|
|
379
550
|
} else {
|
|
380
551
|
content += `${C.dim}No session-state.json found${C.reset}\n`;
|
|
381
552
|
}
|
|
382
553
|
|
|
383
|
-
// 4. SESSION CONTEXT (
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
const
|
|
554
|
+
// 4. SESSION CONTEXT (details - banner shown above)
|
|
555
|
+
// Note: Prominent SESSION CONTEXT banner is shown at the top of output
|
|
556
|
+
// This section provides additional details for non-main sessions
|
|
557
|
+
const sessionMgrPath = path.join(__dirname, 'session-manager.js');
|
|
558
|
+
const altSessionMgrPath = '.agileflow/scripts/session-manager.js';
|
|
387
559
|
|
|
388
|
-
if (fs.existsSync(
|
|
389
|
-
const
|
|
390
|
-
|
|
391
|
-
: altSessionManagerPath;
|
|
392
|
-
const sessionStatus = safeExec(`node "${managerPath}" status`);
|
|
560
|
+
if (fs.existsSync(sessionMgrPath) || fs.existsSync(altSessionMgrPath)) {
|
|
561
|
+
const mgrPath = fs.existsSync(sessionMgrPath) ? sessionMgrPath : altSessionMgrPath;
|
|
562
|
+
const sessionStatusStr = safeExec(`node "${mgrPath}" status`);
|
|
393
563
|
|
|
394
|
-
if (
|
|
564
|
+
if (sessionStatusStr) {
|
|
395
565
|
try {
|
|
396
|
-
const statusData = JSON.parse(
|
|
397
|
-
if (statusData.current) {
|
|
566
|
+
const statusData = JSON.parse(sessionStatusStr);
|
|
567
|
+
if (statusData.current && !statusData.current.is_main) {
|
|
568
|
+
// Only show additional details for non-main sessions
|
|
569
|
+
content += `\n${C.skyBlue}${C.bold}═══ Session Details ═══${C.reset}\n`;
|
|
398
570
|
const session = statusData.current;
|
|
399
|
-
const isMain = session.is_main === true;
|
|
400
|
-
|
|
401
|
-
if (isMain) {
|
|
402
|
-
content += `Session: ${C.mintGreen}Main project${C.reset} (Session ${session.id || 1})\n`;
|
|
403
|
-
} else {
|
|
404
|
-
// NON-MAIN SESSION - Show prominent banner
|
|
405
|
-
const sessionName = session.nickname
|
|
406
|
-
? `${session.id} "${session.nickname}"`
|
|
407
|
-
: `${session.id}`;
|
|
408
|
-
content += `${C.teal}${C.bold}🔀 SESSION ${sessionName} (worktree)${C.reset}\n`;
|
|
409
|
-
content += `Branch: ${C.skyBlue}${session.branch || 'unknown'}${C.reset}\n`;
|
|
410
|
-
content += `Path: ${C.dim}${session.path || process.cwd()}${C.reset}\n`;
|
|
411
571
|
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
572
|
+
// Calculate relative path to main
|
|
573
|
+
const mainPath = process.cwd().replace(/-[^/]+$/, ''); // Heuristic: strip session suffix
|
|
574
|
+
content += `Main project: ${C.dim}${mainPath}${C.reset}\n`;
|
|
415
575
|
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
}
|
|
419
|
-
|
|
420
|
-
// Show other active sessions
|
|
421
|
-
if (statusData.otherActive > 0) {
|
|
422
|
-
content += `${C.peach}⚠️ ${statusData.otherActive} other session(s) active${C.reset}\n`;
|
|
423
|
-
}
|
|
424
|
-
} else {
|
|
425
|
-
content += `${C.dim}No session registered${C.reset}\n`;
|
|
576
|
+
// Remind about merge flow
|
|
577
|
+
content += `${C.lavender}💡 When done: /agileflow:session:end → merge to main${C.reset}\n`;
|
|
426
578
|
}
|
|
427
579
|
} catch (e) {
|
|
428
|
-
|
|
580
|
+
// Silently ignore - banner above has basic info
|
|
429
581
|
}
|
|
430
|
-
} else {
|
|
431
|
-
content += `${C.dim}Session manager available${C.reset}\n`;
|
|
432
582
|
}
|
|
433
|
-
} else {
|
|
434
|
-
content += `${C.dim}Multi-session not configured${C.reset}\n`;
|
|
435
583
|
}
|
|
436
584
|
|
|
437
585
|
// 5. STORY CLAIMS (inter-session coordination)
|
|
@@ -440,9 +588,7 @@ function generateFullContent() {
|
|
|
440
588
|
|
|
441
589
|
if (fs.existsSync(storyClaimingPath) || fs.existsSync(altStoryClaimingPath)) {
|
|
442
590
|
try {
|
|
443
|
-
const claimPath = fs.existsSync(storyClaimingPath)
|
|
444
|
-
? storyClaimingPath
|
|
445
|
-
: altStoryClaimingPath;
|
|
591
|
+
const claimPath = fs.existsSync(storyClaimingPath) ? storyClaimingPath : altStoryClaimingPath;
|
|
446
592
|
const storyClaiming = require(claimPath);
|
|
447
593
|
|
|
448
594
|
// Get stories claimed by other sessions
|
|
@@ -451,7 +597,9 @@ function generateFullContent() {
|
|
|
451
597
|
content += `\n${C.amber}${C.bold}═══ 🔒 Claimed Stories ═══${C.reset}\n`;
|
|
452
598
|
content += `${C.dim}Stories locked by other sessions - pick a different one${C.reset}\n`;
|
|
453
599
|
othersResult.stories.forEach(story => {
|
|
454
|
-
const sessionDir = story.claimedBy?.path
|
|
600
|
+
const sessionDir = story.claimedBy?.path
|
|
601
|
+
? path.basename(story.claimedBy.path)
|
|
602
|
+
: 'unknown';
|
|
455
603
|
content += ` ${C.coral}🔒${C.reset} ${C.lavender}${story.id}${C.reset} "${story.title}" ${C.dim}→ Session ${story.claimedBy?.session_id || '?'} (${sessionDir})${C.reset}\n`;
|
|
456
604
|
});
|
|
457
605
|
content += '\n';
|
|
@@ -477,9 +625,7 @@ function generateFullContent() {
|
|
|
477
625
|
|
|
478
626
|
if (fs.existsSync(fileTrackingPath) || fs.existsSync(altFileTrackingPath)) {
|
|
479
627
|
try {
|
|
480
|
-
const trackPath = fs.existsSync(fileTrackingPath)
|
|
481
|
-
? fileTrackingPath
|
|
482
|
-
: altFileTrackingPath;
|
|
628
|
+
const trackPath = fs.existsSync(fileTrackingPath) ? fileTrackingPath : altFileTrackingPath;
|
|
483
629
|
const fileTracking = require(trackPath);
|
|
484
630
|
|
|
485
631
|
// Get file overlaps with other sessions
|
|
@@ -488,10 +634,12 @@ function generateFullContent() {
|
|
|
488
634
|
content += `\n${C.amber}${C.bold}═══ ⚠️ File Overlaps ═══${C.reset}\n`;
|
|
489
635
|
content += `${C.dim}Files also edited by other sessions - conflicts auto-resolved during merge${C.reset}\n`;
|
|
490
636
|
overlapsResult.overlaps.forEach(overlap => {
|
|
491
|
-
const sessionInfo = overlap.otherSessions
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
637
|
+
const sessionInfo = overlap.otherSessions
|
|
638
|
+
.map(s => {
|
|
639
|
+
const dir = path.basename(s.path);
|
|
640
|
+
return `Session ${s.id} (${dir})`;
|
|
641
|
+
})
|
|
642
|
+
.join(', ');
|
|
495
643
|
content += ` ${C.amber}⚠${C.reset} ${C.lavender}${overlap.file}${C.reset} ${C.dim}→ ${sessionInfo}${C.reset}\n`;
|
|
496
644
|
});
|
|
497
645
|
content += '\n';
|
|
@@ -524,7 +672,8 @@ function generateFullContent() {
|
|
|
524
672
|
// 6. VISUAL E2E STATUS (detect from metadata or filesystem)
|
|
525
673
|
const metadata = safeReadJSON('docs/00-meta/agileflow-metadata.json');
|
|
526
674
|
const visualE2eConfig = metadata?.features?.visual_e2e;
|
|
527
|
-
const playwrightExists =
|
|
675
|
+
const playwrightExists =
|
|
676
|
+
fs.existsSync('playwright.config.ts') || fs.existsSync('playwright.config.js');
|
|
528
677
|
const screenshotsExists = fs.existsSync('screenshots');
|
|
529
678
|
const testsE2eExists = fs.existsSync('tests/e2e');
|
|
530
679
|
|
|
@@ -551,35 +700,7 @@ function generateFullContent() {
|
|
|
551
700
|
content += `${C.dim} /agileflow:configure → Visual E2E testing${C.reset}\n\n`;
|
|
552
701
|
}
|
|
553
702
|
|
|
554
|
-
//
|
|
555
|
-
const askUserQuestionConfig = metadata?.features?.askUserQuestion;
|
|
556
|
-
|
|
557
|
-
if (askUserQuestionConfig?.enabled) {
|
|
558
|
-
content += `\n${C.brand}${C.bold}═══ ⚡ INTERACTION MODE: AskUserQuestion ENABLED ═══${C.reset}\n`;
|
|
559
|
-
content += `${C.dim}${'─'.repeat(60)}${C.reset}\n`;
|
|
560
|
-
content += `${C.bold}CRITICAL RULE:${C.reset} End ${C.skyBlue}EVERY${C.reset} response with the AskUserQuestion tool.\n\n`;
|
|
561
|
-
content += `${C.mintGreen}✓ CORRECT:${C.reset} Call the actual AskUserQuestion tool\n`;
|
|
562
|
-
content += `${C.coral}✗ WRONG:${C.reset} Text like "Want me to continue?" or "What's next?"\n\n`;
|
|
563
|
-
content += `${C.lavender}Required format:${C.reset}\n`;
|
|
564
|
-
content += `${C.dim}\`\`\`xml
|
|
565
|
-
<invoke name="AskUserQuestion">
|
|
566
|
-
<parameter name="questions">[{
|
|
567
|
-
"question": "What would you like to do next?",
|
|
568
|
-
"header": "Next step",
|
|
569
|
-
"multiSelect": false,
|
|
570
|
-
"options": [
|
|
571
|
-
{"label": "Option A (Recommended)", "description": "Why this is best"},
|
|
572
|
-
{"label": "Option B", "description": "Alternative approach"},
|
|
573
|
-
{"label": "Pause", "description": "Stop here for now"}
|
|
574
|
-
]
|
|
575
|
-
}]</parameter>
|
|
576
|
-
</invoke>
|
|
577
|
-
\`\`\`${C.reset}\n`;
|
|
578
|
-
content += `${C.dim}${'─'.repeat(60)}${C.reset}\n`;
|
|
579
|
-
content += `${C.dim}Mode: ${askUserQuestionConfig.mode || 'all'} | Configure: /agileflow:configure${C.reset}\n\n`;
|
|
580
|
-
}
|
|
581
|
-
|
|
582
|
-
// 5. DOCS STRUCTURE (using vibrant 256-color palette)
|
|
703
|
+
// DOCS STRUCTURE (using vibrant 256-color palette)
|
|
583
704
|
content += `\n${C.skyBlue}${C.bold}═══ Documentation ═══${C.reset}\n`;
|
|
584
705
|
const docsDir = 'docs';
|
|
585
706
|
const docFolders = safeLs(docsDir).filter(f => {
|
|
@@ -61,8 +61,9 @@ function isInsideSession(filePath, sessionPath) {
|
|
|
61
61
|
const normalizedFile = path.resolve(filePath);
|
|
62
62
|
const normalizedSession = path.resolve(sessionPath);
|
|
63
63
|
|
|
64
|
-
return
|
|
65
|
-
|
|
64
|
+
return (
|
|
65
|
+
normalizedFile.startsWith(normalizedSession + path.sep) || normalizedFile === normalizedSession
|
|
66
|
+
);
|
|
66
67
|
}
|
|
67
68
|
|
|
68
69
|
// Output blocked message
|
|
@@ -117,7 +118,6 @@ function main() {
|
|
|
117
118
|
// File is OUTSIDE active session - BLOCK
|
|
118
119
|
outputBlocked(filePath, activeSession);
|
|
119
120
|
process.exit(2);
|
|
120
|
-
|
|
121
121
|
} catch (e) {
|
|
122
122
|
// Parse error or other issue - fail open
|
|
123
123
|
process.exit(0);
|