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.
- package/CHANGELOG.md +10 -0
- package/README.md +57 -85
- package/lib/dashboard-automations.js +130 -0
- package/lib/dashboard-git.js +254 -0
- package/lib/dashboard-inbox.js +64 -0
- package/lib/dashboard-protocol.js +1 -0
- package/lib/dashboard-server.js +114 -924
- package/lib/dashboard-session.js +136 -0
- package/lib/dashboard-status.js +72 -0
- package/lib/dashboard-terminal.js +354 -0
- package/lib/dashboard-websocket.js +88 -0
- package/lib/drivers/codex-driver.ts +4 -4
- package/lib/logger.js +106 -0
- package/package.json +4 -2
- package/scripts/agileflow-configure.js +2 -2
- package/scripts/agileflow-welcome.js +409 -434
- package/scripts/claude-tmux.sh +80 -2
- package/scripts/context-loader.js +4 -9
- package/scripts/lib/browser-qa-evidence.js +409 -0
- package/scripts/lib/browser-qa-status.js +192 -0
- package/scripts/lib/command-prereqs.js +280 -0
- package/scripts/lib/configure-detect.js +92 -2
- package/scripts/lib/configure-features.js +295 -1
- package/scripts/lib/context-formatter.js +468 -233
- package/scripts/lib/context-loader.js +27 -15
- package/scripts/lib/damage-control-utils.js +8 -1
- package/scripts/lib/feature-catalog.js +321 -0
- package/scripts/lib/portable-tasks-cli.js +274 -0
- package/scripts/lib/portable-tasks.js +479 -0
- package/scripts/lib/signal-detectors.js +1 -1
- package/scripts/lib/team-events.js +86 -1
- package/scripts/obtain-context.js +28 -4
- package/scripts/smart-detect.js +17 -0
- package/scripts/strip-ai-attribution.js +63 -0
- package/scripts/team-manager.js +7 -2
- package/scripts/welcome-deferred.js +437 -0
- package/src/core/agents/browser-qa.md +328 -0
- package/src/core/agents/perf-analyzer-assets.md +174 -0
- package/src/core/agents/perf-analyzer-bundle.md +165 -0
- package/src/core/agents/perf-analyzer-caching.md +160 -0
- package/src/core/agents/perf-analyzer-compute.md +165 -0
- package/src/core/agents/perf-analyzer-memory.md +182 -0
- package/src/core/agents/perf-analyzer-network.md +157 -0
- package/src/core/agents/perf-analyzer-queries.md +155 -0
- package/src/core/agents/perf-analyzer-rendering.md +156 -0
- package/src/core/agents/perf-consensus.md +280 -0
- package/src/core/agents/security-analyzer-api.md +199 -0
- package/src/core/agents/security-analyzer-auth.md +160 -0
- package/src/core/agents/security-analyzer-authz.md +168 -0
- package/src/core/agents/security-analyzer-deps.md +147 -0
- package/src/core/agents/security-analyzer-infra.md +176 -0
- package/src/core/agents/security-analyzer-injection.md +148 -0
- package/src/core/agents/security-analyzer-input.md +191 -0
- package/src/core/agents/security-analyzer-secrets.md +175 -0
- package/src/core/agents/security-consensus.md +276 -0
- package/src/core/agents/test-analyzer-assertions.md +181 -0
- package/src/core/agents/test-analyzer-coverage.md +183 -0
- package/src/core/agents/test-analyzer-fragility.md +185 -0
- package/src/core/agents/test-analyzer-integration.md +155 -0
- package/src/core/agents/test-analyzer-maintenance.md +173 -0
- package/src/core/agents/test-analyzer-mocking.md +178 -0
- package/src/core/agents/test-analyzer-patterns.md +189 -0
- package/src/core/agents/test-analyzer-structure.md +177 -0
- package/src/core/agents/test-consensus.md +294 -0
- package/src/core/commands/{legal/audit.md → audit/legal.md} +13 -13
- package/src/core/commands/{logic/audit.md → audit/logic.md} +12 -12
- package/src/core/commands/audit/performance.md +443 -0
- package/src/core/commands/audit/security.md +443 -0
- package/src/core/commands/audit/test.md +442 -0
- package/src/core/commands/babysit.md +505 -463
- package/src/core/commands/browser-qa.md +240 -0
- package/src/core/commands/configure.md +8 -8
- package/src/core/commands/research/ask.md +42 -9
- package/src/core/commands/research/import.md +14 -8
- package/src/core/commands/research/list.md +17 -16
- package/src/core/commands/research/synthesize.md +8 -8
- package/src/core/commands/research/view.md +28 -4
- package/src/core/commands/whats-new.md +2 -2
- package/src/core/experts/devops/expertise.yaml +13 -2
- package/src/core/experts/documentation/expertise.yaml +26 -4
- package/src/core/profiles/COMPARISON.md +170 -0
- package/src/core/profiles/README.md +178 -0
- package/src/core/profiles/claude-code.yaml +111 -0
- package/src/core/profiles/codex.yaml +103 -0
- package/src/core/profiles/cursor.yaml +134 -0
- package/src/core/profiles/examples.js +250 -0
- package/src/core/profiles/loader.js +235 -0
- package/src/core/profiles/windsurf.yaml +159 -0
- package/src/core/teams/logic-audit.json +6 -0
- package/src/core/teams/perf-audit.json +71 -0
- package/src/core/teams/security-audit.json +71 -0
- package/src/core/teams/test-audit.json +71 -0
- package/src/core/templates/browser-qa-spec.yaml +94 -0
- package/src/core/templates/command-prerequisites.yaml +169 -0
- package/src/core/templates/damage-control-patterns.yaml +9 -0
- package/tools/cli/installers/ide/_base-ide.js +33 -3
- package/tools/cli/installers/ide/claude-code.js +2 -69
- package/tools/cli/installers/ide/codex.js +9 -9
- package/tools/cli/installers/ide/cursor.js +165 -4
- package/tools/cli/installers/ide/windsurf.js +237 -6
- package/tools/cli/lib/content-transformer.js +234 -9
- package/tools/cli/lib/docs-setup.js +1 -1
- package/tools/cli/lib/ide-generator.js +357 -0
- package/tools/cli/lib/ide-registry.js +2 -2
- package/scripts/tmux-task-name.sh +0 -105
- 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 {
|
|
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
|
|
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
|
-
|
|
412
|
-
|
|
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
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
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
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
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
|
-
|
|
443
|
-
|
|
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
|
-
|
|
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
|
-
//
|
|
481
|
-
if (
|
|
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
|
-
|
|
510
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 (
|
|
557
|
-
|
|
558
|
-
content +=
|
|
559
|
-
|
|
560
|
-
|
|
561
|
-
.
|
|
562
|
-
.
|
|
563
|
-
|
|
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
|
-
|
|
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
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
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 += `
|
|
578
|
-
if (
|
|
579
|
-
content += `Working on: ${C.lightYellow}${
|
|
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
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
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
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
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
|
-
//
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
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
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
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 (
|
|
731
|
-
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
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
|
-
}
|
|
739
|
-
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
743
|
-
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
|
|
754
|
-
|
|
755
|
-
|
|
756
|
-
|
|
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
|
|
835
|
+
content += `${C.dim}No research notes${C.reset}\n`;
|
|
761
836
|
}
|
|
762
|
-
}
|
|
763
|
-
|
|
764
|
-
|
|
765
|
-
|
|
766
|
-
|
|
767
|
-
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
772
|
-
|
|
773
|
-
|
|
774
|
-
|
|
775
|
-
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
781
|
-
|
|
782
|
-
|
|
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}
|
|
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
|
-
|
|
799
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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
|
-
|
|
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
|
};
|