agileflow 3.1.0 → 3.2.1

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.
Files changed (106) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +57 -85
  3. package/lib/dashboard-automations.js +130 -0
  4. package/lib/dashboard-git.js +254 -0
  5. package/lib/dashboard-inbox.js +64 -0
  6. package/lib/dashboard-protocol.js +1 -0
  7. package/lib/dashboard-server.js +114 -924
  8. package/lib/dashboard-session.js +136 -0
  9. package/lib/dashboard-status.js +72 -0
  10. package/lib/dashboard-terminal.js +354 -0
  11. package/lib/dashboard-websocket.js +88 -0
  12. package/lib/drivers/codex-driver.ts +4 -4
  13. package/lib/logger.js +106 -0
  14. package/package.json +4 -2
  15. package/scripts/agileflow-configure.js +2 -2
  16. package/scripts/agileflow-welcome.js +409 -434
  17. package/scripts/claude-tmux.sh +80 -2
  18. package/scripts/context-loader.js +4 -9
  19. package/scripts/lib/browser-qa-evidence.js +409 -0
  20. package/scripts/lib/browser-qa-status.js +192 -0
  21. package/scripts/lib/command-prereqs.js +280 -0
  22. package/scripts/lib/configure-detect.js +92 -2
  23. package/scripts/lib/configure-features.js +295 -1
  24. package/scripts/lib/context-formatter.js +468 -233
  25. package/scripts/lib/context-loader.js +27 -15
  26. package/scripts/lib/damage-control-utils.js +8 -1
  27. package/scripts/lib/feature-catalog.js +321 -0
  28. package/scripts/lib/portable-tasks-cli.js +274 -0
  29. package/scripts/lib/portable-tasks.js +479 -0
  30. package/scripts/lib/signal-detectors.js +1 -1
  31. package/scripts/lib/team-events.js +86 -1
  32. package/scripts/obtain-context.js +28 -4
  33. package/scripts/smart-detect.js +17 -0
  34. package/scripts/strip-ai-attribution.js +63 -0
  35. package/scripts/team-manager.js +7 -2
  36. package/scripts/welcome-deferred.js +437 -0
  37. package/src/core/agents/browser-qa.md +328 -0
  38. package/src/core/agents/perf-analyzer-assets.md +174 -0
  39. package/src/core/agents/perf-analyzer-bundle.md +165 -0
  40. package/src/core/agents/perf-analyzer-caching.md +160 -0
  41. package/src/core/agents/perf-analyzer-compute.md +165 -0
  42. package/src/core/agents/perf-analyzer-memory.md +182 -0
  43. package/src/core/agents/perf-analyzer-network.md +157 -0
  44. package/src/core/agents/perf-analyzer-queries.md +155 -0
  45. package/src/core/agents/perf-analyzer-rendering.md +156 -0
  46. package/src/core/agents/perf-consensus.md +280 -0
  47. package/src/core/agents/security-analyzer-api.md +199 -0
  48. package/src/core/agents/security-analyzer-auth.md +160 -0
  49. package/src/core/agents/security-analyzer-authz.md +168 -0
  50. package/src/core/agents/security-analyzer-deps.md +147 -0
  51. package/src/core/agents/security-analyzer-infra.md +176 -0
  52. package/src/core/agents/security-analyzer-injection.md +148 -0
  53. package/src/core/agents/security-analyzer-input.md +191 -0
  54. package/src/core/agents/security-analyzer-secrets.md +175 -0
  55. package/src/core/agents/security-consensus.md +276 -0
  56. package/src/core/agents/test-analyzer-assertions.md +181 -0
  57. package/src/core/agents/test-analyzer-coverage.md +183 -0
  58. package/src/core/agents/test-analyzer-fragility.md +185 -0
  59. package/src/core/agents/test-analyzer-integration.md +155 -0
  60. package/src/core/agents/test-analyzer-maintenance.md +173 -0
  61. package/src/core/agents/test-analyzer-mocking.md +178 -0
  62. package/src/core/agents/test-analyzer-patterns.md +189 -0
  63. package/src/core/agents/test-analyzer-structure.md +177 -0
  64. package/src/core/agents/test-consensus.md +294 -0
  65. package/src/core/commands/{legal/audit.md → audit/legal.md} +13 -13
  66. package/src/core/commands/{logic/audit.md → audit/logic.md} +12 -12
  67. package/src/core/commands/audit/performance.md +443 -0
  68. package/src/core/commands/audit/security.md +443 -0
  69. package/src/core/commands/audit/test.md +442 -0
  70. package/src/core/commands/babysit.md +505 -463
  71. package/src/core/commands/browser-qa.md +240 -0
  72. package/src/core/commands/configure.md +8 -8
  73. package/src/core/commands/research/ask.md +42 -9
  74. package/src/core/commands/research/import.md +14 -8
  75. package/src/core/commands/research/list.md +17 -16
  76. package/src/core/commands/research/synthesize.md +8 -8
  77. package/src/core/commands/research/view.md +28 -4
  78. package/src/core/commands/whats-new.md +2 -2
  79. package/src/core/experts/devops/expertise.yaml +13 -2
  80. package/src/core/experts/documentation/expertise.yaml +26 -4
  81. package/src/core/profiles/COMPARISON.md +170 -0
  82. package/src/core/profiles/README.md +178 -0
  83. package/src/core/profiles/claude-code.yaml +111 -0
  84. package/src/core/profiles/codex.yaml +103 -0
  85. package/src/core/profiles/cursor.yaml +134 -0
  86. package/src/core/profiles/examples.js +250 -0
  87. package/src/core/profiles/loader.js +235 -0
  88. package/src/core/profiles/windsurf.yaml +159 -0
  89. package/src/core/teams/logic-audit.json +6 -0
  90. package/src/core/teams/perf-audit.json +71 -0
  91. package/src/core/teams/security-audit.json +71 -0
  92. package/src/core/teams/test-audit.json +71 -0
  93. package/src/core/templates/browser-qa-spec.yaml +94 -0
  94. package/src/core/templates/command-prerequisites.yaml +169 -0
  95. package/src/core/templates/damage-control-patterns.yaml +9 -0
  96. package/tools/cli/installers/ide/_base-ide.js +33 -3
  97. package/tools/cli/installers/ide/claude-code.js +2 -69
  98. package/tools/cli/installers/ide/codex.js +9 -9
  99. package/tools/cli/installers/ide/cursor.js +165 -4
  100. package/tools/cli/installers/ide/windsurf.js +237 -6
  101. package/tools/cli/lib/content-transformer.js +234 -9
  102. package/tools/cli/lib/docs-setup.js +1 -1
  103. package/tools/cli/lib/ide-generator.js +357 -0
  104. package/tools/cli/lib/ide-registry.js +2 -2
  105. package/scripts/tmux-task-name.sh +0 -105
  106. package/scripts/tmux-task-watcher.sh +0 -344
@@ -342,6 +342,14 @@ function generateSummary(prefetched = null, options = {}) {
342
342
  }
