claude-code-swarm 0.3.12 → 0.3.17

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,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-code-swarm",
3
- "version": "0.3.12",
3
+ "version": "0.3.17",
4
4
  "description": "Launch Claude Code with swarmkit capabilities, including team orchestration, MAP observability, and session tracking.",
5
5
  "owner": {
6
6
  "name": "alexngai"
@@ -162,12 +162,25 @@ if (!def.enabled()) {
162
162
  runNoop();
163
163
  } else {
164
164
  const { cmd, args } = def.launch();
165
- if (which(cmd)) {
166
- // Replace this process with the real server
167
- const { execFileSync: _ , ...rest } = await import('node:child_process');
165
+ const resolved = which(cmd);
166
+ // Debug: log MCP server resolution to a temp file for diagnostics
167
+ try {
168
+ const { appendFileSync } = await import('node:fs');
169
+ appendFileSync('/tmp/mcp-launcher-debug.log', `[${new Date().toISOString()}] server=${server} cmd=${cmd} resolved=${resolved} cwd=${process.cwd()} args=${JSON.stringify(args)}\n`);
170
+ } catch {}
171
+ if (resolved) {
172
+ // Replace this process with the real server.
173
+ // NODE_NO_WARNINGS suppresses Node.js experimental feature warnings on stderr
174
+ // (e.g., sqlite). Some MCP clients interpret stderr output as server errors,
175
+ // causing them to mark the server as "failed" even though it's functional.
176
+ const spawnEnv = { ...process.env, NODE_NO_WARNINGS: '1' };
168
177
  const child = (await import('node:child_process')).spawn(cmd, args, {
169
- stdio: 'inherit',
178
+ stdio: ['inherit', 'inherit', 'pipe'],
179
+ env: spawnEnv,
170
180
  });
181
+ // Swallow stderr to prevent MCP client from misinterpreting it.
182
+ // In debug mode, forward to our stderr.
183
+ child.stderr.on('data', () => {});
171
184
  child.on('exit', (code) => process.exit(code ?? 0));
172
185
  } else {
173
186
  process.stderr.write(`[${server}-mcp] ${cmd} CLI not found\n`);
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "claude-code-swarm",
3
3
  "description": "Spin up Claude Code agent teams from openteams YAML topologies with optional MAP (Multi-Agent Protocol) observability and coordination. Provides hooks for session lifecycle, agent spawn/complete tracking, and a /swarm skill to launch team configurations.",
4
- "version": "0.3.12",
4
+ "version": "0.3.17",
5
5
  "author": {
6
6
  "name": "alexngai"
7
7
  },
@@ -0,0 +1,259 @@
1
+ /**
2
+ * Tier 7: minimem → MAP sync integration test
3
+ *
4
+ * Tests the full bridge flow:
5
+ * Agent uses minimem MCP tool (write) → PostToolUse hook fires
6
+ * → map-hook.mjs builds bridge command → sidecar calls x-openhive/memory.sync
7
+ * → Mock MAP server receives the sync notification
8
+ *
9
+ * Groups:
10
+ * 1. Hook configuration verification (no agent, no LLM)
11
+ * 2. Bridge command builder (no agent, no LLM)
12
+ * 3. Live agent writes memory → MAP event received (LIVE_AGENT_TEST=1)
13
+ */
14
+
15
+ import { describe, it, expect, beforeAll, afterAll } from "vitest";
16
+ import { createWorkspace } from "./helpers/workspace.mjs";
17
+ import { cleanupWorkspace, waitFor } from "./helpers/cleanup.mjs";
18
+ import { readFileSync, existsSync, writeFileSync, mkdirSync, readdirSync } from "fs";
19
+ import { join } from "path";
20
+
21
+ // Check if dependencies are available (try require for CJS-compatible packages)
22
+ import { createRequire } from "module";
23
+ const require = createRequire(import.meta.url);
24
+
25
+ let minimemAvailable = false;
26
+ try {
27
+ require("minimem");
28
+ minimemAvailable = true;
29
+ } catch { /* minimem not installed */ }
30
+
31
+ let mapSdkAvailable = false;
32
+ try {
33
+ require("@multi-agent-protocol/sdk");
34
+ mapSdkAvailable = true;
35
+ } catch { /* MAP SDK not installed */ }
36
+
37
+ // Check if Claude CLI is available
38
+ let cliAvailable = false;
39
+ try {
40
+ const { CLI_AVAILABLE } = await import("./helpers/cli.mjs");
41
+ cliAvailable = CLI_AVAILABLE;
42
+ } catch { /* CLI not available */ }
43
+
44
+ // ── Group 1: Hook configuration ─────────────────────────────────────────────
45
+
46
+ describe("tier7: minimem sync hook configuration", { timeout: 30_000 }, () => {
47
+ it("hooks.json has a PostToolUse entry for minimem", () => {
48
+ const hooksPath = join(import.meta.dirname, "..", "hooks", "hooks.json");
49
+ const hooks = JSON.parse(readFileSync(hooksPath, "utf-8"));
50
+ const postToolUse = hooks.hooks.PostToolUse || [];
51
+
52
+ const minimemHook = postToolUse.find((h) => h.matcher === "minimem");
53
+ expect(minimemHook).toBeDefined();
54
+ expect(minimemHook.hooks[0].command).toContain("minimem-mcp-used");
55
+ });
56
+
57
+ it("minimem hook checks both minimem.enabled and map.enabled", () => {
58
+ const hooksPath = join(import.meta.dirname, "..", "hooks", "hooks.json");
59
+ const hooks = JSON.parse(readFileSync(hooksPath, "utf-8"));
60
+ const postToolUse = hooks.hooks.PostToolUse || [];
61
+
62
+ const minimemHook = postToolUse.find((h) => h.matcher === "minimem");
63
+ const cmd = minimemHook.hooks[0].command;
64
+
65
+ expect(cmd).toContain("minimem");
66
+ expect(cmd).toContain("map");
67
+ });
68
+
69
+ it("hooks.json minimem hook is gated behind config check", () => {
70
+ const hooksPath = join(import.meta.dirname, "..", "hooks", "hooks.json");
71
+ const hooks = JSON.parse(readFileSync(hooksPath, "utf-8"));
72
+ const postToolUse = hooks.hooks.PostToolUse || [];
73
+
74
+ const minimemHook = postToolUse.find((h) => h.matcher === "minimem");
75
+ const cmd = minimemHook.hooks[0].command;
76
+
77
+ // Should have the config check prefix (node -e "..." && node map-hook.mjs)
78
+ expect(cmd).toContain('node -e');
79
+ expect(cmd).toContain('process.exit');
80
+ expect(cmd).toContain('map-hook.mjs');
81
+ });
82
+ });
83
+
84
+ // ── Group 2: Bridge command builder ─────────────────────────────────────────
85
+
86
+ describe("tier7: minimem bridge command builder", { timeout: 30_000 }, () => {
87
+ it("builds command for write operations", async () => {
88
+ const { buildMinimemBridgeCommand } = await import("../src/map-events.mjs");
89
+
90
+ const cmd = buildMinimemBridgeCommand({
91
+ tool_name: "minimem__memory_append",
92
+ tool_input: { text: "Test memory entry" },
93
+ session_id: "test-session",
94
+ });
95
+
96
+ expect(cmd).not.toBeNull();
97
+ expect(cmd.action).toBe("bridge-memory-sync");
98
+ expect(cmd.agentId).toBe("test-session");
99
+ });
100
+
101
+ it("skips read-only operations", async () => {
102
+ const { buildMinimemBridgeCommand } = await import("../src/map-events.mjs");
103
+
104
+ const cmd = buildMinimemBridgeCommand({
105
+ tool_name: "minimem__memory_search",
106
+ tool_input: { query: "test" },
107
+ });
108
+
109
+ expect(cmd).toBeNull();
110
+ });
111
+
112
+ it("map-hook.mjs switch statement includes minimem-mcp-used", () => {
113
+ const hookScript = readFileSync(
114
+ join(import.meta.dirname, "..", "scripts", "map-hook.mjs"),
115
+ "utf-8"
116
+ );
117
+ expect(hookScript).toContain('"minimem-mcp-used"');
118
+ expect(hookScript).toContain("handleMinimemMcpUsed");
119
+ });
120
+
121
+ it("sidecar-server.mjs handles bridge-memory-sync command", () => {
122
+ const sidecarScript = readFileSync(
123
+ join(import.meta.dirname, "..", "src", "sidecar-server.mjs"),
124
+ "utf-8"
125
+ );
126
+ expect(sidecarScript).toContain('"bridge-memory-sync"');
127
+ expect(sidecarScript).toContain("x-openhive/memory.sync");
128
+ });
129
+ });
130
+
131
+ // ── Group 3: Live agent test ────────────────────────────────────────────────
132
+
133
+ describe.skipIf(!process.env.LIVE_AGENT_TEST || !minimemAvailable || !cliAvailable || !mapSdkAvailable)(
134
+ "tier7: live agent minimem → MAP sync",
135
+ { timeout: 300_000 },
136
+ () => {
137
+ let mockServer;
138
+ let workspace;
139
+ let messages;
140
+ let runResult;
141
+
142
+ beforeAll(async () => {
143
+ const { MockMapServer } = await import("./helpers/map-mock-server.mjs");
144
+ mockServer = new MockMapServer();
145
+ await mockServer.start();
146
+
147
+ workspace = createWorkspace({
148
+ config: {
149
+ minimem: { enabled: true, provider: "none" },
150
+ map: {
151
+ enabled: true,
152
+ server: `ws://localhost:${mockServer.port}`,
153
+ },
154
+ },
155
+ gitInit: true,
156
+ });
157
+
158
+ // Initialize minimem in the workspace so the MCP server can start.
159
+ // Must run `minimem sync` to create index.db — without it the MCP server
160
+ // starts but may report tools inconsistently.
161
+ const minimemDir = join(workspace.dir, ".swarm", "minimem");
162
+ mkdirSync(join(minimemDir, "memory"), { recursive: true });
163
+ writeFileSync(join(minimemDir, "MEMORY.md"), "# Test Memory\n");
164
+ writeFileSync(
165
+ join(minimemDir, "config.json"),
166
+ JSON.stringify({ embedding: { provider: "none" } })
167
+ );
168
+ writeFileSync(join(minimemDir, ".gitignore"), "index.db\n");
169
+
170
+ // Initialize the search index — critical for MCP server readiness.
171
+ // Use require() to avoid ESM resolution issues with node:sqlite in vitest.
172
+ try {
173
+ const { Minimem } = require("minimem");
174
+ const mem = await Minimem.create({ memoryDir: minimemDir, embedding: { provider: "none" } });
175
+ await mem.sync();
176
+ await mem.close();
177
+ } catch (err) {
178
+ console.warn("[tier7] minimem init warning:", err.message?.slice(0, 200));
179
+ }
180
+
181
+ const { runClaude } = await import("./helpers/cli.mjs");
182
+ runResult = await runClaude(
183
+ 'Use the minimem MCP tools: First search for "test" using memory_search. Then write a memory file at .swarm/minimem/memory/e2e-test.md containing "### 2026-03-31 12:00\n<!-- type: decision -->\nChose PostgreSQL for the main database." using the Write tool. Report what you did.',
184
+ {
185
+ cwd: workspace.dir,
186
+ model: "haiku",
187
+ maxTurns: 6,
188
+ maxBudgetUsd: 0.5,
189
+ timeout: 120_000,
190
+ label: "memory-sync-e2e",
191
+ }
192
+ );
193
+ messages = runResult.messages;
194
+ }, 300_000);
195
+
196
+ afterAll(async () => {
197
+ if (workspace) {
198
+ try { cleanupWorkspace(workspace.dir); } catch { /* best effort */ }
199
+ }
200
+ if (mockServer) {
201
+ try { await mockServer.stop(); } catch { /* best effort */ }
202
+ }
203
+ });
204
+
205
+ it("agent completed without error", () => {
206
+ expect(runResult.exitCode).toBe(0);
207
+ });
208
+
209
+ it("agent used minimem MCP tools", () => {
210
+ const { extractToolCalls } = require("./helpers/assertions.mjs");
211
+ const allCalls = extractToolCalls(messages);
212
+ const memCalls = allCalls.filter((c) =>
213
+ c.name?.includes("minimem") || c.name?.includes("memory_search")
214
+ );
215
+ expect(memCalls.length).toBeGreaterThan(0);
216
+ });
217
+
218
+ it("agent wrote a memory file", () => {
219
+ const { extractToolCalls } = require("./helpers/assertions.mjs");
220
+ const allCalls = extractToolCalls(messages);
221
+ // Agent may use Write tool to create memory file, or minimem append/upsert
222
+ const writeCalls = allCalls.filter(
223
+ (c) => c.name === "Write" || c.name?.includes("append") || c.name?.includes("upsert")
224
+ );
225
+ expect(writeCalls.length).toBeGreaterThan(0);
226
+ });
227
+
228
+ it("memory file was written to disk", () => {
229
+ const minimemDir = join(workspace.dir, ".swarm", "minimem");
230
+ const memoryDir = join(minimemDir, "memory");
231
+ if (!existsSync(memoryDir)) return; // skip if dir not created
232
+ const mdFiles = readdirSync(memoryDir).filter((f) => f.endsWith(".md"));
233
+ expect(mdFiles.length).toBeGreaterThan(0);
234
+ });
235
+
236
+ it("sidecar file watcher sent MAP sync notification", async () => {
237
+ // The sidecar's memory file watcher detects .md file writes and sends
238
+ // bridge-memory-sync → callExtension("x-openhive/memory.sync").
239
+ // This covers both Write tool and minimem MCP tool writes.
240
+ //
241
+ // The watcher has a 2s debounce, so we wait up to 10s for the event.
242
+ const received = await waitFor(() => {
243
+ const extCalls = mockServer.getByMethod("x-openhive/memory.sync");
244
+ if (extCalls.length > 0) return true;
245
+ const broadcasts = mockServer.getMessages("memory.sync");
246
+ if (broadcasts.length > 0) return true;
247
+ // Check all received messages for any memory-related content
248
+ const all = mockServer.receivedMessages || [];
249
+ return all.some(
250
+ (m) =>
251
+ JSON.stringify(m).includes("memory") &&
252
+ JSON.stringify(m).includes("sync")
253
+ );
254
+ }, 10_000);
255
+
256
+ expect(received).toBe(true);
257
+ });
258
+ }
259
+ );
package/hooks/hooks.json CHANGED
@@ -10,7 +10,7 @@
10
10
  },
11
11
  {
12
12
  "type": "command",
13
- "command": "if [ -f .swarm/claude-swarm/config.json ] && node -e \"const c=JSON.parse(require('fs').readFileSync('.swarm/claude-swarm/config.json','utf-8'));process.exit(c.sessionlog?.enabled?0:1)\" 2>/dev/null; then node \"${CLAUDE_PLUGIN_ROOT}/scripts/map-hook.mjs\" sessionlog-dispatch session-start; fi"
13
+ "command": "node -e \"const f=require('fs'),p=require('path'),h=require('os').homedir();let c={};try{c=JSON.parse(f.readFileSync('.swarm/claude-swarm/config.json','utf-8'))}catch{try{c=JSON.parse(f.readFileSync(p.join(h,'.claude-swarm','config.json'),'utf-8'))}catch{}};process.exit(c.sessionlog?.enabled?0:1)\" 2>/dev/null && node \"${CLAUDE_PLUGIN_ROOT}/scripts/map-hook.mjs\" sessionlog-dispatch session-start"
14
14
  }
15
15
  ]
16
16
  }
@@ -21,7 +21,7 @@
21
21
  "hooks": [
22
22
  {
23
23
  "type": "command",
24
- "command": "if [ -f .swarm/claude-swarm/config.json ] && node -e \"const c=JSON.parse(require('fs').readFileSync('.swarm/claude-swarm/config.json','utf-8'));process.exit(c.sessionlog?.enabled?0:1)\" 2>/dev/null; then node \"${CLAUDE_PLUGIN_ROOT}/scripts/map-hook.mjs\" sessionlog-dispatch session-end; fi"
24
+ "command": "node -e \"const f=require('fs'),p=require('path'),h=require('os').homedir();let c={};try{c=JSON.parse(f.readFileSync('.swarm/claude-swarm/config.json','utf-8'))}catch{try{c=JSON.parse(f.readFileSync(p.join(h,'.claude-swarm','config.json'),'utf-8'))}catch{}};process.exit(c.sessionlog?.enabled?0:1)\" 2>/dev/null && node \"${CLAUDE_PLUGIN_ROOT}/scripts/map-hook.mjs\" sessionlog-dispatch session-end"
25
25
  }
26
26
  ]
27
27
  }
