opencode-swarm-plugin 0.38.0 → 0.39.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 (69) hide show
  1. package/.env +2 -0
  2. package/.hive/eval-results.json +26 -0
  3. package/.hive/issues.jsonl +11 -0
  4. package/.hive/memories.jsonl +23 -1
  5. package/.opencode/eval-history.jsonl +12 -0
  6. package/CHANGELOG.md +130 -0
  7. package/README.md +29 -12
  8. package/bin/swarm.test.ts +475 -0
  9. package/bin/swarm.ts +383 -0
  10. package/dist/compaction-hook.d.ts +1 -1
  11. package/dist/compaction-hook.d.ts.map +1 -1
  12. package/dist/compaction-prompt-scoring.d.ts +124 -0
  13. package/dist/compaction-prompt-scoring.d.ts.map +1 -0
  14. package/dist/eval-capture.d.ts +81 -1
  15. package/dist/eval-capture.d.ts.map +1 -1
  16. package/dist/eval-gates.d.ts +84 -0
  17. package/dist/eval-gates.d.ts.map +1 -0
  18. package/dist/eval-history.d.ts +117 -0
  19. package/dist/eval-history.d.ts.map +1 -0
  20. package/dist/eval-learning.d.ts +216 -0
  21. package/dist/eval-learning.d.ts.map +1 -0
  22. package/dist/index.d.ts +44 -0
  23. package/dist/index.d.ts.map +1 -1
  24. package/dist/index.js +370 -13
  25. package/dist/plugin.js +203 -13
  26. package/dist/post-compaction-tracker.d.ts +133 -0
  27. package/dist/post-compaction-tracker.d.ts.map +1 -0
  28. package/dist/swarm-orchestrate.d.ts +23 -0
  29. package/dist/swarm-orchestrate.d.ts.map +1 -1
  30. package/dist/swarm-prompts.d.ts +25 -1
  31. package/dist/swarm-prompts.d.ts.map +1 -1
  32. package/dist/swarm.d.ts +4 -0
  33. package/dist/swarm.d.ts.map +1 -1
  34. package/evals/README.md +589 -105
  35. package/evals/compaction-prompt.eval.ts +149 -0
  36. package/evals/coordinator-behavior.eval.ts +8 -8
  37. package/evals/fixtures/compaction-prompt-cases.ts +305 -0
  38. package/evals/lib/compaction-loader.test.ts +248 -0
  39. package/evals/lib/compaction-loader.ts +320 -0
  40. package/evals/lib/data-loader.test.ts +345 -0
  41. package/evals/lib/data-loader.ts +107 -6
  42. package/evals/scorers/compaction-prompt-scorers.ts +145 -0
  43. package/evals/scorers/compaction-scorers.ts +13 -13
  44. package/evals/scorers/coordinator-discipline.evalite-test.ts +3 -2
  45. package/evals/scorers/coordinator-discipline.ts +13 -13
  46. package/examples/plugin-wrapper-template.ts +117 -0
  47. package/package.json +7 -5
  48. package/scripts/migrate-unknown-sessions.ts +349 -0
  49. package/src/compaction-capture.integration.test.ts +257 -0
  50. package/src/compaction-hook.test.ts +42 -0
  51. package/src/compaction-hook.ts +81 -0
  52. package/src/compaction-prompt-scorers.test.ts +299 -0
  53. package/src/compaction-prompt-scoring.ts +298 -0
  54. package/src/eval-capture.test.ts +422 -0
  55. package/src/eval-capture.ts +94 -2
  56. package/src/eval-gates.test.ts +306 -0
  57. package/src/eval-gates.ts +218 -0
  58. package/src/eval-history.test.ts +508 -0
  59. package/src/eval-history.ts +214 -0
  60. package/src/eval-learning.test.ts +378 -0
  61. package/src/eval-learning.ts +360 -0
  62. package/src/index.ts +61 -1
  63. package/src/post-compaction-tracker.test.ts +251 -0
  64. package/src/post-compaction-tracker.ts +237 -0
  65. package/src/swarm-decompose.ts +2 -2
  66. package/src/swarm-orchestrate.ts +2 -2
  67. package/src/swarm-prompts.ts +2 -2
  68. package/src/swarm-review.ts +3 -3
  69. /package/evals/{evalite.config.ts → evalite.config.ts.bak} +0 -0
package/bin/swarm.ts CHANGED
@@ -80,6 +80,8 @@ const yellow = (s: string) => `\x1b[33m${s}\x1b[0m`;
80
80
  const cyan = (s: string) => `\x1b[36m${s}\x1b[0m`;
81
81
  const green = (s: string) => `\x1b[32m${s}\x1b[0m`;
82
82
  const magenta = (s: string) => `\x1b[35m${s}\x1b[0m`;
83
+ const red = (s: string) => `\x1b[31m${s}\x1b[0m`;
84
+ const bold = (s: string) => `\x1b[1m${s}\x1b[0m`;
83
85
 
84
86
  const PACKAGE_NAME = "opencode-swarm-plugin";
85
87
 
@@ -2518,6 +2520,7 @@ ${cyan("Commands:")}
2518
2520
  swarm migrate Migrate PGlite database to libSQL
2519
2521
  swarm cells List or get cells from database (replaces 'swarm tool hive_query')
2520
2522
  swarm log View swarm logs with filtering
2523
+ swarm eval Eval-driven development commands
2521
2524
  swarm update Update to latest version
2522
2525
  swarm version Show version and banner
2523
2526
  swarm tool Execute a tool (for plugin wrapper)
@@ -2546,6 +2549,11 @@ ${cyan("Log Viewing:")}
2546
2549
  swarm log --watch, -w Watch mode - continuously monitor for new logs
2547
2550
  swarm log --interval <ms> Poll interval in ms (default: 1000, min: 100)
2548
2551
 
2552
+ ${cyan("Eval Commands:")}
2553
+ swarm eval status [eval-name] Show current phase, thresholds, recent scores
2554
+ swarm eval history Show eval run history with trends
2555
+ swarm eval run Execute evals and report results (stub)
2556
+
2549
2557
  ${cyan("Usage in OpenCode:")}
