opencode-swarm-plugin 0.28.2 → 0.30.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,9 +1,9 @@
1
1
  $ bun build ./src/index.ts --outdir ./dist --target node --external @electric-sql/pglite --external swarm-mail && bun build ./src/plugin.ts --outfile ./dist/plugin.js --target node --external @electric-sql/pglite --external swarm-mail && tsc
2
- Bundled 200 modules in 35ms
2
+ Bundled 200 modules in 34ms
3
3
 
4
4
  index.js 1.20 MB (entry point)
5
5
 
6
- Bundled 201 modules in 32ms
6
+ Bundled 201 modules in 44ms
7
7
 
8
8
  plugin.js 1.16 MB (entry point)
9
9
 
package/CHANGELOG.md CHANGED
@@ -1,5 +1,114 @@
1
1
  # opencode-swarm-plugin
2
2
 
3
+ ## 0.30.0
4
+
5
+ ### Minor Changes
6
+
7
+ - [`f3917ad`](https://github.com/joelhooks/swarm-tools/commit/f3917ad911d3c716a2470a01c66bce3500f644f4) Thanks [@joelhooks](https://github.com/joelhooks)! - ## 🐝 The Great bd CLI Purge
8
+
9
+ The `bd` CLI is officially dead. Long live HiveAdapter!
10
+
11
+ **What changed:**
12
+
13
+ ### `swarm init` Command Rewritten
14
+
15
+ - No longer shells out to `bd init` or `bd create`
16
+ - Uses `ensureHiveDirectory()` and `getHiveAdapter()` directly
17
+ - Supports `.beads` → `.hive` migration with user prompts
18
+ - Creates cells via HiveAdapter, not CLI
19
+
20
+ ### Auto-sync Removed from `index.ts`
21
+
22
+ - Removed `void $\`bd sync\`.quiet().nothrow()`after`hive_close`
23
+ - Users should call `hive_sync` explicitly at session end
24
+ - This was a fire-and-forget that could race with other operations
25
+
26
+ ### Plugin Template Updated
27
+
28
+ - `detectSwarm()` now has confidence levels (HIGH/MEDIUM/LOW/NONE)
29
+ - Added `SWARM_DETECTION_FALLBACK` for uncertain cases
30
+ - Compaction hook injects context based on confidence:
31
+ - HIGH/MEDIUM → Full swarm context
32
+ - LOW → Fallback detection prompt
33
+ - NONE → No injection
34
+
35
+ ### Error Handling Fixed
36
+
37
+ - `execTool()` now handles both string and object error formats
38
+ - Fixes "Tool execution failed" generic error from `swarm_complete`
39
+ - Actual error messages now propagate to the agent
40
+
41
+ **Why it matters:**
42
+
43
+ - No external CLI dependency for core functionality
44
+ - HiveAdapter is type-safe and testable
45
+ - Plugin works in environments without `bd` installed
46
+ - Better error messages for debugging
47
+
48
+ **Migration:** Run `swarm setup` to update your deployed plugin.
49
+
50
+ ## 0.29.0
51
+
52
+ ### Minor Changes
53
+
54
+ - [`a2ff1f4`](https://github.com/joelhooks/swarm-tools/commit/a2ff1f4257a2e9857f63abe4e9b941a573f44380) Thanks [@joelhooks](https://github.com/joelhooks)! - ## 🐝 Cell IDs Now Wear Their Project Colors
55
+
56
+ > _"We may fantasize about being International Men of Mystery, but our code needs to be mundane and clear. One of the most important parts of clear code is good names."_
57
+ > — Martin Fowler, _Refactoring_
58
+
59
+ Cell IDs finally know where they came from. Instead of anonymous `bd-xxx` prefixes,
60
+ new cells proudly display their project name: `swarm-mail-lf2p4u-abc123`.
61
+
62
+ ### What Changed
63
+
64
+ **swarm-mail:**
65
+
66
+ - `generateBeadId()` now reads `package.json` name field from project directory
67
+ - Added `slugifyProjectName()` for safe ID generation (lowercase, special chars → dashes)
68
+ - Falls back to `cell-` prefix if no package.json or no name field
69
+
70
+ **opencode-swarm-plugin:**
71
+
72
+ - Removed all `bd` CLI usage from `swarm-orchestrate.ts` - now uses HiveAdapter
73
+ - Improved compaction hook swarm detection with confidence levels (high/medium/low)
74
+ - Added fallback detection prompt for uncertain swarm states
75
+
76
+ ### Examples
77
+
78
+ | Before | After |
79
+ | ----------------------- | ------------------------------- |
80
+ | `bd-lf2p4u-mjbneh7mqah` | `swarm-mail-lf2p4u-mjbneh7mqah` |
81
+ | `bd-abc123-xyz` | `my-cool-app-abc123-xyz` |
82
+ | (no package.json) | `cell-abc123-xyz` |
83
+
84
+ ### Why It Matters
85
+
86
+ - **Identifiable at a glance** - Know which project a cell belongs to without looking it up
87
+ - **Multi-project workspaces** - Filter/search cells by project prefix
88
+ - **Terminology cleanup** - Removes legacy "bead" (`bd-`) from user-facing IDs
89
+
90
+ ### Backward Compatible
91
+
92
+ Existing `bd-*` IDs still work fine. No migration needed - only NEW cells get project prefixes.
93
+
94
+ ### Compaction: Keeping the Swarm Alive
95
+
96
+ > _"Intelligent and structured group dynamics that emerge not from a leader, but from the local interactions of the elements themselves."_
97
+ > — Daniel Shiffman, _The Nature of Code_
98
+
99
+ The compaction hook now uses multi-signal detection to keep swarms cooking through context compression:
100
+
101
+ - **HIGH confidence:** Active reservations, in_progress cells → full swarm context
102
+ - **MEDIUM confidence:** Open subtasks, unclosed epics → full swarm context
103
+ - **LOW confidence:** Any cells exist → fallback detection prompt
104
+
105
+ Philosophy: Err on the side of continuation. A false positive costs context space. A false negative loses the swarm.
106
+
107
+ ### Patch Changes
108
+
109
+ - Updated dependencies [[`a2ff1f4`](https://github.com/joelhooks/swarm-tools/commit/a2ff1f4257a2e9857f63abe4e9b941a573f44380)]:
110
+ - swarm-mail@0.4.0
111
+
3
112
  ## 0.28.2
4
113
 
5
114
  ### Patch Changes
@@ -0,0 +1,163 @@
1
+ #!/usr/bin/env bun
2
+ /**
3
+ * Tests for swarm CLI file operation helpers
4
+ *
5
+ * These tests verify the verbose output helpers used in `swarm setup`:
6
+ * - writeFileWithStatus: logs created/updated/unchanged status
7
+ * - mkdirWithStatus: logs directory creation
8
+ * - rmWithStatus: logs file removal
9
+ */
10
+ import { describe, test, expect, beforeEach, afterEach } from "bun:test";
11
+ import { mkdirSync, rmSync, writeFileSync, existsSync, readFileSync } from "fs";
12
+ import { join } from "path";
13
+ import { tmpdir } from "os";
14
+
15
+ type FileStatus = "created" | "updated" | "unchanged";
16
+
17
+ /**
18
+ * Mock logger for testing (matches @clack/prompts API)
19
+ */
20
+ class MockLogger {
21
+ logs: Array<{ type: string; message: string }> = [];
22
+
23
+ success(msg: string) {
24
+ this.logs.push({ type: "success", message: msg });
25
+ }
26
+
27
+ message(msg: string) {
28
+ this.logs.push({ type: "message", message: msg });
29
+ }
30
+
31
+ reset() {
32
+ this.logs = [];
33
+ }
34
+ }
35
+
36
+ describe("File operation helpers", () => {
37
+ let testDir: string;
38
+ let logger: MockLogger;
39
+
40
+ beforeEach(() => {
41
+ testDir = join(tmpdir(), `swarm-test-${Date.now()}`);
42
+ mkdirSync(testDir, { recursive: true });
43
+ logger = new MockLogger();
44
+ });
45
+
46
+ afterEach(() => {
47
+ if (existsSync(testDir)) {
48
+ rmSync(testDir, { recursive: true, force: true });
49
+ }
50
+ });
51
+
52
+ describe("writeFileWithStatus", () => {
53
+ // Helper that mimics the implementation
54
+ function writeFileWithStatus(path: string, content: string, label: string): FileStatus {
55
+ const exists = existsSync(path);
56
+
57
+ if (exists) {
58
+ const current = readFileSync(path, "utf-8");
59
+ if (current === content) {
60
+ logger.message(` ${label}: ${path} (unchanged)`);
61
+ return "unchanged";
62
+ }
63
+ }
64
+
65
+ writeFileSync(path, content);
66
+ const status: FileStatus = exists ? "updated" : "created";
67
+ logger.success(`${label}: ${path} (${status})`);
68
+ return status;
69
+ }
70
+
71
+ test("returns 'created' for new file", () => {
72
+ const filePath = join(testDir, "new.txt");
73
+ const result = writeFileWithStatus(filePath, "content", "Test");
74
+
75
+ expect(result).toBe("created");
76
+ expect(logger.logs[0].type).toBe("success");
77
+ expect(logger.logs[0].message).toContain("(created)");
78
+ expect(existsSync(filePath)).toBe(true);
79
+ });
80
+
81
+ test("returns 'unchanged' if content is same", () => {
82
+ const filePath = join(testDir, "existing.txt");
83
+ writeFileSync(filePath, "same content");
84
+
85
+ const result = writeFileWithStatus(filePath, "same content", "Test");
86
+
87
+ expect(result).toBe("unchanged");
88
+ expect(logger.logs[0].type).toBe("message");
89
+ expect(logger.logs[0].message).toContain("(unchanged)");
90
+ });
91
+
92
+ test("returns 'updated' if content differs", () => {
93
+ const filePath = join(testDir, "existing.txt");
94
+ writeFileSync(filePath, "old content");
95
+
96
+ const result = writeFileWithStatus(filePath, "new content", "Test");
97
+
98
+ expect(result).toBe("updated");
99
+ expect(logger.logs[0].type).toBe("success");
100
+ expect(logger.logs[0].message).toContain("(updated)");
101
+ expect(readFileSync(filePath, "utf-8")).toBe("new content");
102
+ });
103
+ });
104
+
105
+ describe("mkdirWithStatus", () => {
106
+ function mkdirWithStatus(path: string): boolean {
107
+ if (!existsSync(path)) {
108
+ mkdirSync(path, { recursive: true });
109
+ logger.message(` Created directory: ${path}`);
110
+ return true;
111
+ }
112
+ return false;
113
+ }
114
+
115
+ test("creates directory and logs when it doesn't exist", () => {
116
+ const dirPath = join(testDir, "newdir");
117
+ const result = mkdirWithStatus(dirPath);
118
+
119
+ expect(result).toBe(true);
120
+ expect(existsSync(dirPath)).toBe(true);
121
+ expect(logger.logs[0].type).toBe("message");
122
+ expect(logger.logs[0].message).toContain("Created directory");
123
+ });
124
+
125
+ test("returns false when directory already exists", () => {
126
+ const dirPath = join(testDir, "existing");
127
+ mkdirSync(dirPath);
128
+
129
+ const result = mkdirWithStatus(dirPath);
130
+
131
+ expect(result).toBe(false);
132
+ expect(logger.logs.length).toBe(0);
133
+ });
134
+ });
135
+
136
+ describe("rmWithStatus", () => {
137
+ function rmWithStatus(path: string, label: string): void {
138
+ if (existsSync(path)) {
139
+ rmSync(path);
140
+ logger.message(` Removed ${label}: ${path}`);
141
+ }
142
+ }
143
+
144
+ test("removes file and logs when it exists", () => {
145
+ const filePath = join(testDir, "todelete.txt");
146
+ writeFileSync(filePath, "content");
147
+
148
+ rmWithStatus(filePath, "test file");
149
+
150
+ expect(existsSync(filePath)).toBe(false);
151
+ expect(logger.logs[0].type).toBe("message");
152
+ expect(logger.logs[0].message).toContain("Removed test file");
153
+ });
154
+
155
+ test("does nothing when file doesn't exist", () => {
156
+ const filePath = join(testDir, "nonexistent.txt");
157
+
158
+ rmWithStatus(filePath, "test file");
159
+
160
+ expect(logger.logs.length).toBe(0);
161
+ });
162
+ });
163
+ });
package/bin/swarm.ts CHANGED
@@ -32,6 +32,8 @@ import {
32
32
  migrateBeadsToHive,
33
33
  mergeHistoricBeads,
34
34
  importJsonlToPGLite,
35
+ ensureHiveDirectory,
36
+ getHiveAdapter,
35
37
  } from "../src/hive";
36
38
 
37
39
  const __dirname = dirname(fileURLToPath(import.meta.url));
@@ -71,6 +73,68 @@ const magenta = (s: string) => `\x1b[35m${s}\x1b[0m`;
71
73
 
72
74
  const PACKAGE_NAME = "opencode-swarm-plugin";
73
75
 
76
+ // ============================================================================
77
+ // File Operation Helpers
78
+ // ============================================================================
79
+
80
+ type FileStatus = "created" | "updated" | "unchanged";
81
+
82
+ interface FileStats {
83
+ created: number;
84
+ updated: number;
85
+ unchanged: number;
86
+ }
87
+
88
+ /**
89
+ * Write a file with status logging (created/updated/unchanged)
90
+ * @param path - File path to write
91
+ * @param content - Content to write
92
+ * @param label - Label for logging (e.g., "Plugin", "Command")
93
+ * @returns Status of the operation
94
+ */
95
+ function writeFileWithStatus(path: string, content: string, label: string): FileStatus {
96
+ const exists = existsSync(path);
97
+
98
+ if (exists) {
99
+ const current = readFileSync(path, "utf-8");
100
+ if (current === content) {
101
+ p.log.message(dim(` ${label}: ${path} (unchanged)`));
102
+ return "unchanged";
103
+ }
104
+ }
105
+
106
+ writeFileSync(path, content);
107
+ const status: FileStatus = exists ? "updated" : "created";
108
+ p.log.success(`${label}: ${path} (${status})`);
109
+ return status;
110
+ }
111
+
112
+ /**
113
+ * Create a directory with logging
114
+ * @param path - Directory path to create
115
+ * @returns true if created, false if already exists
116
+ */
117
+ function mkdirWithStatus(path: string): boolean {
118
+ if (!existsSync(path)) {
119
+ mkdirSync(path, { recursive: true });
120
+ p.log.message(dim(` Created directory: ${path}`));
121
+ return true;
122
+ }
123
+ return false;
124
+ }
125
+
126
+ /**
127
+ * Remove a file with logging
128
+ * @param path - File path to remove
129
+ * @param label - Label for logging
130
+ */
131
+ function rmWithStatus(path: string, label: string): void {
132
+ if (existsSync(path)) {
133
+ rmSync(path);
134
+ p.log.message(dim(` Removed ${label}: ${path}`));
135
+ }
136
+ }
137
+
74
138
  // ============================================================================
75
139
  // Seasonal Messages (inspired by Astro's Houston)
76
140
  // ============================================================================
@@ -1707,39 +1771,29 @@ async function setup() {
1707
1771
 
1708
1772
  p.log.step("Setting up OpenCode integration...");
1709
1773
 
1774
+ // Track file operation statistics
1775
+ const stats: FileStats = { created: 0, updated: 0, unchanged: 0 };
1776
+
1710
1777
  // Create directories if needed
1711
1778
  const skillsDir = join(configDir, "skills");
1712
1779
  for (const dir of [pluginDir, commandDir, agentDir, swarmAgentDir, skillsDir]) {
1713
- if (!existsSync(dir)) {
1714
- mkdirSync(dir, { recursive: true });
1715
- }
1780
+ mkdirWithStatus(dir);
1716
1781
  }
1717
1782
 
1718
- writeFileSync(pluginPath, getPluginWrapper());
1719
- p.log.success("Plugin: " + pluginPath);
1720
-
1721
- writeFileSync(commandPath, SWARM_COMMAND);
1722
- p.log.success("Command: " + commandPath);
1783
+ // Write plugin and command files
1784
+ stats[writeFileWithStatus(pluginPath, getPluginWrapper(), "Plugin")]++;
1785
+ stats[writeFileWithStatus(commandPath, SWARM_COMMAND, "Command")]++;
1723
1786
 
1724
1787
  // Write nested agent files (swarm/planner.md, swarm/worker.md)
1725
1788
  // This is the format used by Task(subagent_type="swarm/worker")
1726
- writeFileSync(plannerAgentPath, getPlannerAgent(coordinatorModel as string));
1727
- p.log.success("Planner agent: " + plannerAgentPath);
1728
-
1729
- writeFileSync(workerAgentPath, getWorkerAgent(workerModel as string));
1730
- p.log.success("Worker agent: " + workerAgentPath);
1789
+ stats[writeFileWithStatus(plannerAgentPath, getPlannerAgent(coordinatorModel as string), "Planner agent")]++;
1790
+ stats[writeFileWithStatus(workerAgentPath, getWorkerAgent(workerModel as string), "Worker agent")]++;
1731
1791
 
1732
1792
  // Clean up legacy flat agent files if they exist
1733
- if (existsSync(legacyPlannerPath)) {
1734
- rmSync(legacyPlannerPath);
1735
- p.log.message(dim(" Removed legacy: " + legacyPlannerPath));
1736
- }
1737
- if (existsSync(legacyWorkerPath)) {
1738
- rmSync(legacyWorkerPath);
1739
- p.log.message(dim(" Removed legacy: " + legacyWorkerPath));
1740
- }
1793
+ rmWithStatus(legacyPlannerPath, "legacy planner");
1794
+ rmWithStatus(legacyWorkerPath, "legacy worker");
1741
1795
 
1742
- p.log.success("Skills directory: " + skillsDir);
1796
+ p.log.message(dim(` Skills directory: ${skillsDir}`));
1743
1797
 
1744
1798
  // Show bundled skills info (and optionally sync to global skills dir)
1745
1799
  const bundledSkillsPath = join(__dirname, "..", "global-skills");
@@ -1850,17 +1904,29 @@ async function setup() {
1850
1904
  }
1851
1905
  }
1852
1906
 
1907
+ // Show setup summary
1908
+ const totalFiles = stats.created + stats.updated + stats.unchanged;
1909
+ const summaryParts: string[] = [];
1910
+ if (stats.created > 0) summaryParts.push(`${stats.created} created`);
1911
+ if (stats.updated > 0) summaryParts.push(`${stats.updated} updated`);
1912
+ if (stats.unchanged > 0) summaryParts.push(`${stats.unchanged} unchanged`);
1913
+
1914
+ p.log.message("");
1915
+ p.log.success(`Setup complete: ${totalFiles} files (${summaryParts.join(", ")})`);
1916
+
1853
1917
  p.note(
1854
- 'cd your-project\nbd init\nopencode\n/swarm "your task"\n\nSkills: Use skills_list to see available skills',
1918
+ 'cd your-project\nswarm init\nopencode\n/swarm "your task"\n\nSkills: Use skills_list to see available skills',
1855
1919
  "Next steps",
1856
1920
  );
1857
1921
 
1858
- p.outro("Setup complete! Run 'swarm doctor' to verify.");
1922
+ p.outro("Run 'swarm doctor' to verify installation.");
1859
1923
  }
1860
1924
 
1861
1925
  async function init() {
1862
1926
  p.intro("swarm init v" + VERSION);
1863
1927
 
1928
+ const projectPath = process.cwd();
1929
+
1864
1930
  const gitDir = existsSync(".git");
1865
1931
  if (!gitDir) {
1866
1932
  p.log.error("Not in a git repository");
@@ -1869,12 +1935,15 @@ async function init() {
1869
1935
  process.exit(1);
1870
1936
  }
1871
1937
 
1938
+ // Check for existing .hive or .beads directories
1939
+ const hiveDir = existsSync(".hive");
1872
1940
  const beadsDir = existsSync(".beads");
1873
- if (beadsDir) {
1874
- p.log.warn("Beads already initialized in this project");
1941
+
1942
+ if (hiveDir) {
1943
+ p.log.warn("Hive already initialized in this project (.hive/ exists)");
1875
1944
 
1876
1945
  const reinit = await p.confirm({
1877
- message: "Re-initialize beads?",
1946
+ message: "Continue anyway?",
1878
1947
  initialValue: false,
1879
1948
  });
1880
1949
 
@@ -1882,25 +1951,54 @@ async function init() {
1882
1951
  p.outro("Aborted");
1883
1952
  process.exit(0);
1884
1953
  }
1954
+ } else if (beadsDir) {
1955
+ // Offer migration from .beads to .hive
1956
+ p.log.warn("Found legacy .beads/ directory");
1957
+
1958
+ const migrate = await p.confirm({
1959
+ message: "Migrate .beads/ to .hive/?",
1960
+ initialValue: true,
1961
+ });
1962
+
1963
+ if (!p.isCancel(migrate) && migrate) {
1964
+ const s = p.spinner();
1965
+ s.start("Migrating .beads/ to .hive/...");
1966
+
1967
+ const result = await migrateBeadsToHive(projectPath);
1968
+
1969
+ if (result.migrated) {
1970
+ s.stop("Migration complete");
1971
+ p.log.success("Renamed .beads/ to .hive/");
1972
+
1973
+ // Merge historic beads if beads.base.jsonl exists
1974
+ const mergeResult = await mergeHistoricBeads(projectPath);
1975
+ if (mergeResult.merged > 0) {
1976
+ p.log.success(`Merged ${mergeResult.merged} historic cells`);
1977
+ }
1978
+ } else {
1979
+ s.stop("Migration skipped: " + result.reason);
1980
+ }
1981
+ }
1885
1982
  }
1886
1983
 
1887
1984
  const s = p.spinner();
1888
- s.start("Initializing beads...");
1889
-
1890
- const success = await runInstall("bd init");
1985
+ s.start("Initializing hive...");
1891
1986
 
1892
- if (success) {
1893
- s.stop("Beads initialized");
1894
- p.log.success("Created .beads/ directory");
1987
+ try {
1988
+ // Create .hive directory using our function (no bd CLI needed)
1989
+ ensureHiveDirectory(projectPath);
1990
+
1991
+ s.stop("Hive initialized");
1992
+ p.log.success("Created .hive/ directory");
1895
1993
 
1896
- const createBead = await p.confirm({
1897
- message: "Create your first bead?",
1994
+ const createCell = await p.confirm({
1995
+ message: "Create your first cell?",
1898
1996
  initialValue: true,
1899
1997
  });
1900
1998
 
1901
- if (!p.isCancel(createBead) && createBead) {
1999
+ if (!p.isCancel(createCell) && createCell) {
1902
2000
  const title = await p.text({
1903
- message: "Bead title:",
2001
+ message: "Cell title:",
1904
2002
  placeholder: "Implement user authentication",
1905
2003
  validate: (v) => (v.length === 0 ? "Title required" : undefined),
1906
2004
  });
@@ -1917,17 +2015,22 @@ async function init() {
1917
2015
  });
1918
2016
 
1919
2017
  if (!p.isCancel(typeResult)) {
1920
- const beadSpinner = p.spinner();
1921
- beadSpinner.start("Creating bead...");
1922
-
1923
- const createSuccess = await runInstall(
1924
- 'bd create --title "' + title + '" --type ' + typeResult,
1925
- );
1926
-
1927
- if (createSuccess) {
1928
- beadSpinner.stop("Bead created");
1929
- } else {
1930
- beadSpinner.stop("Failed to create bead");
2018
+ const cellSpinner = p.spinner();
2019
+ cellSpinner.start("Creating cell...");
2020
+
2021
+ try {
2022
+ // Use HiveAdapter to create the cell (no bd CLI needed)
2023
+ const adapter = await getHiveAdapter(projectPath);
2024
+ const cell = await adapter.createCell(projectPath, {
2025
+ title: title as string,
2026
+ type: typeResult as "feature" | "bug" | "task" | "chore",
2027
+ priority: 2,
2028
+ });
2029
+
2030
+ cellSpinner.stop("Cell created: " + cell.id);
2031
+ } catch (error) {
2032
+ cellSpinner.stop("Failed to create cell");
2033
+ p.log.error(error instanceof Error ? error.message : String(error));
1931
2034
  }
1932
2035
  }
1933
2036
  }
@@ -1953,9 +2056,9 @@ async function init() {
1953
2056
  }
1954
2057
 
1955
2058
  p.outro("Project initialized! Use '/swarm' in OpenCode to get started.");
1956
- } else {
1957
- s.stop("Failed to initialize beads");
1958
- p.log.error("Make sure 'bd' is installed: swarm doctor");
2059
+ } catch (error) {
2060
+ s.stop("Failed to initialize hive");
2061
+ p.log.error(error instanceof Error ? error.message : String(error));
1959
2062
  p.outro("Aborted");
1960
2063
  process.exit(1);
1961
2064
  }
@@ -5,6 +5,12 @@
5
5
  * When context is compacted, this hook injects instructions for the summarizer
6
6
  * to preserve swarm coordination state and enable seamless resumption.
7
7
  *
8
+ * ## Philosophy: Err on the Side of Continuation
9
+ *
10
+ * It's better to inject swarm context unnecessarily than to lose an active swarm.
11
+ * The cost of a false positive (extra context) is low.
12
+ * The cost of a false negative (lost swarm) is high - wasted work, confused agents.
13
+ *
8
14
  * Hook signature (from @opencode-ai/plugin):
9
15
  * ```typescript
10
16
  * "experimental.session.compacting"?: (
@@ -33,11 +39,23 @@
33
39
  * autonomously after context compression.
34
40
  */
35
41
  export declare const SWARM_COMPACTION_CONTEXT = "## \uD83D\uDC1D SWARM ACTIVE - Keep Cooking\n\nYou are the **COORDINATOR** of an active swarm. Context was compacted but the swarm is still running.\n\n**YOUR JOB:** Keep orchestrating. Spawn agents. Monitor progress. Unblock work. Ship it.\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:** <bd-xxx> - <title>\n**Project:** <path>\n**Progress:** X/Y subtasks complete\n\n**Active:**\n- <bd-xxx>: <title> [in_progress] \u2192 <agent> working on <files>\n\n**Blocked:**\n- <bd-xxx>: <title> - BLOCKED: <reason>\n\n**Completed:**\n- <bd-xxx>: <title> \u2713\n\n**Ready to Spawn:**\n- <bd-xxx>: <title> (files: <...>)\n```\n\n### On Resume - IMMEDIATELY\n\n1. `swarm_status(epic_id=\"<epic>\", project_key=\"<path>\")` - Get current state\n2. `swarmmail_inbox(limit=5)` - Check for agent messages\n3. **Spawn ready subtasks** - Don't wait, fire them off\n4. **Unblock blocked work** - Resolve dependencies, reassign if needed\n5. **Collect completed work** - Close done subtasks, verify quality\n\n### Keep the Swarm Cooking\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- **Close the loop** - When all subtasks done, verify and close the epic\n- **Don't stop** - The swarm runs until the epic is closed\n\n**You are not waiting for instructions. You are the coordinator. Coordinate.**\n";
42
+ /**
43
+ * Fallback detection prompt - tells the compactor what to look for
44
+ *
45
+ * Used when we can't definitively detect a swarm but want to be safe.
46
+ * The compactor can check the conversation context for these patterns.
47
+ */
48
+ export declare const SWARM_DETECTION_FALLBACK = "## \uD83D\uDC1D Swarm Detection - Check Your Context\n\n**IMPORTANT:** Before summarizing, check if this session involves an active swarm.\n\nLook for ANY of these patterns in the conversation:\n\n### Tool Calls (definite swarm sign)\n- `swarm_decompose`, `swarm_spawn_subtask`, `swarm_status`, `swarm_complete`\n- `swarmmail_init`, `swarmmail_reserve`, `swarmmail_send`\n- `hive_create_epic`, `hive_start`, `hive_close`\n\n### IDs and Names\n- Cell IDs: `bd-xxx`, `bd-xxx.N` (subtask format)\n- Agent names: BlueLake, RedMountain, GreenValley, etc.\n- Epic references: \"epic\", \"subtask\", \"parent\"\n\n### Coordination Language\n- \"spawn\", \"worker\", \"coordinator\"\n- \"reserve\", \"reservation\", \"files\"\n- \"blocked\", \"unblock\", \"dependency\"\n- \"progress\", \"complete\", \"in_progress\"\n\n### If You Find Swarm Evidence\n\nInclude this in your summary:\n1. Epic ID and title\n2. Project path\n3. Subtask status (running/blocked/done/pending)\n4. Any blockers or issues\n5. What should happen next\n\n**Then tell the resumed session:**\n\"This is an active swarm. Check swarm_status and swarmmail_inbox immediately.\"\n";
36
49
  /**
37
50
  * Create the compaction hook for use in plugin registration
38
51
  *
39
- * Only injects swarm context if there's an active swarm (in-progress beads).
40
- * This keeps the coordinator cooking after compaction.
52
+ * Injects swarm context based on detection confidence:
53
+ * - HIGH/MEDIUM: Full swarm context (definitely/probably a swarm)
54
+ * - LOW: Fallback detection prompt (let compactor check context)
55
+ * - NONE: No injection (probably not a swarm)
56
+ *
57
+ * Philosophy: Err on the side of continuation. A false positive costs
58
+ * a bit of context space. A false negative loses the swarm.
41
59
  *
42
60
  * @example
43
61
  * ```typescript
@@ -1 +1 @@
1
- {"version":3,"file":"compaction-hook.d.ts","sourceRoot":"","sources":["../src/compaction-hook.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAMH;;;;;;;;;GASG;AACH,eAAO,MAAM,wBAAwB,2wDAsDpC,CAAC;AAyCF;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,oBAAoB,KAEhC,QAAQ;IAAE,SAAS,EAAE,MAAM,CAAA;CAAE,EAC7B,QAAQ;IAAE,OAAO,EAAE,MAAM,EAAE,CAAA;CAAE,KAC5B,OAAO,CAAC,IAAI,CAAC,CAMjB"}
1
+ {"version":3,"file":"compaction-hook.d.ts","sourceRoot":"","sources":["../src/compaction-hook.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AASH;;;;;;;;;GASG;AACH,eAAO,MAAM,wBAAwB,2wDAsDpC,CAAC;AAEF;;;;;GAKG;AACH,eAAO,MAAM,wBAAwB,0nCAiCpC,CAAC;AAoIF;;;;;;;;;;;;;;;;;;;;GAoBG;AACH,wBAAgB,oBAAoB,KAEhC,QAAQ;IAAE,SAAS,EAAE,MAAM,CAAA;CAAE,EAC7B,QAAQ;IAAE,OAAO,EAAE,MAAM,EAAE,CAAA;CAAE,KAC5B,OAAO,CAAC,IAAI,CAAC,CAcjB"}
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}