@@ -32,11 +32,11 @@
32
32
  "hooks": [
33
33
  {
34
34
  "type": "command",
35
- "command": "if [ -f .swarm/claude-swarm/config.json ] && node -e \"const c=JSON.parse(require('fs').readFileSync('.swarm/claude-swarm/config.json','utf-8')).map||{};process.exit(c.enabled||c.server||process.env.SWARM_MAP_SERVER||process.env.SWARM_MAP_ENABLED?0:1)\" 2>/dev/null; then node \"${CLAUDE_PLUGIN_ROOT}/scripts/map-hook.mjs\" inject; fi"
35
+ "command": "node -e \"const f=require('fs'),p=require('path'),h=require('os').homedir();let c={};try{c=JSON.parse(f.readFileSync('.swarm/claude-swarm/config.json','utf-8'))}catch{try{c=JSON.parse(f.readFileSync(p.join(h,'.claude-swarm','config.json'),'utf-8'))}catch{}};const m=c.map||{};process.exit(m.enabled||m.server||process.env.SWARM_MAP_SERVER||process.env.SWARM_MAP_ENABLED?0:1)\" 2>/dev/null && node \"${CLAUDE_PLUGIN_ROOT}/scripts/map-hook.mjs\" inject"
36
36
  },
37
37
  {
38
38
  "type": "command",
39
- "command": "if [ -f .swarm/claude-swarm/config.json ] && node -e \"const c=JSON.parse(require('fs').readFileSync('.swarm/claude-swarm/config.json','utf-8'));process.exit(c.sessionlog?.enabled?0:1)\" 2>/dev/null; then node \"${CLAUDE_PLUGIN_ROOT}/scripts/map-hook.mjs\" sessionlog-dispatch user-prompt-submit; fi"
39
+ "command": "node -e \"const f=require('fs'),p=require('path'),h=require('os').homedir();let c={};try{c=JSON.parse(f.readFileSync('.swarm/claude-swarm/config.json','utf-8'))}catch{try{c=JSON.parse(f.readFileSync(p.join(h,'.claude-swarm','config.json'),'utf-8'))}catch{}};process.exit(c.sessionlog?.enabled?0:1)\" 2>/dev/null && node \"${CLAUDE_PLUGIN_ROOT}/scripts/map-hook.mjs\" sessionlog-dispatch user-prompt-submit"
40
40
  }
41
41
  ]