2550
2558
  /swarm "Add user authentication with OAuth"
2551
2559
  @swarm/planner "Decompose this into parallel tasks"
@@ -3537,6 +3545,378 @@ async function db() {
3537
3545
  console.log();
3538
3546
  }
3539
3547
 
3548
+ // ============================================================================
3549
+ // Eval Command Helpers
3550
+ // ============================================================================
3551
+
3552
+ /**
3553
+ * Generate sparkline from array of scores (0-1 range)
3554
+ */
3555
+ function generateSparkline(scores: number[]): string {
3556
+ if (scores.length === 0) return "";
3557
+
3558
+ const chars = ["▁", "▂", "▃", "▄", "▅", "▆", "▇", "█"];
3559
+ const min = Math.min(...scores);
3560
+ const max = Math.max(...scores);
3561
+ const range = max - min;
3562
+
3563
+ if (range === 0) {
3564
+ // All scores the same
3565
+ return chars[4].repeat(scores.length);
3566
+ }
3567
+
3568
+ return scores
3569
+ .map((score) => {
3570
+ const normalized = (score - min) / range;
3571
+ const index = Math.min(Math.floor(normalized * chars.length), chars.length - 1);
3572
+ return chars[index];
3573
+ })
3574
+ .join("");
3575
+ }
3576
+
3577
+ /**
3578
+ * Format eval status for display
3579
+ */
3580
+ function formatEvalStatusOutput(status: {
3581
+ phase: "bootstrap" | "stabilization" | "production";
3582
+ runCount: number;
3583
+ thresholds: { stabilization: number; production: number };
3584
+ recentScores: Array<{ timestamp: string; score: number }>;
3585
+ }): void {
3586
+ // Phase banner with color
3587
+ const phaseEmoji = status.phase === "bootstrap" ? "🌱" : status.phase === "stabilization" ? "⚙️" : "🚀";
3588
+ const phaseColor = status.phase === "bootstrap" ? yellow : status.phase === "stabilization" ? cyan : green;
3589
+ p.log.step(`${phaseEmoji} Phase: ${phaseColor(bold(status.phase))}`);
3590
+ p.log.message(`${dim("Runs:")} ${status.runCount}`);
3591
+ console.log();
3592
+
3593
+ // Thresholds box
3594
+ p.log.message(bold("Gate Thresholds"));
3595
+ const stabilizationPct = (status.thresholds.stabilization * 100).toFixed(0);
3596
+ const productionPct = (status.thresholds.production * 100).toFixed(0);
3597
+ p.log.message(` ${yellow("⚠")} Stabilization: ${stabilizationPct}% regression ${dim("(warn)")}`);
3598
+ p.log.message(` ${red("✗")} Production: ${productionPct}% regression ${dim("(fail)")}`);
3599
+ console.log();
3600
+
3601
+ // Recent scores with sparkline
3602
+ if (status.recentScores.length > 0) {
3603
+ p.log.message(bold("Recent Scores"));
3604
+ const sparkline = generateSparkline(status.recentScores.map((s) => s.score));
3605
+ p.log.message(cyan(` ${sparkline}`));
3606
+ for (const { timestamp, score } of status.recentScores) {
3607
+ const time = new Date(timestamp).toLocaleString();
3608
+ const scoreColor = score >= 0.8 ? green : score >= 0.6 ? yellow : red;
3609
+ p.log.message(` ${dim(time)}: ${scoreColor(score.toFixed(2))}`);
3610
+ }
3611
+ } else {
3612
+ p.log.message(dim("No scores yet - collecting data"));
3613
+ }
3614
+ }
3615
+
3616
+ /**
3617
+ * Format eval history for display
3618
+ */
3619
+ function formatEvalHistoryOutput(history: Array<{
3620
+ timestamp: string;
3621
+ eval_name: string;
3622
+ score: number;
3623
+ run_count: number;
3624
+ }>): void {
3625
+ if (history.length === 0) {
3626
+ p.log.message("No eval history found");
3627
+ return;
3628
+ }
3629
+
3630
+ p.log.step("Eval History");
3631
+ console.log();
3632
+
3633
+ // Group by eval name
3634
+ const grouped = new Map<string, typeof history>();
3635
+ for (const entry of history) {
3636
+ if (!grouped.has(entry.eval_name)) {
3637
+ grouped.set(entry.eval_name, []);
3638
+ }
3639
+ grouped.get(entry.eval_name)!.push(entry);
3640
+ }
3641
+
3642
+ // Display each eval group
3643
+ for (const [evalName, entries] of grouped) {
3644
+ p.log.message(bold(cyan(evalName)));
3645
+
3646
+ // Calculate stats
3647
+ const scores = entries.map((e) => e.score);
3648
+ const avgScore = scores.reduce((sum, s) => sum + s, 0) / scores.length;
3649
+ const sparkline = generateSparkline(scores);
3650
+
3651
+ // Trend line with stats
3652
+ const avgColor = avgScore >= 0.8 ? green : avgScore >= 0.6 ? yellow : red;
3653
+ p.log.message(` ${cyan(sparkline)} ${dim("avg:")} ${avgColor(avgScore.toFixed(2))} ${dim(`(${entries.length} runs)`)}`);
3654
+
3655
+ // Show latest 5 entries
3656
+ const latest = entries.slice(-5);
3657
+ for (const entry of latest) {
3658
+ const time = new Date(entry.timestamp).toLocaleTimeString();
3659
+ const scoreColor = entry.score >= 0.8 ? green : entry.score >= 0.6 ? yellow : red;
3660
+ p.log.message(` ${dim(time)} ${dim(`#${entry.run_count}`)} ${scoreColor(entry.score.toFixed(2))}`);
3661
+ }
3662
+
3663
+ if (entries.length > 5) {
3664
+ p.log.message(dim(` ... and ${entries.length - 5} more`));
3665
+ }
3666
+
3667
+ console.log();
3668
+ }
3669
+ }
3670
+
3671
+ /**
3672
+ * Format eval run result (gate check)
3673
+ */
3674
+ function formatEvalRunResultOutput(result: {
3675
+ passed: boolean;
3676
+ phase: "bootstrap" | "stabilization" | "production";
3677
+ message: string;
3678
+ baseline?: number;
3679
+ currentScore: number;
3680
+ regressionPercent?: number;
3681
+ }): void {
3682
+ // Pass/fail banner with color
3683
+ if (result.passed) {
3684
+ p.log.success(bold(green("✓ PASS")));
3685
+ } else {
3686
+ p.log.error(bold(red("✗ FAIL")));
3687
+ }
3688
+ console.log();
3689
+
3690
+ // Phase
3691
+ const phaseColor = result.phase === "bootstrap" ? yellow : result.phase === "stabilization" ? cyan : green;
3692
+ p.log.message(`${dim("Phase:")} ${phaseColor(result.phase)}`);
3693
+
3694
+ // Score with color coding
3695
+ const scoreColor = result.currentScore >= 0.8 ? green : result.currentScore >= 0.6 ? yellow : red;
3696
+ p.log.message(`${dim("Score:")} ${bold(scoreColor(result.currentScore.toFixed(2)))}`);
3697
+
3698
+ if (result.baseline !== undefined) {
3699
+ p.log.message(`${dim("Baseline:")} ${result.baseline.toFixed(2)}`);
3700
+ }
3701
+
3702
+ if (result.regressionPercent !== undefined) {
3703
+ const regressionPct = result.regressionPercent * 100;
3704
+ const sign = regressionPct > 0 ? "+" : "";
3705
+ const regressionColor = regressionPct > 5 ? red : regressionPct > 0 ? yellow : green;
3706
+ p.log.message(`${dim("Regression:")} ${regressionColor(`${sign}${regressionPct.toFixed(1)}%`)}`);
3707
+ }
3708
+
3709
+ console.log();
3710
+ p.log.message(result.message);
3711
+ }
3712
+
3713
+ // ============================================================================
3714
+ // Eval Command
3715
+ // ============================================================================
3716
+
3717
+ async function evalCommand() {
3718
+ const subcommand = process.argv[3];
3719
+
3720
+ switch (subcommand) {
3721
+ case "status": {
3722
+ await evalStatus();
3723
+ break;
3724
+ }
3725
+ case "history": {
3726
+ await evalHistory();
3727
+ break;
3728
+ }
3729
+ case "run": {
3730
+ await evalRun();
3731
+ break;
3732
+ }
3733
+ case undefined:
3734
+ case "--help":
3735
+ case "-h": {
3736
+ await evalHelp();
3737
+ break;
3738
+ }
3739
+ default: {
3740
+ console.error(`Unknown eval subcommand: ${subcommand}`);
3741
+ await evalHelp();
3742
+ process.exit(1);
3743
+ }
3744
+ }
3745
+ }
3746
+
3747
+ async function evalHelp() {
3748
+ p.intro("swarm eval");
3749
+
3750
+ console.log();
3751
+ console.log("Eval-Driven Development with Progressive Gates");
3752
+ console.log();
3753
+ console.log("Usage:");
3754
+ console.log(" swarm eval status - Show current phase, thresholds, recent scores");
3755
+ console.log(" swarm eval history - Show eval run history with trends");
3756
+ console.log(" swarm eval run - Execute evals and report results (stub)");
3757
+ console.log();
3758
+
3759
+ p.outro("Run 'swarm eval <command>' for details");
3760
+ }
3761
+
3762
+ async function evalStatus() {
3763
+ const { getPhase, getScoreHistory } = await import("../src/eval-history.js");
3764
+ const { DEFAULT_THRESHOLDS } = await import("../src/eval-gates.js");
3765
+
3766
+ p.intro("swarm eval status");
3767
+
3768
+ const projectPath = process.cwd();
3769
+ const evalName = process.argv[4] || "swarm-decomposition"; // Default eval
3770
+
3771
+ const phase = getPhase(projectPath, evalName);
3772
+ const history = getScoreHistory(projectPath, evalName);
3773
+ const recentScores = history.slice(-5).map((run) => ({
3774
+ timestamp: run.timestamp,
3775
+ score: run.score,
3776
+ }));
3777
+
3778
+ formatEvalStatusOutput({
3779
+ phase,
3780
+ runCount: history.length,
3781
+ thresholds: DEFAULT_THRESHOLDS,
3782
+ recentScores,
3783
+ });
3784
+
3785
+ console.log();
3786
+ p.outro(`Eval: ${evalName}`);
3787
+ }
3788
+
3789
+ async function evalHistory() {
3790
+ const { getEvalHistoryPath } = await import("../src/eval-history.js");
3791
+
3792
+ p.intro("swarm eval history");
3793
+
3794
+ const projectPath = process.cwd();
3795
+ const historyPath = getEvalHistoryPath(projectPath);
3796
+
3797
+ if (!existsSync(historyPath)) {
3798
+ p.log.warn("No eval history found");
3799
+ p.log.message(dim(`Expected: ${historyPath}`));
3800
+ p.outro("Run evals to generate history");
3801
+ return;
3802
+ }
3803
+
3804
+ // Read all history
3805
+ const content = readFileSync(historyPath, "utf-8");
3806
+ const lines = content.trim().split("\n").filter(Boolean);
3807
+ const history = lines.map((line) => JSON.parse(line));
3808
+
3809
+ formatEvalHistoryOutput(history);
3810
+
3811
+ p.outro(`History file: ${historyPath}`);
3812
+ }
3813
+
3814
+ async function evalRun() {
3815
+ const ciMode = process.argv.includes("--ci");
3816
+ const projectPath = process.cwd();
3817
+
3818
+ if (!ciMode) {
3819
+ p.intro("swarm eval run");
3820
+ }
3821
+
3822
+ // Import gate checking
3823
+ const { checkGate } = await import("../src/eval-gates.js");
3824
+ const { recordEvalRun, getScoreHistory } = await import("../src/eval-history.js");
3825
+
3826
+ // Run evalite for each eval
3827
+ const evalFiles = [
3828
+ "compaction-prompt",
3829
+ "coordinator-behavior",
3830
+ "coordinator-session",
3831
+ "swarm-decomposition",
3832
+ ];
3833
+
3834
+ const results: Record<string, any> = {};
3835
+ let anyFailure = false;
3836
+
3837
+ for (const evalName of evalFiles) {
3838
+ if (!ciMode) {
3839
+ p.log.step(`Running ${evalName}...`);
3840
+ } else {
3841
+ console.log(`Running ${evalName}...`);
3842
+ }
3843
+
3844
+ try {
3845
+ // Run evalite (simplified - in real implementation would parse actual results)
3846
+ // For now, use a placeholder score - the real implementation would integrate with evalite
3847
+ const evalPath = `evals/${evalName}.eval.ts`;
3848
+
3849
+ // This is a stub - real implementation would:
3850
+ // 1. Run evalite and capture results
3851
+ // 2. Parse the score from evalite output
3852
+ // 3. Use that score for gate checking
3853
+
3854
+ // For CI mode, we'll assume passing scores for now
3855
+ const mockScore = 0.85; // Placeholder
3856
+
3857
+ // Check gate
3858
+ const gateResult = checkGate(projectPath, evalName, mockScore);
3859
+
3860
+ // Record to history
3861
+ const history = getScoreHistory(projectPath, evalName);
3862
+ recordEvalRun(projectPath, {
3863
+ timestamp: new Date().toISOString(),
3864
+ eval_name: evalName,
3865
+ score: mockScore,
3866
+ run_count: history.length + 1,
3867
+ });
3868
+
3869
+ // Store result
3870
+ results[evalName] = gateResult;
3871
+
3872
+ if (!gateResult.passed) {
3873
+ anyFailure = true;
3874
+ }
3875
+
3876
+ // Format output
3877
+ if (!ciMode) {
3878
+ formatEvalRunResultOutput(gateResult);
3879
+ } else {
3880
+ const status = gateResult.passed ? "✅ PASS" : "❌ FAIL";
3881
+ console.log(`${evalName}: ${status} (${gateResult.phase}, score: ${gateResult.currentScore.toFixed(2)})`);
3882
+ console.log(` ${gateResult.message}`);
3883
+ }
3884
+ } catch (error) {
3885
+ if (!ciMode) {
3886
+ p.log.error(`Failed to run ${evalName}: ${error}`);
3887
+ } else {
3888
+ console.error(`Failed to run ${evalName}: ${error}`);
3889
+ }
3890
+ anyFailure = true;
3891
+ }
3892
+ }
3893
+
3894
+ // In CI mode, write results to file for PR comment
3895
+ if (ciMode) {
3896
+ const resultsPath = join(projectPath, ".hive", "eval-results.json");
3897
+ ensureHiveDirectory(projectPath);
3898
+ writeFileSync(resultsPath, JSON.stringify(results, null, 2));
3899
+ console.log(`\nResults written to ${resultsPath}`);
3900
+
3901
+ // Exit with error code if any production-phase eval failed
3902
+ if (anyFailure) {
3903
+ const productionFailures = Object.entries(results).filter(
3904
+ ([_, result]) => !result.passed && result.phase === "production"
3905
+ );
3906
+
3907
+ if (productionFailures.length > 0) {
3908
+ console.error(`\n❌ ${productionFailures.length} production-phase eval(s) failed`);
3909
+ process.exit(1);
3910
+ }
3911
+ }
3912
+
3913
+ console.log("\n✅ All evals passed or in pre-production phase");
3914
+ } else {
3915
+ console.log();
3916
+ p.outro(anyFailure ? "Some evals need attention" : "All evals passed!");
3917
+ }
3918
+ }
3919
+
3540
3920
  // ============================================================================
3541
3921
  // Main
3542
3922
  // ============================================================================
@@ -3591,6 +3971,9 @@ switch (command) {
3591
3971
  case "logs":
3592
3972
  await logs();
3593
3973
  break;
3974
+ case "eval":
3975
+ await evalCommand();
3976
+ break;
3594
3977
  case "version":
3595
3978
  case "--version":
3596
3979
  case "-v":
@@ -38,7 +38,7 @@
38
38
  * This is NOT about preserving state for a human - it's about the swarm continuing
39
39
  * autonomously after context compression.
40
40
  */
41
- export declare const SWARM_COMPACTION_CONTEXT = "## \uD83D\uDC1D SWARM ACTIVE - You Are The COORDINATOR\n\nContext was compacted but the swarm is still running. You are the **COORDINATOR**.\n\n### \u26D4 NEVER DO THESE (Coordinator Anti-Patterns)\n\n**CRITICAL: Coordinators NEVER do implementation work. ALWAYS spawn workers.**\n\n- \u274C **NEVER** use `edit` or `write` tools - SPAWN A WORKER\n- \u274C **NEVER** run tests with `bash` - SPAWN A WORKER \n- \u274C **NEVER** implement features yourself - SPAWN A WORKER\n- \u274C **NEVER** \"just do it myself to save time\" - NO. SPAWN A WORKER.\n- \u274C **NEVER** reserve files with `swarmmail_reserve` - Workers reserve files\n\n**If you catch yourself about to edit a file, STOP. Use `swarm_spawn_subtask` instead.**\n\n### \u2705 ALWAYS DO THESE (Coordinator Checklist)\n\nOn resume, execute this checklist IN ORDER:\n\n1. `swarm_status(epic_id=\"<epic>\", project_key=\"<path>\")` - Get current state\n2. `swarmmail_inbox(limit=5)` - Check for agent messages\n3. For completed work: `swarm_review` \u2192 `swarm_review_feedback`\n4. For open subtasks: `swarm_spawn_subtask` (NOT \"do it yourself\")\n5. For blocked work: Investigate, unblock, reassign\n\n### Preserve in Summary\n\nExtract from session context:\n\n1. **Epic & Subtasks** - IDs, titles, status, file assignments\n2. **What's Running** - Which agents are active, what they're working on \n3. **What's Blocked** - Blockers and what's needed to unblock\n4. **What's Done** - Completed work and any follow-ups needed\n5. **What's Next** - Pending subtasks ready to spawn\n\n### Summary Format\n\n```\n## \uD83D\uDC1D Swarm State\n\n**Epic:** <cell-xxx> - <title>\n**Project:** <path>\n**Progress:** X/Y subtasks complete\n\n**Active:**\n- <cell-xxx>: <title> [in_progress] \u2192 <agent> working on <files>\n\n**Blocked:**\n- <cell-xxx>: <title> - BLOCKED: <reason>\n\n**Completed:**\n- <cell-xxx>: <title> \u2713\n\n**Ready to Spawn:**\n- <cell-xxx>: <title> (files: <...>)\n```\n\n### Your Role\n\n- **Spawn aggressively** - If a subtask is ready and unblocked, spawn an agent\n- **Monitor actively** - Check status, read messages, respond to blockers\n- **Review work** - Use `swarm_review` and `swarm_review_feedback` for completed work\n- **Close the loop** - When all subtasks done, verify and close the epic\n\n**You are the COORDINATOR. You orchestrate. You do NOT implement. Spawn workers.**\n";
41
+ export declare const SWARM_COMPACTION_CONTEXT = "\n\u250C\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502 \u2502\n\u2502 \uD83D\uDC1D YOU ARE THE COORDINATOR \uD83D\uDC1D \u2502\n\u2502 \u2502\n\u2502 NOT A WORKER. NOT AN IMPLEMENTER. \u2502\n\u2502 YOU ORCHESTRATE. \u2502\n\u2502 \u2502\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518\n\n## \uD83C\uDFAF NON-NEGOTIABLE: YOU ARE THE COORDINATOR\n\nContext was compacted but the swarm is still running. **YOU ARE THE COORDINATOR.**\n\nYour role is ORCHESTRATION, not implementation. When you catch yourself about to do work directly, STOP.\n\n### \u26D4 NEVER DO THESE (Coordinator Anti-Patterns)\n\n**CRITICAL: Coordinators NEVER do implementation work. ALWAYS spawn workers.**\n\n- \u274C **NEVER** use `edit` or `write` tools - SPAWN A WORKER\n- \u274C **NEVER** run tests with `bash` - SPAWN A WORKER \n- \u274C **NEVER** implement features yourself - SPAWN A WORKER\n- \u274C **NEVER** \"just do it myself to save time\" - NO. SPAWN A WORKER.\n- \u274C **NEVER** reserve files with `swarmmail_reserve` - Workers reserve files\n- \u274C **NEVER** fetch files/docs directly - SPAWN A RESEARCHER\n\n**If you catch yourself about to edit a file, STOP. Use `swarm_spawn_subtask` instead.**\n\n### \uD83D\uDEAB FORBIDDEN TOOLS (Coordinators MUST delegate these)\n\n**NEVER use these tools directly. ALWAYS spawn a researcher worker via `swarm_spawn_researcher`:**\n\n**Repository fetching:**\n- `repo-crawl_file`, `repo-crawl_readme`, `repo-crawl_search`, `repo-crawl_structure`, `repo-crawl_tree`\n- `repo-autopsy_*` (all repo-autopsy tools)\n\n**Web/documentation fetching:**\n- `webfetch`, `fetch_fetch`\n- `context7_resolve-library-id`, `context7_get-library-docs`\n\n**Knowledge base:**\n- `pdf-brain_search`, `pdf-brain_read`\n\n**If you need external data:** Use `swarm_spawn_researcher` with a clear research task. The researcher will fetch, summarize, and return findings.\n\n### \u2705 ALWAYS DO THESE (Coordinator Checklist)\n\nOn resume, execute this checklist IN ORDER:\n\n1. `swarm_status(epic_id=\"<epic>\", project_key=\"<path>\")` - Get current state\n2. `swarmmail_inbox(limit=5)` - Check for agent messages\n3. For completed work: `swarm_review` \u2192 `swarm_review_feedback`\n4. For open subtasks: `swarm_spawn_subtask` (NOT \"do it yourself\")\n5. For blocked work: Investigate, unblock, reassign\n\n### Preserve in Summary\n\nExtract from session context:\n\n1. **Epic & Subtasks** - IDs, titles, status, file assignments\n2. **What's Running** - Which agents are active, what they're working on \n3. **What's Blocked** - Blockers and what's needed to unblock\n4. **What's Done** - Completed work and any follow-ups needed\n5. **What's Next** - Pending subtasks ready to spawn\n\n### Summary Format\n\n```\n## \uD83D\uDC1D Swarm State\n\n**Epic:** <cell-xxx> - <title>\n**Project:** <path>\n**Progress:** X/Y subtasks complete\n\n**Active:**\n- <cell-xxx>: <title> [in_progress] \u2192 <agent> working on <files>\n\n**Blocked:**\n- <cell-xxx>: <title> - BLOCKED: <reason>\n\n**Completed:**\n- <cell-xxx>: <title> \u2713\n\n**Ready to Spawn:**\n- <cell-xxx>: <title> (files: <...>)\n```\n\n### Your Role\n\n- **Spawn aggressively** - If a subtask is ready and unblocked, spawn an agent\n- **Monitor actively** - Check status, read messages, respond to blockers\n- **Review work** - Use `swarm_review` and `swarm_review_feedback` for completed work\n- **Close the loop** - When all subtasks done, verify and close the epic\n\n**You are the COORDINATOR. You orchestrate. You do NOT implement. Spawn workers.**\n\n---\n\n## \uD83D\uDCCB FULL COORDINATOR WORKFLOW (Reference)\n\nYou are ALWAYS swarming. Here is the complete workflow for any new work:\n\n### Phase 1.5: Research Phase (FOR COMPLEX TASKS)\n\n**If the task requires understanding unfamiliar technologies, spawn a researcher FIRST:**\n\n```\nswarm_spawn_researcher(\n research_id=\"research-<topic>\",\n epic_id=\"<epic-id>\",\n tech_stack=[\"<technology>\"],\n project_path=\"<path>\"\n)\n// Then spawn with Task(subagent_type=\"swarm/researcher\", prompt=\"<from above>\")\n```\n\n### Phase 2: Knowledge Gathering\n\n```\nsemantic-memory_find(query=\"<task keywords>\", limit=5) # Past learnings\ncass_search(query=\"<task description>\", limit=5) # Similar past tasks \nskills_list() # Available skills\n```\n\n### Phase 3: Decompose\n\n```\nswarm_select_strategy(task=\"<task>\")\nswarm_plan_prompt(task=\"<task>\", context=\"<synthesized knowledge>\")\nswarm_validate_decomposition(response=\"<CellTree JSON>\")\n```\n\n### Phase 4: Create Cells\n\n`hive_create_epic(epic_title=\"<task>\", subtasks=[...])`\n\n### Phase 5: DO NOT Reserve Files\n\n> **\u26A0\uFE0F Coordinator NEVER reserves files.** Workers reserve their own files.\n\n### Phase 6: Spawn Workers\n\n```\nswarm_spawn_subtask(bead_id, epic_id, title, files, shared_context, project_path)\nTask(subagent_type=\"swarm/worker\", prompt=\"<from above>\")\n```\n\n### Phase 7: MANDATORY Review Loop\n\n**AFTER EVERY Task() RETURNS:**\n\n1. `swarmmail_inbox()` - Check for messages\n2. `swarm_review(project_key, epic_id, task_id, files_touched)` - Generate review\n3. Evaluate against epic goals\n4. `swarm_review_feedback(project_key, task_id, worker_id, status, issues)`\n\n**If needs_changes:**\n```\nswarm_spawn_retry(bead_id, epic_id, original_prompt, attempt, issues, diff, files, project_path)\n// Spawn NEW worker with Task() using retry prompt\n// Max 3 attempts before marking task blocked\n```\n\n### Phase 8: Complete\n\n`hive_sync()` - Sync all cells to git\n\n## Strategy Reference\n\n| Strategy | Best For | Keywords |\n| -------------- | ------------------------ | -------------------------------------- |\n| file-based | Refactoring, migrations | refactor, migrate, rename, update all |\n| feature-based | New features | add, implement, build, create, feature |\n| risk-based | Bug fixes, security | fix, bug, security, critical, urgent |\n\n**You are the COORDINATOR. You orchestrate. You do NOT implement. Spawn workers.**\n";
42
42
  /**
43
43
  * Fallback detection prompt - tells the compactor what to look for
44
44
  *
@@ -1 +1 @@
1
- {"version":3,"file":"compaction-hook.d.ts","sourceRoot":"","sources":["../src/compaction-hook.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AA+BH;;;;;;;;;GASG;AACH,eAAO,MAAM,wBAAwB,40EAkEpC,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,wBAAwB,0nCAiCpC,CAAC;AAqFF;;;;;;;;GAQG;AACH,MAAM,MAAM,cAAc,GAAG,OAAO,CAAC;AAErC;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,GAAG,CACX,MAAM,EACN;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,CACrE,CAAC;IACF,UAAU,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,OAAO,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;CACjE;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,cAAc,EACtB,SAAS,EAAE,MAAM,EACjB,KAAK,GAAE,MAAY,GAClB,OAAO,CAAC,iBAAiB,CAAC,CAgJ5B;AAoVD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,CAAC,EAAE,cAAc,IAExD,OAAO;IAAE,SAAS,EAAE,MAAM,CAAA;CAAE,EAC5B,QAAQ;IAAE,OAAO,EAAE,MAAM,EAAE,CAAA;CAAE,KAC5B,OAAO,CAAC,IAAI,CAAC,CA4HjB"}
1
+ {"version":3,"file":"compaction-hook.d.ts","sourceRoot":"","sources":["../src/compaction-hook.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AA+BH;;;;;;;;;GASG;AACH,eAAO,MAAM,wBAAwB,w6NAiLpC,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,wBAAwB,0nCAiCpC,CAAC;AAqFF;;;;;;;;GAQG;AACH,MAAM,MAAM,cAAc,GAAG,OAAO,CAAC;AAErC;;GAEG;AACH,MAAM,WAAW,iBAAiB;IAChC,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,GAAG,CACX,MAAM,EACN;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,MAAM,CAAC,EAAE,MAAM,CAAC;QAAC,KAAK,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,CACrE,CAAC;IACF,UAAU,CAAC,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,OAAO,CAAC;QAAC,SAAS,EAAE,MAAM,CAAA;KAAE,CAAC;CACjE;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAsB,mBAAmB,CACvC,MAAM,EAAE,cAAc,EACtB,SAAS,EAAE,MAAM,EACjB,KAAK,GAAE,MAAY,GAClB,OAAO,CAAC,iBAAiB,CAAC,CAgJ5B;AAoVD;;;;;;;;;;;;;;;;;;;;;;;;GAwBG;AACH,wBAAgB,oBAAoB,CAAC,MAAM,CAAC,EAAE,cAAc,IAExD,OAAO;IAAE,SAAS,EAAE,MAAM,CAAA;CAAE,EAC5B,QAAQ;IAAE,OAAO,EAAE,MAAM,EAAE,CAAA;CAAE,KAC5B,OAAO,CAAC,IAAI,CAAC,CA4HjB"}
@@ -0,0 +1,124 @@
1
+ /**
2
+ * Compaction Prompt Quality Scoring - Pure Functions
3
+ *
4
+ * Evaluates the quality of continuation prompts generated after context compaction.
5
+ * **Problem**: Post-compaction coordinators often "wake up" confused, forget their role,
6
+ * and start editing files instead of checking worker status.
7
+ *
8
+ * **Solution**: Score prompts on 5 dimensions that predict coordinator success:
9
+ *
10
+ * 1. **Epic ID Specificity (0.20)**: Real IDs (`mjkw...`) not placeholders (`<epic-id>`, `bd-xxx`)
11
+ * - Placeholders = coordinator can't check actual swarm status
12
+ *
13
+ * 2. **Actionability (0.20)**: Tool calls with real values (e.g., `swarm_status(epic_id='mjkw81rkq4c')`)
14
+ * - Generic instructions like "check status" don't work
15
+ *
16
+ * 3. **Coordinator Identity (0.25)**: ASCII header + strong mandates (NEVER/ALWAYS)
17
+ * - Visual + semantic cues reinforce role post-compaction
18
+ *
19
+ * 4. **Forbidden Tools Listed (0.15)**: Explicitly lists Edit, Write, swarmmail_reserve, git commit
20
+ * - Naming forbidden tools reduces violations
21
+ *
22
+ * 5. **Post-Compaction Discipline (0.20)**: First suggested tool is swarm_status or inbox (not Edit)
23
+ * - First tool sets the pattern - "check status" vs "dive into code"
24
+ *
25
+ * **Pure functions**: These can be tested without evalite. The evalite wrappers are in
26
+ * `evals/scorers/compaction-prompt-scorers.ts`.
27
+ *
28
+ * **Data source**: Captured from `captureCompactionEvent()` with `compaction_type: "prompt_generated"`.
29
+ * The payload includes the FULL prompt content (not truncated) for scoring.
30
+ *
31
+ * **Integration**: `compaction-prompt.eval.ts` uses these scorers to track prompt quality over time.
32
+ * Progressive gates enforce quality: bootstrap → stabilization → production.
33
+ *
34
+ * @module compaction-prompt-scoring
35
+ */
36
+ /**
37
+ * Compaction prompt structure (from LLM generation)
38
+ */
39
+ export interface CompactionPrompt {
40
+ content: string;
41
+ }
42
+ /**
43
+ * Scorer result type
44
+ */
45
+ export interface ScorerResult {
46
+ score: number;
47
+ message: string;
48
+ }
49
+ /** Matches real epic/cell IDs (mjkw prefix + 7+ base36 chars) */
50
+ export declare const REAL_EPIC_ID: RegExp;
51
+ /** Matches common placeholder patterns */
52
+ export declare const PLACEHOLDERS: RegExp[];
53
+ /** Matches ASCII box-drawing characters (for headers) */
54
+ export declare const ASCII_BOX: RegExp;
55
+ /** Matches strong mandate language */
56
+ export declare const STRONG_LANGUAGE: RegExp[];
57
+ /**
58
+ * Score epic ID specificity
59
+ *
60
+ * Validates that epic IDs are REAL, not placeholders.
61
+ * Placeholders like <epic-id>, bd-xxx, <path> indicate
62
+ * the prompt generator failed to inject actual values.
63
+ *
64
+ * @returns 1.0 if real IDs, 0.0 if placeholders found
65
+ */
66
+ export declare function scoreEpicIdSpecificity(prompt: CompactionPrompt): ScorerResult;
67
+ /**
68
+ * Score actionability of tool calls
69
+ *
70
+ * Validates that the prompt includes SPECIFIC actionable tool calls.
71
+ * Generic instructions like "check status" are useless.
72
+ * Good: swarm_status(epic_id='mjkw81rkq4c', project_key='/path')
73
+ * Bad: "Check the status of workers"
74
+ *
75
+ * @returns 1.0 if actionable tool calls with real values, 0.0 otherwise
76
+ */
77
+ export declare function scoreActionability(prompt: CompactionPrompt): ScorerResult;
78
+ /**
79
+ * Score coordinator identity reinforcement
80
+ *
81
+ * Validates that the prompt has STRONG coordinator identity reinforcement.
82
+ * Post-compaction coordinators lose their identity without visual+semantic cues.
83
+ *
84
+ * Checks:
85
+ * 1. ASCII box header (visual anchor)
86
+ * 2. Strong language (NEVER/ALWAYS, not "should"/"consider")
87
+ *
88
+ * @returns 1.0 for ASCII header + strong mandates, 0.5 for header only, 0.0 otherwise
89
+ */
90
+ export declare function scoreCoordinatorIdentity(prompt: CompactionPrompt): ScorerResult;
91
+ /**
92
+ * Score forbidden tools listing
93
+ *
94
+ * Validates that the prompt LISTS forbidden tools by name.
95
+ * Coordinators must know exactly which tools to avoid.
96
+ *
97
+ * Required forbidden tools:
98
+ * 1. Edit
99
+ * 2. Write
100
+ * 3. swarmmail_reserve (only workers reserve)
101
+ * 4. git commit (workers commit)
102
+ *
103
+ * @returns ratio of forbidden tools mentioned (0.0 to 1.0)
104
+ */
105
+ export declare function scoreForbiddenToolsPresent(prompt: CompactionPrompt): ScorerResult;
106
+ /**
107
+ * Score post-compaction discipline (first tool correctness)
108
+ *
109
+ * Validates that the FIRST suggested tool is correct.
110
+ * Coordinators should check status FIRST, not edit files.
111
+ *
112
+ * Good first tools:
113
+ * - swarm_status
114
+ * - swarmmail_inbox
115
+ *
116
+ * Bad first tools:
117
+ * - Edit
118
+ * - Write
119
+ * - Read (should check status first)
120
+ *
121
+ * @returns 1.0 if first tool is swarm_status or inbox, 0.0 otherwise
122
+ */
123
+ export declare function scorePostCompactionDiscipline(prompt: CompactionPrompt): ScorerResult;
124
+ //# sourceMappingURL=compaction-prompt-scoring.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"compaction-prompt-scoring.d.ts","sourceRoot":"","sources":["../src/compaction-prompt-scoring.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAkCG;AAEH;;GAEG;AACH,MAAM,WAAW,gBAAgB;IAChC,OAAO,EAAE,MAAM,CAAC;CAChB;AAED;;GAEG;AACH,MAAM,WAAW,YAAY;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;CAChB;AAID,iEAAiE;AACjE,eAAO,MAAM,YAAY,QAAqB,CAAC;AAE/C,0CAA0C;AAC1C,eAAO,MAAM,YAAY,UAKxB,CAAC;AAEF,yDAAyD;AACzD,eAAO,MAAM,SAAS,QAAiB,CAAC;AAExC,sCAAsC;AACtC,eAAO,MAAM,eAAe,UAAoD,CAAC;AAIjF;;;;;;;;GAQG;AACH,wBAAgB,sBAAsB,CAAC,MAAM,EAAE,gBAAgB,GAAG,YAAY,CAuB7E;AAED;;;;;;;;;GASG;AACH,wBAAgB,kBAAkB,CAAC,MAAM,EAAE,gBAAgB,GAAG,YAAY,CA+BzE;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,wBAAwB,CACvC,MAAM,EAAE,gBAAgB,GACtB,YAAY,CA6Bd;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,0BAA0B,CACzC,MAAM,EAAE,gBAAgB,GACtB,YAAY,CAiCd;AAED;;;;;;;;;;;;;;;;GAgBG;AACH,wBAAgB,6BAA6B,CAC5C,MAAM,EAAE,gBAAgB,GACtB,YAAY,CAiCd"}
@@ -70,7 +70,7 @@ export type PartialEvalRecord = Partial<EvalRecord> & {
70
70
  task: string;
71
71
  };
72
72
  /**
73
- * Coordinator Event - captures coordinator decisions, violations, and outcomes
73
+ * Coordinator Event - captures coordinator decisions, violations, outcomes, and compaction
74
74
  */
75
75
  export declare const CoordinatorEventSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
76
76
  session_id: z.ZodString;
@@ -108,6 +108,19 @@ export declare const CoordinatorEventSchema: z.ZodDiscriminatedUnion<[z.ZodObjec
108
108
  epic_complete: "epic_complete";
109
109
  }>;
110
110
  payload: z.ZodAny;
111
+ }, z.core.$strip>, z.ZodObject<{
112
+ session_id: z.ZodString;
113
+ epic_id: z.ZodString;
114
+ timestamp: z.ZodString;
115
+ event_type: z.ZodLiteral<"COMPACTION">;
116
+ compaction_type: z.ZodEnum<{
117
+ detection_complete: "detection_complete";
118
+ prompt_generated: "prompt_generated";
119
+ context_injected: "context_injected";
120
+ resumption_started: "resumption_started";
121
+ tool_call_tracked: "tool_call_tracked";
122
+ }>;
123
+ payload: z.ZodAny;
111
124
  }, z.core.$strip>], "event_type">;