343
343
  }
344
344
 
345
+ // Command prerequisites
346
+ if (options.prereqResult && !options.prereqResult.allMet) {
347
+ const n = options.prereqResult.unmet.length;
348
+ const crit = options.prereqResult.criticalUnmet;
349
+ const prereqText = crit > 0 ? `${crit} critical missing` : `${n} unmet`;
350
+ summary += row('Prereqs', prereqText, C.coral, C.coral);
351
+ }
352
+
345
353
  // Key files
346
354
  const keyFileChecks = [
347
355
  { path: 'CLAUDE.md', label: 'CLAUDE' },
@@ -399,57 +407,72 @@ function generateSummary(prefetched = null, options = {}) {
399
407
  * @returns {string} Full content
400
408
  */
401
409
  function generateFullContent(prefetched = null, options = {}) {
402
- const { commandName = null, activeSections = [], smartDetectResults = null } = options;
410
+ const {
411
+ commandName = null,
412
+ activeSections = [],
413
+ smartDetectResults = null,
414
+ verbosityMode = 'full',
415
+ } = options;
416
+
417
+ // MINIMAL MODE: Return compact output only
418
+ if (verbosityMode === 'minimal') {
419
+ return generateMinimalContent(prefetched, options);
420
+ }
403
421
 
404
422
  let content = '';
405
423
 
406
- const title = commandName ? `AgileFlow Context [${commandName}]` : 'AgileFlow Context';
424
+ const modeLabel = verbosityMode !== 'full' ? ` [${verbosityMode}]` : '';
425
+ const title = commandName
426
+ ? `AgileFlow Context [${commandName}]${modeLabel}`
427
+ : `AgileFlow Context${modeLabel}`;
407
428
  content += `${C.lavender}${C.bold}${title}${C.reset}\n`;
408
429
  content += `${C.dim}Generated: ${new Date().toISOString()}${C.reset}\n`;
409
430
 
410
- // SESSION CONTEXT BANNER
411
- const sessionManagerPath = path.join(__dirname, '..', 'session-manager.js');
412
- const altSessionManagerPath = '.agileflow/scripts/session-manager.js';
431
+ // SESSION CONTEXT BANNER - skip in lite mode
432
+ if (verbosityMode === 'full') {
433
+ const sessionManagerPath = path.join(__dirname, '..', 'session-manager.js');
434
+ const altSessionManagerPath = '.agileflow/scripts/session-manager.js';
413
435
 
414
- if (fs.existsSync(sessionManagerPath) || fs.existsSync(altSessionManagerPath)) {
415
- const managerPath = fs.existsSync(sessionManagerPath)
416
- ? sessionManagerPath
417
- : altSessionManagerPath;
418
- const sessionStatus = safeExec(`node "${managerPath}" status`);
436
+ if (fs.existsSync(sessionManagerPath) || fs.existsSync(altSessionManagerPath)) {
437
+ const managerPath = fs.existsSync(sessionManagerPath)
438
+ ? sessionManagerPath
439
+ : altSessionManagerPath;
440
+ const sessionStatus = safeExec(`node "${managerPath}" status`);
419
441
 
420
- if (sessionStatus) {
421
- try {
422
- const statusData = JSON.parse(sessionStatus);
423
- if (statusData.current) {
424
- const session = statusData.current;
425
- const isMain = session.is_main === true;
426
- const sessionName = session.nickname
427
- ? `Session ${session.id} "${session.nickname}"`
428
- : `Session ${session.id}`;
429
-
430
- content += `\n${C.teal}${C.bold}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${C.reset}\n`;
431
- content += `${C.teal}${C.bold}📍 SESSION CONTEXT${C.reset}\n`;
432
- content += `${C.teal}${C.bold}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${C.reset}\n`;
433
-
434
- if (isMain) {
435
- content += `${C.mintGreen}${C.bold}${sessionName}${C.reset} ${C.dim}(main project)${C.reset}\n`;
436
- } else {
437
- content += `${C.peach}${C.bold}🔀 ${sessionName}${C.reset} ${C.dim}(worktree)${C.reset}\n`;
438
- content += `Branch: ${C.skyBlue}${session.branch || 'unknown'}${C.reset}\n`;
439
- content += `${C.dim}Path: ${session.path || process.cwd()}${C.reset}\n`;
440
- }
442
+ if (sessionStatus) {
443
+ try {
444
+ const statusData = JSON.parse(sessionStatus);
445
+ if (statusData.current) {
446
+ const session = statusData.current;
447
+ const isMain = session.is_main === true;
448
+ const sessionName = session.nickname
449
+ ? `Session ${session.id} "${session.nickname}"`
450
+ : `Session ${session.id}`;
451
+
452
+ content += `\n${C.teal}${C.bold}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${C.reset}\n`;
453
+ content += `${C.teal}${C.bold}📍 SESSION CONTEXT${C.reset}\n`;
454
+ content += `${C.teal}${C.bold}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${C.reset}\n`;
455
+
456
+ if (isMain) {
457
+ content += `${C.mintGreen}${C.bold}${sessionName}${C.reset} ${C.dim}(main project)${C.reset}\n`;
458
+ } else {
459
+ content += `${C.peach}${C.bold}🔀 ${sessionName}${C.reset} ${C.dim}(worktree)${C.reset}\n`;
460
+ content += `Branch: ${C.skyBlue}${session.branch || 'unknown'}${C.reset}\n`;
461
+ content += `${C.dim}Path: ${session.path || process.cwd()}${C.reset}\n`;
462
+ }
441
463
 
442
- if (statusData.otherActive > 0) {
443
- content += `${C.amber}⚠️ ${statusData.otherActive} other active session(s)${C.reset} - check story claims below\n`;
444
- }
464
+ if (statusData.otherActive > 0) {
465
+ content += `${C.amber}⚠️ ${statusData.otherActive} other active session(s)${C.reset} - check story claims below\n`;
466
+ }
445
467
 
446
- content += `${C.teal}${C.bold}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${C.reset}\n\n`;
468
+ content += `${C.teal}${C.bold}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${C.reset}\n\n`;
469
+ }
470
+ } catch {
471
+ // Silently ignore session parse errors
447
472
  }
448
- } catch {
449
- // Silently ignore session parse errors
450
473
  }
451
474
  }
452
- }
475
+ } // end verbosityMode === 'full' (session context banner)
453
476
 
454
477
  // INTERACTION MODE (AskUserQuestion)
455
478
  const earlyMetadata =
@@ -477,8 +500,22 @@ function generateFullContent(prefetched = null, options = {}) {
477
500
  content += generateContextWarning(contextUsage.percent);
478
501
  }
479
502
 
480
- // PROGRESSIVE DISCLOSURE
481
- if (activeSections.length > 0) {
503
+ // COMMAND PREREQUISITES WARNING
504
+ if (
505
+ options.prereqResult &&
506
+ !options.prereqResult.allMet &&
507
+ options.prereqResult.unmet.length > 0
508
+ ) {
509
+ try {
510
+ const { formatPrereqWarnings } = require('./command-prereqs');
511
+ content += formatPrereqWarnings(options.prereqResult);
512
+ } catch {
513
+ // Fail open
514
+ }
515
+ }
516
+
517
+ // PROGRESSIVE DISCLOSURE - skip in lite mode
518
+ if (activeSections.length > 0 && verbosityMode === 'full') {
482
519
  content += `\n${C.cyan}${C.bold}═══ 📖 Progressive Disclosure: Active Sections ═══${C.reset}\n`;
483
520
  content += `${C.dim}The following sections are activated based on command parameters.${C.reset}\n`;
484
521
  content += `${C.dim}Look for <!-- SECTION: name --> markers in the command file.${C.reset}\n\n`;
@@ -505,30 +542,32 @@ function generateFullContent(prefetched = null, options = {}) {
505
542
  content += '\n';
506
543
  }
507
544
 
508
- // SCALE DETECTION (EP-0033)
509
- let scaleDetector;
510
- try {
511
- scaleDetector = require('./scale-detector');
512
- } catch {
513
- /* not available */
514
- }
515
- if (scaleDetector) {
545
+ // SCALE DETECTION (EP-0033) - skip in lite mode
546
+ if (verbosityMode === 'full') {
547
+ let scaleDetector;
516
548
  try {
517
- const scaleResult = scaleDetector.detectScale({
518
- rootDir: process.cwd(),
519
- statusJson: prefetched?.json?.statusJson,
520
- sessionState: prefetched?.json?.sessionState,
521
- });
522
- const recs = scaleDetector.getScaleRecommendations(scaleResult.scale);
523
- const label = scaleDetector.getScaleLabel(scaleResult.scale);
524
- content += `\n${C.cyan}${C.bold}═══ Project Scale: ${label} ═══${C.reset}\n`;
525
- content += `${C.dim}Detected: ${scaleResult.metrics.files} files, ${scaleResult.metrics.stories} stories, ${scaleResult.metrics.commits} commits (6mo)${C.reset}\n`;
526
- content += `Planning depth: ${C.mintGreen}${recs.planningDepth}${C.reset} | Experts: ${C.mintGreen}${recs.expertCount}${C.reset}\n`;
527
- content += `${C.dim}${recs.description}${C.reset}\n`;
549
+ scaleDetector = require('./scale-detector');
528
550
  } catch {
529
- // Silently ignore scale detection errors
551
+ /* not available */
530
552
  }
531
- }
553
+ if (scaleDetector) {
554
+ try {
555
+ const scaleResult = scaleDetector.detectScale({
556
+ rootDir: process.cwd(),
557
+ statusJson: prefetched?.json?.statusJson,
558
+ sessionState: prefetched?.json?.sessionState,
559
+ });
560
+ const recs = scaleDetector.getScaleRecommendations(scaleResult.scale);
561
+ const label = scaleDetector.getScaleLabel(scaleResult.scale);
562
+ content += `\n${C.cyan}${C.bold}═══ Project Scale: ${label} ═══${C.reset}\n`;
563
+ content += `${C.dim}Detected: ${scaleResult.metrics.files} files, ${scaleResult.metrics.stories} stories, ${scaleResult.metrics.commits} commits (6mo)${C.reset}\n`;
564
+ content += `Planning depth: ${C.mintGreen}${recs.planningDepth}${C.reset} | Experts: ${C.mintGreen}${recs.expertCount}${C.reset}\n`;
565
+ content += `${C.dim}${recs.description}${C.reset}\n`;
566
+ } catch {
567
+ // Silently ignore scale detection errors
568
+ }
569
+ }
570
+ } // end verbosityMode === 'full' (scale detection)
532
571
 
533
572
  // GIT STATUS
534
573
  content += `\n${C.skyBlue}${C.bold}═══ Git Status ═══${C.reset}\n`;
@@ -550,65 +589,108 @@ function generateFullContent(prefetched = null, options = {}) {
550
589
  }
551
590
 
552
591
  // STATUS.JSON
553
- content += `\n${C.skyBlue}${C.bold}═══ Status.json (Full Content) ═══${C.reset}\n`;
554
592
  const statusJson = prefetched?.json?.statusJson ?? safeReadJSON('docs/09-agents/status.json');
555
593
 
556
- if (statusJson) {
557
- content += `${C.dim}${'─'.repeat(50)}${C.reset}\n`;
558
- content +=
559
- JSON.stringify(statusJson, null, 2)
560
- .split('\n')
561
- .map(l => ` ${l}`)
562
- .join('\n') + '\n';
563
- content += `${C.dim}${'─'.repeat(50)}${C.reset}\n`;
594
+ if (verbosityMode === 'lite') {
595
+ // Lite mode: show counts and active stories only
596
+ content += `\n${C.skyBlue}${C.bold}═══ Status.json (Active Stories) ═══${C.reset}\n`;
597
+ if (statusJson?.stories) {
598
+ const active = Object.entries(statusJson.stories)
599
+ .filter(([, s]) => s.status === 'in-progress' || s.status === 'ready')
600
+ .slice(0, 10);
601
+ if (active.length > 0) {
602
+ content += `Active stories (${active.length}):\n`;
603
+ active.forEach(([id, s]) => {
604
+ content += ` ${C.lavender}${id}${C.reset}: ${s.title || 'untitled'} ${C.dim}[${s.status}]${C.reset}\n`;
605
+ });
606
+ } else {
607
+ content += `${C.dim}No active stories${C.reset}\n`;
608
+ }
609
+ } else {
610
+ content += `${C.dim}No status.json found${C.reset}\n`;
611
+ }
564
612
  } else {
565
- content += `${C.dim}No status.json found${C.reset}\n`;
613
+ // Full mode: dump entire JSON
614
+ content += `\n${C.skyBlue}${C.bold}═══ Status.json (Full Content) ═══${C.reset}\n`;
615
+ if (statusJson) {
616
+ content += `${C.dim}${'─'.repeat(50)}${C.reset}\n`;
617
+ content +=
618
+ JSON.stringify(statusJson, null, 2)
619
+ .split('\n')
620
+ .map(l => ` ${l}`)
621
+ .join('\n') + '\n';
622
+ content += `${C.dim}${'─'.repeat(50)}${C.reset}\n`;
623
+ } else {
624
+ content += `${C.dim}No status.json found${C.reset}\n`;
625
+ }
566
626
  }
567
627
 
568
628
  // SESSION STATE
569
- content += `\n${C.skyBlue}${C.bold}═══ Session State ═══${C.reset}\n`;
570
629
  const sessionState =
571
630
  prefetched?.json?.sessionState ?? safeReadJSON('docs/09-agents/session-state.json');
572
- if (sessionState) {
573
- const current = sessionState.current_session;
574
- if (current && current.started_at) {
575
- const started = new Date(current.started_at);
631
+
632
+ if (verbosityMode === 'lite') {
633
+ // Brief session state in lite mode
634
+ content += `\n${C.skyBlue}${C.bold}═══ Session State ═══${C.reset}\n`;
635
+ if (sessionState?.current_session?.started_at) {
636
+ const started = new Date(sessionState.current_session.started_at);
576
637
  const duration = Math.round((Date.now() - started.getTime()) / 60000);
577
- content += `Active session: ${C.lightGreen}${duration} min${C.reset}\n`;
578
- if (current.current_story) {
579
- content += `Working on: ${C.lightYellow}${current.current_story}${C.reset}\n`;
638
+ content += `Session: ${C.lightGreen}${duration} min${C.reset}`;
639
+ if (sessionState.current_session.current_story) {
640
+ content += ` | Working on: ${C.lightYellow}${sessionState.current_session.current_story}${C.reset}`;
641
+ }
642
+ content += '\n';
643
+ if (Array.isArray(sessionState.active_commands) && sessionState.active_commands.length > 0) {
644
+ const cmdNames = sessionState.active_commands.map(c => c.name).join(', ');
645
+ content += `Active commands: ${C.skyBlue}${cmdNames}${C.reset}\n`;
580
646
  }
581
647
  } else {
582
648
  content += `${C.dim}No active session${C.reset}\n`;
583
649
  }
584
- if (Array.isArray(sessionState.active_commands) && sessionState.active_commands.length > 0) {
585
- const cmdNames = sessionState.active_commands.map(c => c.name).join(', ');
586
- content += `Active commands: ${C.skyBlue}${cmdNames}${C.reset}\n`;
587
- } else if (sessionState.active_command && sessionState.active_command.name) {
588
- content += `Active command: ${C.skyBlue}${sessionState.active_command.name}${C.reset}\n`;
589
- }
650
+ } else {
651
+ // Full session state
652
+ content += `\n${C.skyBlue}${C.bold}═══ Session State ═══${C.reset}\n`;
653
+ if (sessionState) {
654
+ const current = sessionState.current_session;
655
+ if (current && current.started_at) {
656
+ const started = new Date(current.started_at);
657
+ const duration = Math.round((Date.now() - started.getTime()) / 60000);
658
+ content += `Active session: ${C.lightGreen}${duration} min${C.reset}\n`;
659
+ if (current.current_story) {
660
+ content += `Working on: ${C.lightYellow}${current.current_story}${C.reset}\n`;
661
+ }
662
+ } else {
663
+ content += `${C.dim}No active session${C.reset}\n`;
664
+ }
665
+ if (Array.isArray(sessionState.active_commands) && sessionState.active_commands.length > 0) {
666
+ const cmdNames = sessionState.active_commands.map(c => c.name).join(', ');
667
+ content += `Active commands: ${C.skyBlue}${cmdNames}${C.reset}\n`;
668
+ } else if (sessionState.active_command && sessionState.active_command.name) {
669
+ content += `Active command: ${C.skyBlue}${sessionState.active_command.name}${C.reset}\n`;
670
+ }
590
671
 
591
- const batchLoop = sessionState.batch_loop;
592
- if (batchLoop && batchLoop.enabled) {
593
- content += `\n${C.skyBlue}${C.bold}── Batch Loop Active ──${C.reset}\n`;
594
- content += `Pattern: ${C.cyan}${batchLoop.pattern}${C.reset}\n`;
595
- content += `Action: ${C.cyan}${batchLoop.action}${C.reset}\n`;
596
- content += `Current: ${C.lightYellow}${batchLoop.current_item || 'none'}${C.reset}\n`;
597
- const summary = batchLoop.summary || {};
598
- content += `Progress: ${C.lightGreen}${summary.completed || 0}${C.reset}/${summary.total || 0} `;
599
- content += `(${C.lightYellow}${summary.in_progress || 0}${C.reset} in progress)\n`;
600
- content += `Iteration: ${batchLoop.iteration || 0}/${batchLoop.max_iterations || 50}\n`;
672
+ const batchLoop = sessionState.batch_loop;
673
+ if (batchLoop && batchLoop.enabled) {
674
+ content += `\n${C.skyBlue}${C.bold}── Batch Loop Active ──${C.reset}\n`;
675
+ content += `Pattern: ${C.cyan}${batchLoop.pattern}${C.reset}\n`;
676
+ content += `Action: ${C.cyan}${batchLoop.action}${C.reset}\n`;
677
+ content += `Current: ${C.lightYellow}${batchLoop.current_item || 'none'}${C.reset}\n`;
678
+ const summary = batchLoop.summary || {};
679
+ content += `Progress: ${C.lightGreen}${summary.completed || 0}${C.reset}/${summary.total || 0} `;
680
+ content += `(${C.lightYellow}${summary.in_progress || 0}${C.reset} in progress)\n`;
681
+ content += `Iteration: ${batchLoop.iteration || 0}/${batchLoop.max_iterations || 50}\n`;
682
+ }
683
+ } else {
684
+ content += `${C.dim}No session-state.json found${C.reset}\n`;
601
685
  }
602
- } else {
603
- content += `${C.dim}No session-state.json found${C.reset}\n`;
604
686
  }
605
687
 
606
688
  // Remaining content would continue here...
607
689
  // For brevity, returning the core content
608
690
  // The full implementation would include all remaining sections
609
691
 
610
- // Add remaining sections
611
- content += generateRemainingContent(prefetched, options);
692
+ // Add remaining sections (pass verbosityMode through options)
693
+ content += generateRemainingContent(prefetched, { ...options, verbosityMode });
612
694
 
613
695
  return content;
614
696
  }
@@ -620,11 +702,12 @@ function generateFullContent(prefetched = null, options = {}) {
620
702
  * @returns {string} Remaining content
621
703
  */
622
704
  function generateRemainingContent(prefetched, options = {}) {
705
+ const { verbosityMode = 'full' } = options;
623
706
  let content = '';
624
707
 
625
- // STORY CLAIMS
708
+ // STORY CLAIMS - skip in lite mode
626
709
  const shouldLoadClaims = prefetched?.sectionsToLoad?.sessionClaims !== false;
627
- if (shouldLoadClaims) {
710
+ if (shouldLoadClaims && verbosityMode === 'full') {
628
711
  const storyClaimingPath = path.join(__dirname, 'story-claiming.js');
629
712
  const altStoryClaimingPath = '.agileflow/scripts/lib/story-claiming.js';
630
713
 
@@ -662,155 +745,177 @@ function generateRemainingContent(prefetched, options = {}) {
662
745
  }
663
746
  }
664
747
 
665
- // VISUAL E2E STATUS
666
- const metadata =
667
- prefetched?.json?.metadata ?? safeReadJSON('docs/00-meta/agileflow-metadata.json');
668
- const visualE2eConfig = metadata?.features?.visual_e2e;
669
- const playwrightExists =
670
- fs.existsSync('playwright.config.ts') || fs.existsSync('playwright.config.js');
671
- const screenshotsExists = fs.existsSync('screenshots');
672
- const testsE2eExists = fs.existsSync('tests/e2e');
673
-
674
- const visualE2eEnabled = visualE2eConfig?.enabled || (playwrightExists && screenshotsExists);
675
-
676
- if (visualE2eEnabled) {
677
- content += `\n${C.brand}${C.bold}═══ 📸 VISUAL E2E TESTING: ENABLED ═══${C.reset}\n`;
678
- content += `${C.dim}${'─'.repeat(60)}${C.reset}\n`;
679
- content += `${C.mintGreen}✓ Playwright:${C.reset} ${playwrightExists ? 'configured' : 'not found'}\n`;
680
- content += `${C.mintGreen}✓ Screenshots:${C.reset} ${screenshotsExists ? 'screenshots/' : 'not found'}\n`;
681
- content += `${C.mintGreen}✓ E2E Tests:${C.reset} ${testsE2eExists ? 'tests/e2e/' : 'not found'}\n\n`;
682
- content += `${C.bold}FOR UI WORK:${C.reset} Use ${C.skyBlue}VISUAL=true${C.reset} flag with babysit:\n`;
683
- content += `${C.dim} /agileflow:babysit EPIC=EP-XXXX MODE=loop VISUAL=true${C.reset}\n\n`;
684
- content += `${C.dim}${'─'.repeat(60)}${C.reset}\n\n`;
685
- }
686
-
687
- // DOCS STRUCTURE
688
- content += `\n${C.skyBlue}${C.bold}═══ Documentation ═══${C.reset}\n`;
689
- const docsDir = 'docs';
690
- const docFolders = (prefetched?.dirs?.docs ?? safeLs(docsDir)).filter(f => {
691
- try {
692
- return fs.statSync(path.join(docsDir, f)).isDirectory();
693
- } catch {
694
- return false;
748
+ // UI TESTING STATUS (unified Visual E2E + Browser QA) - skip in lite mode
749
+ if (verbosityMode === 'full') {
750
+ const metadata =
751
+ prefetched?.json?.metadata ?? safeReadJSON('docs/00-meta/agileflow-metadata.json');
752
+ const browserQaConfig = metadata?.features?.browserqa;
753
+ const playwrightExists =
754
+ fs.existsSync('playwright.config.ts') || fs.existsSync('playwright.config.js');
755
+ const screenshotsExists = fs.existsSync('screenshots');
756
+ const browserQaSpecsExist = fs.existsSync('.agileflow/ui-review/specs');
757
+
758
+ const uiTestingEnabled =
759
+ playwrightExists || screenshotsExists || browserQaConfig?.enabled || browserQaSpecsExist;
760
+
761
+ if (uiTestingEnabled) {
762
+ const browserQaRunsExist = fs.existsSync('.agileflow/ui-review/runs');
763
+ content += `\n${C.brand}${C.bold}═══ UI Testing (Bowser): ENABLED ═══${C.reset}\n`;
764
+ content += `${C.dim}${'─'.repeat(60)}${C.reset}\n`;
765
+ content += `${C.mintGreen} Playwright:${C.reset} ${playwrightExists ? 'configured' : 'not found'}\n`;
766
+ content += `${C.mintGreen} Screenshots:${C.reset} ${screenshotsExists ? 'screenshots/ (for visual verification)' : 'not found'}\n`;
767
+ content += `${C.mintGreen}✓ Browser QA Specs:${C.reset} ${browserQaSpecsExist ? '.agileflow/ui-review/specs/' : 'not found'}\n`;
768
+ content += `${C.mintGreen}✓ Evidence Runs:${C.reset} ${browserQaRunsExist ? '.agileflow/ui-review/runs/' : 'not found'}\n\n`;
769
+ content += `${C.bold}AGENTIC TESTING:${C.reset} ${C.skyBlue}/agileflow:browser-qa SCENARIO=<spec.yaml>${C.reset}\n`;
770
+ content += `${C.dim} Pass rate: 80% threshold | Results are informational (not merge-blocking)${C.reset}\n`;
771
+ content += `${C.bold}VISUAL VERIFICATION:${C.reset} ${C.skyBlue}VISUAL=true${C.reset} flag with babysit\n`;
772
+ content += `${C.dim} /agileflow:babysit EPIC=EP-XXXX MODE=loop VISUAL=true${C.reset}\n\n`;
773
+ content += `${C.dim}${'─'.repeat(60)}${C.reset}\n\n`;
695
774
  }
696
- });
697
-
698
- if (docFolders.length > 0) {
699
- docFolders.forEach(folder => {
700
- const folderPath = path.join(docsDir, folder);
701
- const files = safeLs(folderPath);
702
- const mdFiles = files.filter(f => f.endsWith('.md'));
703
- const jsonFiles = files.filter(f => f.endsWith('.json') || f.endsWith('.jsonl'));
704
- const info = [];
705
- if (mdFiles.length > 0) info.push(`${mdFiles.length} md`);
706
- if (jsonFiles.length > 0) info.push(`${jsonFiles.length} json`);
707
- content += ` ${C.dim}${folder}/${C.reset} ${info.length > 0 ? `(${info.join(', ')})` : ''}\n`;
708
- });
709
- }
710
-
711
- // RESEARCH NOTES
712
- const shouldLoadResearch = prefetched?.sectionsToLoad?.researchContent !== false;
713
- content += `\n${C.skyBlue}${C.bold}═══ Research Notes ═══${C.reset}\n`;
714
- const researchDir = 'docs/10-research';
715
- const researchFiles =
716
- prefetched?.researchFiles ??
717
- safeLs(researchDir)
718
- .filter(f => f.endsWith('.md') && f !== 'README.md')
719
- .sort()
720
- .reverse();
721
- if (researchFiles.length > 0) {
722
- content += `${C.dim}───${C.reset} Available Research Notes\n`;
723
- researchFiles.forEach(file => (content += ` ${C.dim}${file}${C.reset}\n`));
775
+ } // end verbosityMode === 'full' (UI testing status)
724
776
 
725
- const mostRecentFile = researchFiles[0];
726
- const mostRecentPath = path.join(researchDir, mostRecentFile);
727
- const mostRecentContent =
728
- prefetched?.mostRecentResearch ?? (shouldLoadResearch ? safeRead(mostRecentPath) : null);
777
+ // DOCS STRUCTURE - skip in lite mode
778
+ if (verbosityMode === 'full') {
779
+ content += `\n${C.skyBlue}${C.bold}═══ Documentation ═══${C.reset}\n`;
780
+ const docsDir = 'docs';
781
+ const docFolders = (prefetched?.dirs?.docs ?? safeLs(docsDir)).filter(f => {
782
+ try {
783
+ return fs.statSync(path.join(docsDir, f)).isDirectory();
784
+ } catch {
785
+ return false;
786
+ }
787
+ });
729
788
 
730
- if (mostRecentContent) {
731
- content += `\n${C.mintGreen}📄 Most Recent: ${mostRecentFile}${C.reset}\n`;
732
- content += `${C.dim}${'─'.repeat(60)}${C.reset}\n`;
733
- content += mostRecentContent + '\n';
734
- content += `${C.dim}${''.repeat(60)}${C.reset}\n`;
735
- } else if (!shouldLoadResearch) {
736
- content += `\n${C.dim}📄 Content deferred (lazy loading). Use /agileflow:research to access.${C.reset}\n`;
789
+ if (docFolders.length > 0) {
790
+ docFolders.forEach(folder => {
791
+ const folderPath = path.join(docsDir, folder);
792
+ const files = safeLs(folderPath);
793
+ const mdFiles = files.filter(f => f.endsWith('.md'));
794
+ const jsonFiles = files.filter(f => f.endsWith('.json') || f.endsWith('.jsonl'));
795
+ const info = [];
796
+ if (mdFiles.length > 0) info.push(`${mdFiles.length} md`);
797
+ if (jsonFiles.length > 0) info.push(`${jsonFiles.length} json`);
798
+ content += ` ${C.dim}${folder}/${C.reset} ${info.length > 0 ? `(${info.join(', ')})` : ''}\n`;
799
+ });
737
800
  }
738
- } else {
739
- content += `${C.dim}No research notes${C.reset}\n`;
740
- }
741
-
742
- // BUS MESSAGES
743
- content += `\n${C.skyBlue}${C.bold}═══ Recent Agent Messages ═══${C.reset}\n`;
744
- const busPath = 'docs/09-agents/bus/log.jsonl';
745
- const busContent = prefetched?.text?.busLog ?? safeRead(busPath);
746
- if (busContent) {
747
- const lines = busContent.trim().split('\n').filter(Boolean);
748
- const recent = lines.slice(-5);
749
- if (recent.length > 0) {
750
- recent.forEach(line => {
751
- try {
752
- const msg = JSON.parse(line);
753
- const time = msg.timestamp ? new Date(msg.timestamp).toLocaleTimeString() : '?';
754
- content += ` ${C.dim}[${time}]${C.reset} ${msg.from || '?'}: ${msg.type || msg.message || '?'}\n`;
755
- } catch {
756
- content += ` ${C.dim}${line.substring(0, 80)}...${C.reset}\n`;
801
+ } // end verbosityMode === 'full' (docs structure)
802
+
803
+ // RESEARCH NOTES - lite shows list only, minimal skips entirely
804
+ if (verbosityMode === 'full' || verbosityMode === 'lite') {
805
+ const shouldLoadResearch = prefetched?.sectionsToLoad?.researchContent !== false;
806
+ content += `\n${C.skyBlue}${C.bold}═══ Research Notes ═══${C.reset}\n`;
807
+ const researchDir = 'docs/10-research';
808
+ const researchFiles =
809
+ prefetched?.researchFiles ??
810
+ safeLs(researchDir)
811
+ .filter(f => f.endsWith('.md') && f !== 'README.md')
812
+ .sort()
813
+ .reverse();
814
+ if (researchFiles.length > 0) {
815
+ content += `${C.dim}───${C.reset} Available Research Notes\n`;
816
+ researchFiles.forEach(file => (content += ` ${C.dim}${file}${C.reset}\n`));
817
+
818
+ // In lite mode, only show the list - no content dump
819
+ if (verbosityMode === 'full') {
820
+ const mostRecentFile = researchFiles[0];
821
+ const mostRecentPath = path.join(researchDir, mostRecentFile);
822
+ const mostRecentContent =
823
+ prefetched?.mostRecentResearch ?? (shouldLoadResearch ? safeRead(mostRecentPath) : null);
824
+
825
+ if (mostRecentContent) {
826
+ content += `\n${C.mintGreen}📄 Most Recent: ${mostRecentFile}${C.reset}\n`;
827
+ content += `${C.dim}${'─'.repeat(60)}${C.reset}\n`;
828
+ content += mostRecentContent + '\n';
829
+ content += `${C.dim}${'─'.repeat(60)}${C.reset}\n`;
830
+ } else if (!shouldLoadResearch) {
831
+ content += `\n${C.dim}📄 Content deferred (lazy loading). Use /agileflow:research to access.${C.reset}\n`;
757
832
  }
758
- });
833
+ }
759
834
  } else {
760
- content += `${C.dim}No messages${C.reset}\n`;
835
+ content += `${C.dim}No research notes${C.reset}\n`;
761
836
  }
762
- } else {
763
- content += `${C.dim}No bus log found${C.reset}\n`;
764
- }
765
-
766
- // KEY FILES
767
- content += `\n${C.cyan}${C.bold}═══ Key Context Files (Full Content) ═══${C.reset}\n`;
768
-
769
- const prefetchedKeyMap = {
770
- 'CLAUDE.md': 'claudeMd',
771
- 'README.md': 'readmeMd',
772
- 'docs/04-architecture/README.md': 'archReadme',
773
- 'docs/02-practices/README.md': 'practicesReadme',
774
- 'docs/08-project/roadmap.md': 'roadmap',
775
- };
776
-
777
- const keyFilesToRead = [
778
- { path: 'CLAUDE.md', label: 'CLAUDE.md (Project Instructions)' },
779
- { path: 'README.md', label: 'README.md (Project Overview)' },
780
- { path: 'docs/04-architecture/README.md', label: 'Architecture Index' },
781
- { path: 'docs/02-practices/README.md', label: 'Practices Index' },
782
- { path: 'docs/08-project/roadmap.md', label: 'Roadmap' },
783
- ];
784
-
785
- keyFilesToRead.forEach(({ path: filePath, label }) => {
786
- const prefetchKey = prefetchedKeyMap[filePath];
787
- const fileContent = prefetched?.text?.[prefetchKey] ?? safeRead(filePath);
788
- if (fileContent) {
789
- content += `\n${C.green}✓ ${label}${C.reset} ${C.dim}(${filePath})${C.reset}\n`;
790
- content += `${C.dim}${'─'.repeat(60)}${C.reset}\n`;
791
- content += fileContent + '\n';
792
- content += `${C.dim}${'─'.repeat(60)}${C.reset}\n`;
837
+ } // end research notes (full/lite)
838
+
839
+ // BUS MESSAGES - skip in lite mode
840
+ if (verbosityMode === 'full') {
841
+ content += `\n${C.skyBlue}${C.bold}═══ Recent Agent Messages ═══${C.reset}\n`;
842
+ const busPath = 'docs/09-agents/bus/log.jsonl';
843
+ const busContent = prefetched?.text?.busLog ?? safeRead(busPath);
844
+ if (busContent) {
845
+ const lines = busContent.trim().split('\n').filter(Boolean);
846
+ const recent = lines.slice(-5);
847
+ if (recent.length > 0) {
848
+ recent.forEach(line => {
849
+ try {
850
+ const msg = JSON.parse(line);
851
+ const time = msg.timestamp ? new Date(msg.timestamp).toLocaleTimeString() : '?';
852
+ content += ` ${C.dim}[${time}]${C.reset} ${msg.from || '?'}: ${msg.type || msg.message || '?'}\n`;
853
+ } catch {
854
+ content += ` ${C.dim}${line.substring(0, 80)}...${C.reset}\n`;
855
+ }
856
+ });
857
+ } else {
858
+ content += `${C.dim}No messages${C.reset}\n`;
859
+ }
793
860
  } else {
794
- content += `${C.dim} ${label} (not found)${C.reset}\n`;
861
+ content += `${C.dim}No bus log found${C.reset}\n`;
795
862
  }
796
- });
863
+ } // end verbosityMode === 'full' (bus messages)
864
+
865
+ // KEY FILES - full dumps in full mode, existence check in lite
866
+ if (verbosityMode === 'full') {
867
+ content += `\n${C.cyan}${C.bold}═══ Key Context Files (Full Content) ═══${C.reset}\n`;
868
+
869
+ const prefetchedKeyMap = {
870
+ 'CLAUDE.md': 'claudeMd',
871
+ 'README.md': 'readmeMd',
872
+ 'docs/04-architecture/README.md': 'archReadme',
873
+ 'docs/02-practices/README.md': 'practicesReadme',
874
+ 'docs/08-project/roadmap.md': 'roadmap',
875
+ };
797
876
 
798
- const settingsExists = fs.existsSync('.claude/settings.json');
799
- content += `\n ${settingsExists ? `${C.green}✓${C.reset}` : `${C.dim}○${C.reset}`} .claude/settings.json\n`;
877
+ const keyFilesToRead = [
878
+ { path: 'CLAUDE.md', label: 'CLAUDE.md (Project Instructions)' },
879
+ { path: 'README.md', label: 'README.md (Project Overview)' },
880
+ { path: 'docs/04-architecture/README.md', label: 'Architecture Index' },
881
+ { path: 'docs/02-practices/README.md', label: 'Practices Index' },
882
+ { path: 'docs/08-project/roadmap.md', label: 'Roadmap' },
883
+ ];
884
+
885
+ keyFilesToRead.forEach(({ path: filePath, label }) => {
886
+ const prefetchKey = prefetchedKeyMap[filePath];
887
+ const fileContent = prefetched?.text?.[prefetchKey] ?? safeRead(filePath);
888
+ if (fileContent) {
889
+ content += `\n${C.green}✓ ${label}${C.reset} ${C.dim}(${filePath})${C.reset}\n`;
890
+ content += `${C.dim}${'─'.repeat(60)}${C.reset}\n`;
891
+ content += fileContent + '\n';
892
+ content += `${C.dim}${'─'.repeat(60)}${C.reset}\n`;
893
+ } else {
894
+ content += `${C.dim}○ ${label} (not found)${C.reset}\n`;
895
+ }
896
+ });
800
897
 
801
- // EPICS FOLDER
898
+ const settingsExists = fs.existsSync('.claude/settings.json');
899
+ content += `\n ${settingsExists ? `${C.green}✓${C.reset}` : `${C.dim}○${C.reset}`} .claude/settings.json\n`;
900
+ } // end verbosityMode === 'full' (key files)
901
+
902
+ // EPICS FOLDER - count only in lite mode
802
903
  content += `\n${C.cyan}${C.bold}═══ Epic Files ═══${C.reset}\n`;
803
904
  const epicFiles =
804
905
  prefetched?.dirs?.epics?.filter(f => f.endsWith('.md') && f !== 'README.md') ??
805
906
  safeLs('docs/05-epics').filter(f => f.endsWith('.md') && f !== 'README.md');
806
907
  if (epicFiles.length > 0) {
807
- epicFiles.forEach(file => (content += ` ${C.dim}${file}${C.reset}\n`));
908
+ if (verbosityMode === 'lite') {
909
+ content += `${C.dim}${epicFiles.length} epic file(s)${C.reset}\n`;
910
+ } else {
911
+ epicFiles.forEach(file => (content += ` ${C.dim}${file}${C.reset}\n`));
912
+ }
808
913
  } else {
809
914
  content += `${C.dim}No epic files${C.reset}\n`;
810
915
  }
811
916
 
812
- // IDEATION SUGGESTIONS (filtered to exclude implemented)
813
- if (ideationIndex) {
917
+ // IDEATION SUGGESTIONS (filtered to exclude implemented) - skip in lite mode
918
+ if (ideationIndex && verbosityMode === 'full') {
814
919
  try {
815
920
  const rootDir = process.cwd();
816
921
  const indexResult = ideationIndex.loadIdeationIndex(rootDir);
@@ -864,11 +969,12 @@ function generateRemainingContent(prefetched, options = {}) {
864
969
  }
865
970
  }
866
971
 
867
- // SMART RECOMMENDATIONS (Contextual Feature Router)
972
+ // SMART RECOMMENDATIONS (Contextual Feature Router) - immediate only in lite mode
868
973
  const smartDetectResults = options.smartDetectResults;
869
974
  if (smartDetectResults && !smartDetectResults.disabled) {
870
975
  const { immediate, available, auto_enabled } = smartDetectResults.recommendations;
871
- const hasRecommendations = immediate.length > 0 || available.length > 0;
976
+ const hasRecommendations =
977
+ immediate.length > 0 || (verbosityMode === 'full' && available.length > 0);
872
978
 
873
979
  if (hasRecommendations) {
874
980
  content += `\n${C.brand}${C.bold}═══ Smart Recommendations ═══${C.reset}\n`;
@@ -881,7 +987,8 @@ function generateRemainingContent(prefetched, options = {}) {
881
987
  });
882
988
  }
883
989
 
884
- if (available.length > 0) {
990
+ // Available recommendations - full mode only
991
+ if (verbosityMode === 'full' && available.length > 0) {
885
992
  content += `\n${C.skyBlue}Available:${C.reset}\n`;
886
993
  available.slice(0, 5).forEach(r => {
887
994
  content += ` ${C.skyBlue}>${C.reset} ${r.feature}: ${r.trigger} ${C.dim}(${r.command})${C.reset}\n`;
@@ -900,6 +1007,56 @@ function generateRemainingContent(prefetched, options = {}) {
900
1007
  if (enabledModes.length > 0) {
901
1008
  content += `\n${C.mintGreen}Auto-enabled:${C.reset} ${enabledModes.join(', ')}\n`;
902
1009
  }
1010
+
1011
+ // Feature catalog - show all major features with status (full mode only)
1012
+ const catalog = smartDetectResults.feature_catalog;
1013
+ if (catalog && catalog.length > 0 && verbosityMode === 'full') {
1014
+ const categoryLabels = {
1015
+ modes: 'Modes',
1016
+ collaboration: 'Collaboration',
1017
+ workflow: 'Workflow',
1018
+ analysis: 'Analysis',
1019
+ testing: 'Testing',
1020
+ automation: 'Automation',
1021
+ };
1022
+ const statusIcons = {
1023
+ triggered: `${C.mintGreen}*${C.reset}`,
1024
+ available: `${C.skyBlue}>${C.reset}`,
1025
+ unavailable: `${C.dim}-${C.reset}`,
1026
+ };
1027
+
1028
+ // Group by category, skip disabled
1029
+ const grouped = {};
1030
+ for (const entry of catalog) {
1031
+ if (entry.status === 'disabled') continue;
1032
+ if (!grouped[entry.category]) grouped[entry.category] = [];
1033
+ grouped[entry.category].push(entry);
1034
+ }
1035
+
1036
+ const categoryOrder = [
1037
+ 'modes',
1038
+ 'collaboration',
1039
+ 'workflow',
1040
+ 'analysis',
1041
+ 'testing',
1042
+ 'automation',
1043
+ ];
1044
+ const hasEntries = Object.keys(grouped).length > 0;
1045
+
1046
+ if (hasEntries) {
1047
+ content += `\n${C.brand}${C.bold}═══ Feature Catalog ═══${C.reset}\n`;
1048
+
1049
+ for (const cat of categoryOrder) {
1050
+ const entries = grouped[cat];
1051
+ if (!entries || entries.length === 0) continue;
1052
+ content += `\n${C.bold}${categoryLabels[cat] || cat}:${C.reset}\n`;
1053
+ for (const entry of entries) {
1054
+ const icon = statusIcons[entry.status] || `${C.dim}?${C.reset}`;
1055
+ content += ` ${icon} ${C.bold}${entry.name}${C.reset} - ${entry.description} ${C.dim}(${entry.how_to_use})${C.reset}\n`;
1056
+ }
1057
+ }
1058
+ }
1059
+ }
903
1060
  }
904
1061
 
905
1062
  // FOOTER
@@ -909,6 +1066,83 @@ function generateRemainingContent(prefetched, options = {}) {
909
1066
  return content;
910
1067
  }
911
1068
 
1069
+ /**
1070
+ * Generate minimal content - compact summary only.
1071
+ * Used when verbosityMode is 'minimal' to save ~80% of context tokens.
1072
+ *
1073
+ * @param {Object} prefetched - Pre-fetched data from prefetchAllData()
1074
+ * @param {Object} options - Additional options
1075
+ * @returns {string} Minimal content
1076
+ */
1077
+ function generateMinimalContent(prefetched, options = {}) {
1078
+ const { commandName = null } = options;
1079
+ let content = '';
1080
+
1081
+ // Title
1082
+ const title = commandName
1083
+ ? `AgileFlow Context [${commandName}] [minimal]`
1084
+ : 'AgileFlow Context [minimal]';
1085
+ content += `${C.lavender}${C.bold}${title}${C.reset}\n`;
1086
+
1087
+ // Git (compact)
1088
+ const branch = prefetched?.git?.branch ?? safeExec('git branch --show-current') ?? 'unknown';
1089
+ const statusStr = prefetched?.git?.status ?? safeExec('git status --short') ?? '';
1090
+ const uncommitted = statusStr.split('\n').filter(Boolean).length;
1091
+ const lastCommit =
1092
+ prefetched?.git?.commitFull ?? safeExec('git log -1 --format="%h %s"') ?? 'no commits';
1093
+ content += `Git: ${C.mintGreen}${branch}${C.reset} | ${uncommitted > 0 ? `${C.peach}${uncommitted} uncommitted${C.reset}` : `${C.mintGreen}clean${C.reset}`} | ${C.dim}${lastCommit}${C.reset}\n`;
1094
+
1095
+ // Story counts
1096
+ const statusJson = prefetched?.json?.statusJson ?? safeReadJSON('docs/09-agents/status.json');
1097
+ if (statusJson?.stories) {
1098
+ const counts = {};
1099
+ Object.values(statusJson.stories).forEach(s => {
1100
+ const st = s.status || 'unknown';
1101
+ counts[st] = (counts[st] || 0) + 1;
1102
+ });
1103
+ content += `Stories: ${C.skyBlue}${counts.ready || 0} ready${C.reset}, ${C.peach}${counts['in-progress'] || 0} in-progress${C.reset}, ${C.mintGreen}${counts.done || 0} done${C.reset}`;
1104
+ if (counts.blocked) content += `, ${C.coral}${counts.blocked} blocked${C.reset}`;
1105
+ content += '\n';
1106
+ }
1107
+
1108
+ // Active session (one-liner)
1109
+ const sessionState =
1110
+ prefetched?.json?.sessionState ?? safeReadJSON('docs/09-agents/session-state.json');
1111
+ if (sessionState?.current_session?.started_at) {
1112
+ const started = new Date(sessionState.current_session.started_at);
1113
+ const duration = Math.round((Date.now() - started.getTime()) / 60000);
1114
+ content += `Session: ${C.lightGreen}${duration} min${C.reset}`;
1115
+ if (sessionState.current_session.current_story) {
1116
+ content += ` | Working on: ${C.lightYellow}${sessionState.current_session.current_story}${C.reset}`;
1117
+ }
1118
+ content += '\n';
1119
+ }
1120
+
1121
+ // Active commands
1122
+ if (Array.isArray(sessionState?.active_commands) && sessionState.active_commands.length > 0) {
1123
+ const cmdNames = sessionState.active_commands.map(c => c.name).join(', ');
1124
+ content += `Active commands: ${C.skyBlue}${cmdNames}${C.reset}\n`;
1125
+ }
1126
+
1127
+ // Context budget warning (always show)
1128
+ const contextUsage = getContextPercentage();
1129
+ if (contextUsage && contextUsage.percent >= 50) {
1130
+ content += generateContextWarning(contextUsage.percent);
1131
+ }
1132
+
1133
+ // AskUserQuestion banner (keep in minimal - it's an instruction, not data)
1134
+ const earlyMetadata =
1135
+ prefetched?.json?.metadata ?? safeReadJSON('docs/00-meta/agileflow-metadata.json');
1136
+ if (earlyMetadata?.features?.askUserQuestion?.enabled) {
1137
+ content += `\n${C.coral}${C.bold}🔔 SMART AskUserQuestion After EVERY Response${C.reset}\n`;
1138
+ content += `${C.mintGreen}→ ALWAYS${C.reset} call AskUserQuestion with contextual options. ${C.coral}→ NEVER${C.reset} generic.\n`;
1139
+ }
1140
+
1141
+ content += `\n${C.dim}[minimal mode - run /agileflow:configure to change context verbosity]${C.reset}\n`;
1142
+
1143
+ return content;
1144
+ }
1145
+
912
1146
  module.exports = {
913
1147
  // String utilities
914
1148
  pad,
@@ -920,4 +1154,5 @@ module.exports = {
920
1154
  // Main generators
921
1155
  generateSummary,
922
1156
  generateFullContent,
1157
+ generateMinimalContent,
923
1158
  };