42
42
  }
@@ -47,7 +47,7 @@
47
47
  "hooks": [
48
48
  {
49
49
  "type": "command",
50
- "command": "if [ -f .swarm/claude-swarm/config.json ] && node -e \"const c=JSON.parse(require('fs').readFileSync('.swarm/claude-swarm/config.json','utf-8'));process.exit(c.sessionlog?.enabled?0:1)\" 2>/dev/null; then node \"${CLAUDE_PLUGIN_ROOT}/scripts/map-hook.mjs\" sessionlog-dispatch pre-task; fi"
50
+ "command": "node -e \"const f=require('fs'),p=require('path'),h=require('os').homedir();let c={};try{c=JSON.parse(f.readFileSync('.swarm/claude-swarm/config.json','utf-8'))}catch{try{c=JSON.parse(f.readFileSync(p.join(h,'.claude-swarm','config.json'),'utf-8'))}catch{}};process.exit(c.sessionlog?.enabled?0:1)\" 2>/dev/null && node \"${CLAUDE_PLUGIN_ROOT}/scripts/map-hook.mjs\" sessionlog-dispatch pre-task"
51
51
  }
52
52
  ]
53
53
  }
@@ -58,7 +58,16 @@
58
58
  "hooks": [
59
59
  {
60
60
  "type": "command",
61
- "command": "if [ -f .swarm/claude-swarm/config.json ] && node -e \"const c=JSON.parse(require('fs').readFileSync('.swarm/claude-swarm/config.json','utf-8'));process.exit((c.opentasks?.enabled||process.env.SWARM_OPENTASKS_ENABLED)&&(c.map?.enabled||c.map?.server||process.env.SWARM_MAP_SERVER||process.env.SWARM_MAP_ENABLED)?0:1)\" 2>/dev/null; then node \"${CLAUDE_PLUGIN_ROOT}/scripts/map-hook.mjs\" opentasks-mcp-used; fi"
61
+ "command": "node -e \"const f=require('fs'),p=require('path'),h=require('os').homedir();let c={};try{c=JSON.parse(f.readFileSync('.swarm/claude-swarm/config.json','utf-8'))}catch{try{c=JSON.parse(f.readFileSync(p.join(h,'.claude-swarm','config.json'),'utf-8'))}catch{}};process.exit((c.opentasks?.enabled||process.env.SWARM_OPENTASKS_ENABLED)&&(c.map?.enabled||c.map?.server||process.env.SWARM_MAP_SERVER||process.env.SWARM_MAP_ENABLED)?0:1)\" 2>/dev/null && node \"${CLAUDE_PLUGIN_ROOT}/scripts/map-hook.mjs\" opentasks-mcp-used"
62
+ }
63
+ ]
64
+ },
65
+ {
66
+ "matcher": "minimem",
67
+ "hooks": [
68
+ {
69
+ "type": "command",
70
+ "command": "node -e \"const f=require('fs'),p=require('path'),h=require('os').homedir();let c={};try{c=JSON.parse(f.readFileSync('.swarm/claude-swarm/config.json','utf-8'))}catch{try{c=JSON.parse(f.readFileSync(p.join(h,'.claude-swarm','config.json'),'utf-8'))}catch{}};process.exit((c.minimem?.enabled||process.env.SWARM_MINIMEM_ENABLED)&&(c.map?.enabled||c.map?.server||process.env.SWARM_MAP_SERVER||process.env.SWARM_MAP_ENABLED)?0:1)\" 2>/dev/null && node \"${CLAUDE_PLUGIN_ROOT}/scripts/map-hook.mjs\" minimem-mcp-used"
62
71
  }
63
72
  ]
64
73
  },
