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.
- package/.turbo/turbo-build.log +2 -2
- package/CHANGELOG.md +109 -0
- package/bin/swarm.test.ts +163 -0
- package/bin/swarm.ts +154 -51
- package/dist/compaction-hook.d.ts +20 -2
- package/dist/compaction-hook.d.ts.map +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +174 -74
- package/dist/plugin.js +63 -65
- package/dist/swarm-orchestrate.d.ts.map +1 -1
- package/dist/tool-availability.d.ts.map +1 -1
- package/examples/plugin-wrapper-template.ts +157 -28
- package/package.json +2 -2
- package/src/compaction-hook.test.ts +110 -0
- package/src/compaction-hook.ts +183 -29
- package/src/index.ts +3 -5
- package/src/learning.integration.test.ts +11 -3
- package/src/swarm-orchestrate.ts +76 -79
- package/src/swarm.integration.test.ts +88 -2
- package/src/tool-availability.ts +6 -24
package/.turbo/turbo-build.log
CHANGED
|
@@ -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
|
|
2
|
+
Bundled 200 modules in 34ms
|
|
3
3
|
|
|
4
4
|
index.js 1.20 MB (entry point)
|
|
5
5
|
|
|
6
|
-
Bundled 201 modules in
|
|
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
|
-
|
|
1714
|
-
mkdirSync(dir, { recursive: true });
|
|
1715
|
-
}
|
|
1780
|
+
mkdirWithStatus(dir);
|
|
1716
1781
|
}
|
|
1717
1782
|
|
|
1718
|
-
|
|
1719
|
-
|
|
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
|
-
|
|
1727
|
-
|
|
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
|
-
|
|
1734
|
-
|
|
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.
|
|
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\
|
|
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("
|
|
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
|
-
|
|
1874
|
-
|
|
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: "
|
|
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
|
|
1889
|
-
|
|
1890
|
-
const success = await runInstall("bd init");
|
|
1985
|
+
s.start("Initializing hive...");
|
|
1891
1986
|
|
|
1892
|
-
|
|
1893
|
-
|
|
1894
|
-
|
|
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
|
|
1897
|
-
message: "Create your first
|
|
1994
|
+
const createCell = await p.confirm({
|
|
1995
|
+
message: "Create your first cell?",
|
|
1898
1996
|
initialValue: true,
|
|
1899
1997
|
});
|
|
1900
1998
|
|
|
1901
|
-
if (!p.isCancel(
|
|
1999
|
+
if (!p.isCancel(createCell) && createCell) {
|
|
1902
2000
|
const title = await p.text({
|
|
1903
|
-
message: "
|
|
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
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
1925
|
-
|
|
1926
|
-
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
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
|
-
}
|
|
1957
|
-
s.stop("Failed to initialize
|
|
1958
|
-
p.log.error(
|
|
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
|
-
*
|
|
40
|
-
*
|
|
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
|
|
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"}
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AACH,OAAO,KAAK,EAAE,MAAM,EAAsB,MAAM,qBAAqB,CAAC;AAoCtE;;;;;;;;;;;;;;;;GAgBG;AACH,eAAO,MAAM,WAAW,EAAE,
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|