112
125
  export type CoordinatorEvent = z.infer<typeof CoordinatorEventSchema>;
113
126
  /**
@@ -154,6 +167,19 @@ export declare const CoordinatorSessionSchema: z.ZodObject<{
154
167
  epic_complete: "epic_complete";
155
168
  }>;
156
169
  payload: z.ZodAny;
170
+ }, z.core.$strip>, z.ZodObject<{
171
+ session_id: z.ZodString;
172
+ epic_id: z.ZodString;
173
+ timestamp: z.ZodString;
174
+ event_type: z.ZodLiteral<"COMPACTION">;
175
+ compaction_type: z.ZodEnum<{
176
+ detection_complete: "detection_complete";
177
+ prompt_generated: "prompt_generated";
178
+ context_injected: "context_injected";
179
+ resumption_started: "resumption_started";
180
+ tool_call_tracked: "tool_call_tracked";
181
+ }>;
182
+ payload: z.ZodAny;
157
183
  }, z.core.$strip>], "event_type">>;
158
184
  }, z.core.$strip>;
159
185
  export type CoordinatorSession = z.infer<typeof CoordinatorSessionSchema>;
@@ -294,6 +320,60 @@ export declare function ensureSessionDir(): void;
294
320
  * Appends the event as a JSONL line to ~/.config/swarm-tools/sessions/{session_id}.jsonl
295
321
  */