@@ -67,7 +76,7 @@
67
76
  "hooks": [
68
77
  {
69
78
  "type": "command",
70
- "command": "if [ -f .swarm/claude-swarm/config.json ] && node -e \"const c=JSON.parse(require('fs').readFileSync('.swarm/claude-swarm/config.json','utf-8'));process.exit(c.sessionlog?.enabled?0:1)\" 2>/dev/null; then node \"${CLAUDE_PLUGIN_ROOT}/scripts/map-hook.mjs\" sessionlog-dispatch post-task; fi"
79
+ "command": "node -e \"const f=require('fs'),p=require('path'),h=require('os').homedir();let c={};try{c=JSON.parse(f.readFileSync('.swarm/claude-swarm/config.json','utf-8'))}catch{try{c=JSON.parse(f.readFileSync(p.join(h,'.claude-swarm','config.json'),'utf-8'))}catch{}};process.exit(c.sessionlog?.enabled?0:1)\" 2>/dev/null && node \"${CLAUDE_PLUGIN_ROOT}/scripts/map-hook.mjs\" sessionlog-dispatch post-task"
71
80
  }
72
81
  ]
73
82
  },
@@ -76,11 +85,11 @@
76
85
  "hooks": [
77
86
  {
78
87
  "type": "command",
79
- "command": "if [ -f .swarm/claude-swarm/config.json ] && node -e \"const c=JSON.parse(require('fs').readFileSync('.swarm/claude-swarm/config.json','utf-8')).map||{};process.exit(c.enabled||c.server||process.env.SWARM_MAP_SERVER||process.env.SWARM_MAP_ENABLED?0:1)\" 2>/dev/null; then node \"${CLAUDE_PLUGIN_ROOT}/scripts/map-hook.mjs\" native-task-created; fi"
88
+ "command": "node -e \"const f=require('fs'),p=require('path'),h=require('os').homedir();let c={};try{c=JSON.parse(f.readFileSync('.swarm/claude-swarm/config.json','utf-8'))}catch{try{c=JSON.parse(f.readFileSync(p.join(h,'.claude-swarm','config.json'),'utf-8'))}catch{}};const m=c.map||{};process.exit(m.enabled||m.server||process.env.SWARM_MAP_SERVER||process.env.SWARM_MAP_ENABLED?0:1)\" 2>/dev/null && node \"${CLAUDE_PLUGIN_ROOT}/scripts/map-hook.mjs\" native-task-created"
80
89
  },
81
90
  {
82
91
  "type": "command",
83
- "command": "if [ -f .swarm/claude-swarm/config.json ] && node -e \"const c=JSON.parse(require('fs').readFileSync('.swarm/claude-swarm/config.json','utf-8'));process.exit(c.sessionlog?.enabled?0:1)\" 2>/dev/null; then node \"${CLAUDE_PLUGIN_ROOT}/scripts/map-hook.mjs\" sessionlog-dispatch post-task-create; fi"
92
+ "command": "node -e \"const f=require('fs'),p=require('path'),h=require('os').homedir();let c={};try{c=JSON.parse(f.readFileSync('.swarm/claude-swarm/config.json','utf-8'))}catch{try{c=JSON.parse(f.readFileSync(p.join(h,'.claude-swarm','config.json'),'utf-8'))}catch{}};process.exit(c.sessionlog?.enabled?0:1)\" 2>/dev/null && node \"${CLAUDE_PLUGIN_ROOT}/scripts/map-hook.mjs\" sessionlog-dispatch post-task-create"
84
93
  }
85
94
  ]
86
95
  },
@@ -89,11 +98,11 @@
89
98
  "hooks": [
90
99
  {
91
100
  "type": "command",
92
- "command": "if [ -f .swarm/claude-swarm/config.json ] && node -e \"const c=JSON.parse(require('fs').readFileSync('.swarm/claude-swarm/config.json','utf-8')).map||{};process.exit(c.enabled||c.server||process.env.SWARM_MAP_SERVER||process.env.SWARM_MAP_ENABLED?0:1)\" 2>/dev/null; then node \"${CLAUDE_PLUGIN_ROOT}/scripts/map-hook.mjs\" native-task-updated; fi"
101
+ "command": "node -e \"const f=require('fs'),p=require('path'),h=require('os').homedir();let c={};try{c=JSON.parse(f.readFileSync('.swarm/claude-swarm/config.json','utf-8'))}catch{try{c=JSON.parse(f.readFileSync(p.join(h,'.claude-swarm','config.json'),'utf-8'))}catch{}};const m=c.map||{};process.exit(m.enabled||m.server||process.env.SWARM_MAP_SERVER||process.env.SWARM_MAP_ENABLED?0:1)\" 2>/dev/null && node \"${CLAUDE_PLUGIN_ROOT}/scripts/map-hook.mjs\" native-task-updated"
93
102
  },
94
103
  {
95
104
  "type": "command",
96
- "command": "if [ -f .swarm/claude-swarm/config.json ] && node -e \"const c=JSON.parse(require('fs').readFileSync('.swarm/claude-swarm/config.json','utf-8'));process.exit(c.sessionlog?.enabled?0:1)\" 2>/dev/null; then node \"${CLAUDE_PLUGIN_ROOT}/scripts/map-hook.mjs\" sessionlog-dispatch post-task-update; fi"
105
+ "command": "node -e \"const f=require('fs'),p=require('path'),h=require('os').homedir();let c={};try{c=JSON.parse(f.readFileSync('.swarm/claude-swarm/config.json','utf-8'))}catch{try{c=JSON.parse(f.readFileSync(p.join(h,'.claude-swarm','config.json'),'utf-8'))}catch{}};process.exit(c.sessionlog?.enabled?0:1)\" 2>/dev/null && node \"${CLAUDE_PLUGIN_ROOT}/scripts/map-hook.mjs\" sessionlog-dispatch post-task-update"
97
106
  }
98
107
  ]