296
322
  export declare function captureCoordinatorEvent(event: CoordinatorEvent): void;
323
+ /**
324
+ * Capture a compaction event to the session file
325
+ *
326
+ * Helper for capturing COMPACTION events with automatic timestamp generation.
327
+ * Tracks compaction hook lifecycle: detection → prompt generation → context injection → resumption.
328
+ *
329
+ * **Part of eval-driven development pipeline:** Compaction events are used by `compaction-prompt.eval.ts`
330
+ * to score prompt quality (ID specificity, actionability, coordinator identity).
331
+ *
332
+ * **Lifecycle stages:**
333
+ * - `detection_complete` - Compaction detected (confidence level, context type)
334
+ * - `prompt_generated` - Continuation prompt created (FULL content stored for eval)
335
+ * - `context_injected` - Prompt injected into OpenCode context
336
+ * - `resumption_started` - Coordinator resumed from checkpoint
337
+ * - `tool_call_tracked` - First tool called post-compaction (measures discipline)
338
+ *
339
+ * @param params - Compaction event parameters
340
+ * @param params.session_id - Coordinator session ID
341
+ * @param params.epic_id - Epic ID being coordinated
342
+ * @param params.compaction_type - Stage of compaction lifecycle
343
+ * @param params.payload - Event-specific data (full prompt content, detection results, etc.)
344
+ *
345
+ * @example
346
+ * // Capture detection complete
347
+ * captureCompactionEvent({
348
+ * session_id: "session-123",
349
+ * epic_id: "bd-456",
350
+ * compaction_type: "detection_complete",
351
+ * payload: {
352
+ * confidence: "high",
353
+ * context_type: "full",
354
+ * epic_id: "bd-456",
355
+ * },
356
+ * });
357
+ *
358
+ * @example
359
+ * // Capture prompt generated (with full content for eval)
360
+ * captureCompactionEvent({
361
+ * session_id: "session-123",
362
+ * epic_id: "bd-456",
363
+ * compaction_type: "prompt_generated",
364
+ * payload: {
365
+ * prompt_length: 5000,
366
+ * full_prompt: "You are a coordinator...", // Full prompt, not truncated - used for quality scoring
367
+ * context_type: "full",
368
+ * },
369
+ * });
370
+ */
371
+ export declare function captureCompactionEvent(params: {
372
+ session_id: string;
373
+ epic_id: string;
374
+ compaction_type: "detection_complete" | "prompt_generated" | "context_injected" | "resumption_started" | "tool_call_tracked";
375
+ payload: any;
376
+ }): void;
297
377
  /**
298
378
  * Read all events from a session file
299
379
  */