99
108
  },
@@ -102,7 +111,7 @@
102
111
  "hooks": [
103
112
  {
104
113
  "type": "command",
105
- "command": "if [ -f .swarm/claude-swarm/config.json ] && node -e \"const c=JSON.parse(require('fs').readFileSync('.swarm/claude-swarm/config.json','utf-8'));process.exit(c.sessionlog?.enabled?0:1)\" 2>/dev/null; then node \"${CLAUDE_PLUGIN_ROOT}/scripts/map-hook.mjs\" sessionlog-dispatch post-todo; fi"
114
+ "command": "node -e \"const f=require('fs'),p=require('path'),h=require('os').homedir();let c={};try{c=JSON.parse(f.readFileSync('.swarm/claude-swarm/config.json','utf-8'))}catch{try{c=JSON.parse(f.readFileSync(p.join(h,'.claude-swarm','config.json'),'utf-8'))}catch{}};process.exit(c.sessionlog?.enabled?0:1)\" 2>/dev/null && node \"${CLAUDE_PLUGIN_ROOT}/scripts/map-hook.mjs\" sessionlog-dispatch post-todo"
106
115
  }
107
116
  ]
108
117
  },
@@ -111,7 +120,7 @@
111
120
  "hooks": [
112
121
  {
113
122
  "type": "command",
114
- "command": "if [ -f .swarm/claude-swarm/config.json ] && node -e \"const c=JSON.parse(require('fs').readFileSync('.swarm/claude-swarm/config.json','utf-8'));process.exit(c.sessionlog?.enabled?0:1)\" 2>/dev/null; then node \"${CLAUDE_PLUGIN_ROOT}/scripts/map-hook.mjs\" sessionlog-dispatch post-plan-enter; fi"
123
+ "command": "node -e \"const f=require('fs'),p=require('path'),h=require('os').homedir();let c={};try{c=JSON.parse(f.readFileSync('.swarm/claude-swarm/config.json','utf-8'))}catch{try{c=JSON.parse(f.readFileSync(p.join(h,'.claude-swarm','config.json'),'utf-8'))}catch{}};process.exit(c.sessionlog?.enabled?0:1)\" 2>/dev/null && node \"${CLAUDE_PLUGIN_ROOT}/scripts/map-hook.mjs\" sessionlog-dispatch post-plan-enter"
115
124
  }
116
125
  ]
117
126
  },
@@ -120,7 +129,7 @@
120
129
  "hooks": [
121
130
  {
122
131
  "type": "command",
123
- "command": "if [ -f .swarm/claude-swarm/config.json ] && node -e \"const c=JSON.parse(require('fs').readFileSync('.swarm/claude-swarm/config.json','utf-8'));process.exit(c.sessionlog?.enabled?0:1)\" 2>/dev/null; then node \"${CLAUDE_PLUGIN_ROOT}/scripts/map-hook.mjs\" sessionlog-dispatch post-plan-exit; fi"
132
+ "command": "node -e \"const f=require('fs'),p=require('path'),h=require('os').homedir();let c={};try{c=JSON.parse(f.readFileSync('.swarm/claude-swarm/config.json','utf-8'))}catch{try{c=JSON.parse(f.readFileSync(p.join(h,'.claude-swarm','config.json'),'utf-8'))}catch{}};process.exit(c.sessionlog?.enabled?0:1)\" 2>/dev/null && node \"${CLAUDE_PLUGIN_ROOT}/scripts/map-hook.mjs\" sessionlog-dispatch post-plan-exit"
124
133
  }
125
134
  ]
126
135
  },
@@ -129,7 +138,7 @@
129
138
  "hooks": [
130
139
  {
131
140
  "type": "command",
132
- "command": "if [ -f .swarm/claude-swarm/config.json ] && node -e \"const c=JSON.parse(require('fs').readFileSync('.swarm/claude-swarm/config.json','utf-8'));process.exit(c.sessionlog?.enabled?0:1)\" 2>/dev/null; then node \"${CLAUDE_PLUGIN_ROOT}/scripts/map-hook.mjs\" sessionlog-dispatch post-skill; fi"
141
+ "command": "node -e \"const f=require('fs'),p=require('path'),h=require('os').homedir();let c={};try{c=JSON.parse(f.readFileSync('.swarm/claude-swarm/config.json','utf-8'))}catch{try{c=JSON.parse(f.readFileSync(p.join(h,'.claude-swarm','config.json'),'utf-8'))}catch{}};process.exit(c.sessionlog?.enabled?0:1)\" 2>/dev/null && node \"${CLAUDE_PLUGIN_ROOT}/scripts/map-hook.mjs\" sessionlog-dispatch post-skill"
133
142
  }
134
143
  ]
135
144
  }
@@ -140,15 +149,15 @@
140
149
  "hooks": [
141
150
  {
142
151
  "type": "command",
143
- "command": "if [ -f .swarm/claude-swarm/config.json ] && node -e \"const c=JSON.parse(require('fs').readFileSync('.swarm/claude-swarm/config.json','utf-8')).map||{};process.exit(c.enabled||c.server||process.env.SWARM_MAP_SERVER||process.env.SWARM_MAP_ENABLED?0:1)\" 2>/dev/null; then node \"${CLAUDE_PLUGIN_ROOT}/scripts/map-hook.mjs\" turn-completed; fi"
152
+ "command": "node -e \"const f=require('fs'),p=require('path'),h=require('os').homedir();let c={};try{c=JSON.parse(f.readFileSync('.swarm/claude-swarm/config.json','utf-8'))}catch{try{c=JSON.parse(f.readFileSync(p.join(h,'.claude-swarm','config.json'),'utf-8'))}catch{}};const m=c.map||{};process.exit(m.enabled||m.server||process.env.SWARM_MAP_SERVER||process.env.SWARM_MAP_ENABLED?0:1)\" 2>/dev/null && node \"${CLAUDE_PLUGIN_ROOT}/scripts/map-hook.mjs\" turn-completed"
144
153
  },
145
154
  {
146
155
  "type": "command",
147
- "command": "if [ -f .swarm/claude-swarm/config.json ] && node -e \"const c=JSON.parse(require('fs').readFileSync('.swarm/claude-swarm/config.json','utf-8'));const m=c.map||{};process.exit((m.enabled||m.server||process.env.SWARM_MAP_SERVER||process.env.SWARM_MAP_ENABLED)&&c.sessionlog?.sync&&c.sessionlog.sync!=='off'?0:1)\" 2>/dev/null; then node \"${CLAUDE_PLUGIN_ROOT}/scripts/map-hook.mjs\" sessionlog-sync; fi"
156
+ "command": "node -e \"const f=require('fs'),p=require('path'),h=require('os').homedir();let c={};try{c=JSON.parse(f.readFileSync('.swarm/claude-swarm/config.json','utf-8'))}catch{try{c=JSON.parse(f.readFileSync(p.join(h,'.claude-swarm','config.json'),'utf-8'))}catch{}};const m=c.map||{};process.exit((m.enabled||m.server||process.env.SWARM_MAP_SERVER||process.env.SWARM_MAP_ENABLED)&&c.sessionlog?.sync&&c.sessionlog.sync!=='off'?0:1)\" 2>/dev/null && node \"${CLAUDE_PLUGIN_ROOT}/scripts/map-hook.mjs\" sessionlog-sync"
148
157
  },
149
158
  {
150
159
  "type": "command",
151
- "command": "if [ -f .swarm/claude-swarm/config.json ] && node -e \"const c=JSON.parse(require('fs').readFileSync('.swarm/claude-swarm/config.json','utf-8'));process.exit(c.sessionlog?.enabled?0:1)\" 2>/dev/null; then node \"${CLAUDE_PLUGIN_ROOT}/scripts/map-hook.mjs\" sessionlog-dispatch stop; fi"
160
+ "command": "node -e \"const f=require('fs'),p=require('path'),h=require('os').homedir();let c={};try{c=JSON.parse(f.readFileSync('.swarm/claude-swarm/config.json','utf-8'))}catch{try{c=JSON.parse(f.readFileSync(p.join(h,'.claude-swarm','config.json'),'utf-8'))}catch{}};process.exit(c.sessionlog?.enabled?0:1)\" 2>/dev/null && node \"${CLAUDE_PLUGIN_ROOT}/scripts/map-hook.mjs\" sessionlog-dispatch stop"
152
161
  }
153
162
  ]
154
163
  }
@@ -159,7 +168,7 @@
159
168
  "hooks": [
160
169
  {
161
170
  "type": "command",
162
- "command": "if [ -f .swarm/claude-swarm/config.json ] && node -e \"const c=JSON.parse(require('fs').readFileSync('.swarm/claude-swarm/config.json','utf-8')).map||{};process.exit(c.enabled||c.server||process.env.SWARM_MAP_SERVER||process.env.SWARM_MAP_ENABLED?0:1)\" 2>/dev/null; then node \"${CLAUDE_PLUGIN_ROOT}/scripts/map-hook.mjs\" subagent-start; fi"
171
+ "command": "node -e \"const f=require('fs'),p=require('path'),h=require('os').homedir();let c={};try{c=JSON.parse(f.readFileSync('.swarm/claude-swarm/config.json','utf-8'))}catch{try{c=JSON.parse(f.readFileSync(p.join(h,'.claude-swarm','config.json'),'utf-8'))}catch{}};const m=c.map||{};process.exit(m.enabled||m.server||process.env.SWARM_MAP_SERVER||process.env.SWARM_MAP_ENABLED?0:1)\" 2>/dev/null && node \"${CLAUDE_PLUGIN_ROOT}/scripts/map-hook.mjs\" subagent-start"
163
172
  }
164
173
  ]
165
174
  }
@@ -170,7 +179,7 @@
170
179
  "hooks": [
171
180
  {
172
181
  "type": "command",
173
- "command": "if [ -f .swarm/claude-swarm/config.json ] && node -e \"const c=JSON.parse(require('fs').readFileSync('.swarm/claude-swarm/config.json','utf-8')).map||{};process.exit(c.enabled||c.server||process.env.SWARM_MAP_SERVER||process.env.SWARM_MAP_ENABLED?0:1)\" 2>/dev/null; then node \"${CLAUDE_PLUGIN_ROOT}/scripts/map-hook.mjs\" subagent-stop; fi"
182
+ "command": "node -e \"const f=require('fs'),p=require('path'),h=require('os').homedir();let c={};try{c=JSON.parse(f.readFileSync('.swarm/claude-swarm/config.json','utf-8'))}catch{try{c=JSON.parse(f.readFileSync(p.join(h,'.claude-swarm','config.json'),'utf-8'))}catch{}};const m=c.map||{};process.exit(m.enabled||m.server||process.env.SWARM_MAP_SERVER||process.env.SWARM_MAP_ENABLED?0:1)\" 2>/dev/null && node \"${CLAUDE_PLUGIN_ROOT}/scripts/map-hook.mjs\" subagent-stop"
174
183
  }
175
184
  ]
176
185
  }
@@ -181,7 +190,7 @@
181
190
  "hooks": [
182
191
  {
183
192
  "type": "command",
184
- "command": "if [ -f .swarm/claude-swarm/config.json ] && node -e \"const c=JSON.parse(require('fs').readFileSync('.swarm/claude-swarm/config.json','utf-8')).map||{};process.exit(c.enabled||c.server||process.env.SWARM_MAP_SERVER||process.env.SWARM_MAP_ENABLED?0:1)\" 2>/dev/null; then node \"${CLAUDE_PLUGIN_ROOT}/scripts/map-hook.mjs\" teammate-idle; fi"
193
+ "command": "node -e \"const f=require('fs'),p=require('path'),h=require('os').homedir();let c={};try{c=JSON.parse(f.readFileSync('.swarm/claude-swarm/config.json','utf-8'))}catch{try{c=JSON.parse(f.readFileSync(p.join(h,'.claude-swarm','config.json'),'utf-8'))}catch{}};const m=c.map||{};process.exit(m.enabled||m.server||process.env.SWARM_MAP_SERVER||process.env.SWARM_MAP_ENABLED?0:1)\" 2>/dev/null && node \"${CLAUDE_PLUGIN_ROOT}/scripts/map-hook.mjs\" teammate-idle"
185
194
  }
186
195
  ]
187
196
  }
@@ -192,7 +201,7 @@
192
201
  "hooks": [
193
202
  {
194
203
  "type": "command",
195
- "command": "if [ -f .swarm/claude-swarm/config.json ] && node -e \"const c=JSON.parse(require('fs').readFileSync('.swarm/claude-swarm/config.json','utf-8')).map||{};process.exit(c.enabled||c.server||process.env.SWARM_MAP_SERVER||process.env.SWARM_MAP_ENABLED?0:1)\" 2>/dev/null; then node \"${CLAUDE_PLUGIN_ROOT}/scripts/map-hook.mjs\" task-completed; fi"
204
+ "command": "node -e \"const f=require('fs'),p=require('path'),h=require('os').homedir();let c={};try{c=JSON.parse(f.readFileSync('.swarm/claude-swarm/config.json','utf-8'))}catch{try{c=JSON.parse(f.readFileSync(p.join(h,'.claude-swarm','config.json'),'utf-8'))}catch{}};const m=c.map||{};process.exit(m.enabled||m.server||process.env.SWARM_MAP_SERVER||process.env.SWARM_MAP_ENABLED?0:1)\" 2>/dev/null && node \"${CLAUDE_PLUGIN_ROOT}/scripts/map-hook.mjs\" task-completed"
196
205
  }
197
206
  ]
198
207
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "claude-code-swarm",
3
- "version": "0.3.12",
3
+ "version": "0.3.17",
4
4
  "description": "Claude Code plugin for launching agent teams from openteams topologies with MAP observability",
5
5
  "type": "module",
6
6
  "exports": {
@@ -18,7 +18,7 @@
18
18
  "swarm-generate-agents": "./scripts/generate-agents.mjs"
19
19
  },
20
20
  "dependencies": {
21
- "@multi-agent-protocol/sdk": "^0.1.4",
21
+ "@multi-agent-protocol/sdk": "^0.1.9",
22
22
  "agentic-mesh": "^0.2.0",
23
23
  "js-yaml": "^4.1.0"
24
24
  },
@@ -54,8 +54,8 @@
54
54
  },
55
55
  "devDependencies": {
56
56
  "agent-inbox": "^0.1.9",
57
- "minimem": "^0.1.0",
58
- "opentasks": "^0.0.6",
57
+ "minimem": "^0.1.1",
58
+ "opentasks": "^0.0.8",
59
59
  "skill-tree": "^0.1.5",
60
60
  "vitest": "^4.0.18",
61
61
  "ws": "^8.0.0"
@@ -48,6 +48,7 @@ import {
48
48
  buildOpentasksBridgeCommands,
49
49
  handleNativeTaskCreatedEvent,
50
50
  handleNativeTaskUpdatedEvent,
51
+ buildMinimemBridgeCommand,
51
52
  } from "../src/map-events.mjs";
52
53
  import { syncSessionlog, dispatchSessionlogHook } from "../src/sessionlog.mjs";
53
54
  import { findSocketPath, pushSyncEvent } from "../src/opentasks-client.mjs";
@@ -194,6 +195,18 @@ async function handleSessionlogDispatch(hookData) {
194
195
  await dispatchSessionlogHook(sessionlogHookName, hookData);
195
196
  }
196
197
 
198
+ // ── minimem sync ─────────────────────────────────────────────────────────────
199
+
200
+ async function handleMinimemMcpUsed(hookData, sessionId) {
201
+ const config = readConfig();
202
+ if (!config.minimem?.enabled) return;
203
+
204
+ const command = buildMinimemBridgeCommand(hookData);
205
+ if (command) {
206
+ await sendCommand(config, command, sessionId);
207
+ }
208
+ }
209
+
197
210
  // ── Main ──────────────────────────────────────────────────────────────────────
198
211
 
199
212
  async function main() {
@@ -214,6 +227,7 @@ async function main() {
214
227
  case "opentasks-mcp-used": await handleOpentasksMcpUsed(hookData, sessionId); break;
215
228
  case "native-task-created": await handleNativeTaskCreated(hookData, sessionId); break;
216
229
  case "native-task-updated": await handleNativeTaskUpdated(hookData, sessionId); break;
230
+ case "minimem-mcp-used": await handleMinimemMcpUsed(hookData, sessionId); break;
217
231
  case "sessionlog-dispatch": await handleSessionlogDispatch(hookData); break;
218
232
  default:
219
233
  log.warn("unknown action", { action });