claude-code-swarm 0.3.3 → 0.3.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (273) hide show
  1. package/.claude-plugin/marketplace.json +1 -1
  2. package/.claude-plugin/plugin.json +22 -1
  3. package/.claude-plugin/run-agent-inbox-mcp.sh +76 -0
  4. package/.claude-plugin/run-minimem-mcp.sh +98 -0
  5. package/.claude-plugin/run-opentasks-mcp.sh +65 -0
  6. package/CLAUDE.md +200 -36
  7. package/README.md +65 -0
  8. package/e2e/helpers/cleanup.mjs +17 -3
  9. package/e2e/helpers/map-mock-server.mjs +201 -25
  10. package/e2e/helpers/sidecar.mjs +222 -0
  11. package/e2e/helpers/workspace.mjs +2 -1
  12. package/e2e/tier5-sidecar-inbox.test.mjs +900 -0
  13. package/e2e/tier6-inbox-mcp.test.mjs +173 -0
  14. package/e2e/tier6-live-agent.test.mjs +759 -0
  15. package/e2e/vitest.config.e2e.mjs +1 -1
  16. package/hooks/hooks.json +15 -8
  17. package/package.json +13 -1
  18. package/references/agent-inbox/CLAUDE.md +151 -0
  19. package/references/agent-inbox/README.md +238 -0
  20. package/references/agent-inbox/docs/CLAUDE-CODE-SWARM-PROPOSAL.md +137 -0
  21. package/references/agent-inbox/docs/DESIGN.md +1156 -0
  22. package/references/agent-inbox/hooks/inbox-hook.mjs +119 -0
  23. package/references/agent-inbox/hooks/register-hook.mjs +69 -0
  24. package/references/agent-inbox/package-lock.json +3347 -0
  25. package/references/agent-inbox/package.json +58 -0
  26. package/references/agent-inbox/rules/agent-inbox.md +78 -0
  27. package/references/agent-inbox/src/federation/address.ts +61 -0
  28. package/references/agent-inbox/src/federation/connection-manager.ts +573 -0
  29. package/references/agent-inbox/src/federation/delivery-queue.ts +222 -0
  30. package/references/agent-inbox/src/federation/index.ts +6 -0
  31. package/references/agent-inbox/src/federation/routing-engine.ts +188 -0
  32. package/references/agent-inbox/src/federation/trust.ts +71 -0
  33. package/references/agent-inbox/src/index.ts +390 -0
  34. package/references/agent-inbox/src/ipc/ipc-server.ts +207 -0
  35. package/references/agent-inbox/src/jsonrpc/mail-server.ts +382 -0
  36. package/references/agent-inbox/src/map/map-client.ts +414 -0
  37. package/references/agent-inbox/src/mcp/mcp-server.ts +272 -0
  38. package/references/agent-inbox/src/mesh/delivery-bridge.ts +110 -0
  39. package/references/agent-inbox/src/mesh/mesh-connector.ts +41 -0
  40. package/references/agent-inbox/src/mesh/mesh-transport.ts +157 -0
  41. package/references/agent-inbox/src/mesh/type-mapper.ts +239 -0
  42. package/references/agent-inbox/src/push/notifier.ts +233 -0
  43. package/references/agent-inbox/src/registry/warm-registry.ts +255 -0
  44. package/references/agent-inbox/src/router/message-router.ts +175 -0
  45. package/references/agent-inbox/src/storage/interface.ts +48 -0
  46. package/references/agent-inbox/src/storage/memory.ts +145 -0
  47. package/references/agent-inbox/src/storage/sqlite.ts +671 -0
  48. package/references/agent-inbox/src/traceability/traceability.ts +183 -0
  49. package/references/agent-inbox/src/types.ts +303 -0
  50. package/references/agent-inbox/test/federation/address.test.ts +101 -0
  51. package/references/agent-inbox/test/federation/connection-manager.test.ts +546 -0
  52. package/references/agent-inbox/test/federation/delivery-queue.test.ts +159 -0
  53. package/references/agent-inbox/test/federation/integration.test.ts +857 -0
  54. package/references/agent-inbox/test/federation/routing-engine.test.ts +117 -0
  55. package/references/agent-inbox/test/federation/sdk-integration.test.ts +744 -0
  56. package/references/agent-inbox/test/federation/trust.test.ts +89 -0
  57. package/references/agent-inbox/test/ipc-jsonrpc.test.ts +113 -0
  58. package/references/agent-inbox/test/ipc-server.test.ts +197 -0
  59. package/references/agent-inbox/test/mail-server.test.ts +285 -0
  60. package/references/agent-inbox/test/map-client.test.ts +408 -0
  61. package/references/agent-inbox/test/mesh/delivery-bridge.test.ts +178 -0
  62. package/references/agent-inbox/test/mesh/e2e-mesh.test.ts +527 -0
  63. package/references/agent-inbox/test/mesh/e2e-real-meshpeer.test.ts +629 -0
  64. package/references/agent-inbox/test/mesh/federation-mesh.test.ts +269 -0
  65. package/references/agent-inbox/test/mesh/mesh-connector.test.ts +66 -0
  66. package/references/agent-inbox/test/mesh/mesh-transport.test.ts +191 -0
  67. package/references/agent-inbox/test/mesh/meshpeer-integration.test.ts +442 -0
  68. package/references/agent-inbox/test/mesh/mock-mesh.ts +125 -0
  69. package/references/agent-inbox/test/mesh/mock-meshpeer.ts +266 -0
  70. package/references/agent-inbox/test/mesh/type-mapper.test.ts +226 -0
  71. package/references/agent-inbox/test/message-router.test.ts +184 -0
  72. package/references/agent-inbox/test/push-notifier.test.ts +139 -0
  73. package/references/agent-inbox/test/registry/warm-registry.test.ts +171 -0
  74. package/references/agent-inbox/test/sqlite-prefix.test.ts +192 -0
  75. package/references/agent-inbox/test/sqlite-storage.test.ts +243 -0
  76. package/references/agent-inbox/test/storage.test.ts +196 -0
  77. package/references/agent-inbox/test/traceability.test.ts +123 -0
  78. package/references/agent-inbox/test/wake.test.ts +330 -0
  79. package/references/agent-inbox/tsconfig.json +20 -0
  80. package/references/agent-inbox/tsup.config.ts +10 -0
  81. package/references/agent-inbox/vitest.config.ts +8 -0
  82. package/references/minimem/.claude/settings.json +7 -0
  83. package/references/minimem/.sudocode/issues.jsonl +18 -0
  84. package/references/minimem/.sudocode/specs.jsonl +1 -0
  85. package/references/minimem/CLAUDE.md +329 -0
  86. package/references/minimem/README.md +565 -0
  87. package/references/minimem/claude-plugin/.claude-plugin/plugin.json +10 -0
  88. package/references/minimem/claude-plugin/.mcp.json +7 -0
  89. package/references/minimem/claude-plugin/README.md +158 -0
  90. package/references/minimem/claude-plugin/commands/recall.md +47 -0
  91. package/references/minimem/claude-plugin/commands/remember.md +41 -0
  92. package/references/minimem/claude-plugin/hooks/__tests__/hooks.test.ts +272 -0
  93. package/references/minimem/claude-plugin/hooks/hooks.json +27 -0
  94. package/references/minimem/claude-plugin/hooks/session-end.sh +86 -0
  95. package/references/minimem/claude-plugin/hooks/session-start.sh +85 -0
  96. package/references/minimem/claude-plugin/skills/memory/SKILL.md +108 -0
  97. package/references/minimem/media/banner.png +0 -0
  98. package/references/minimem/package-lock.json +5373 -0
  99. package/references/minimem/package.json +76 -0
  100. package/references/minimem/scripts/postbuild.js +49 -0
  101. package/references/minimem/src/__tests__/edge-cases.test.ts +371 -0
  102. package/references/minimem/src/__tests__/errors.test.ts +265 -0
  103. package/references/minimem/src/__tests__/helpers.ts +199 -0
  104. package/references/minimem/src/__tests__/internal.test.ts +407 -0
  105. package/references/minimem/src/__tests__/knowledge-frontmatter.test.ts +148 -0
  106. package/references/minimem/src/__tests__/knowledge.test.ts +148 -0
  107. package/references/minimem/src/__tests__/minimem.integration.test.ts +1127 -0
  108. package/references/minimem/src/__tests__/session.test.ts +190 -0
  109. package/references/minimem/src/cli/__tests__/commands.test.ts +760 -0
  110. package/references/minimem/src/cli/__tests__/contained-layout.test.ts +286 -0
  111. package/references/minimem/src/cli/commands/__tests__/conflicts.test.ts +141 -0
  112. package/references/minimem/src/cli/commands/append.ts +76 -0
  113. package/references/minimem/src/cli/commands/config.ts +262 -0
  114. package/references/minimem/src/cli/commands/conflicts.ts +415 -0
  115. package/references/minimem/src/cli/commands/daemon.ts +169 -0
  116. package/references/minimem/src/cli/commands/index.ts +12 -0
  117. package/references/minimem/src/cli/commands/init.ts +166 -0
  118. package/references/minimem/src/cli/commands/mcp.ts +221 -0
  119. package/references/minimem/src/cli/commands/push-pull.ts +213 -0
  120. package/references/minimem/src/cli/commands/search.ts +223 -0
  121. package/references/minimem/src/cli/commands/status.ts +84 -0
  122. package/references/minimem/src/cli/commands/store.ts +189 -0
  123. package/references/minimem/src/cli/commands/sync-init.ts +290 -0
  124. package/references/minimem/src/cli/commands/sync.ts +70 -0
  125. package/references/minimem/src/cli/commands/upsert.ts +197 -0
  126. package/references/minimem/src/cli/config.ts +611 -0
  127. package/references/minimem/src/cli/index.ts +299 -0
  128. package/references/minimem/src/cli/shared.ts +189 -0
  129. package/references/minimem/src/cli/sync/__tests__/central.test.ts +152 -0
  130. package/references/minimem/src/cli/sync/__tests__/conflicts.test.ts +209 -0
  131. package/references/minimem/src/cli/sync/__tests__/daemon.test.ts +118 -0
  132. package/references/minimem/src/cli/sync/__tests__/detection.test.ts +207 -0
  133. package/references/minimem/src/cli/sync/__tests__/integration.test.ts +476 -0
  134. package/references/minimem/src/cli/sync/__tests__/registry.test.ts +363 -0
  135. package/references/minimem/src/cli/sync/__tests__/state.test.ts +255 -0
  136. package/references/minimem/src/cli/sync/__tests__/validation.test.ts +193 -0
  137. package/references/minimem/src/cli/sync/__tests__/watcher.test.ts +178 -0
  138. package/references/minimem/src/cli/sync/central.ts +292 -0
  139. package/references/minimem/src/cli/sync/conflicts.ts +205 -0
  140. package/references/minimem/src/cli/sync/daemon.ts +407 -0
  141. package/references/minimem/src/cli/sync/detection.ts +138 -0
  142. package/references/minimem/src/cli/sync/index.ts +107 -0
  143. package/references/minimem/src/cli/sync/operations.ts +373 -0
  144. package/references/minimem/src/cli/sync/registry.ts +279 -0
  145. package/references/minimem/src/cli/sync/state.ts +358 -0
  146. package/references/minimem/src/cli/sync/validation.ts +206 -0
  147. package/references/minimem/src/cli/sync/watcher.ts +237 -0
  148. package/references/minimem/src/cli/version.ts +34 -0
  149. package/references/minimem/src/core/index.ts +9 -0
  150. package/references/minimem/src/core/indexer.ts +628 -0
  151. package/references/minimem/src/core/searcher.ts +221 -0
  152. package/references/minimem/src/db/schema.ts +183 -0
  153. package/references/minimem/src/db/sqlite-vec.ts +24 -0
  154. package/references/minimem/src/embeddings/__tests__/embeddings.test.ts +431 -0
  155. package/references/minimem/src/embeddings/batch-gemini.ts +392 -0
  156. package/references/minimem/src/embeddings/batch-openai.ts +409 -0
  157. package/references/minimem/src/embeddings/embeddings.ts +434 -0
  158. package/references/minimem/src/index.ts +132 -0
  159. package/references/minimem/src/internal.ts +299 -0
  160. package/references/minimem/src/minimem.ts +1291 -0
  161. package/references/minimem/src/search/__tests__/hybrid.test.ts +247 -0
  162. package/references/minimem/src/search/graph.ts +234 -0
  163. package/references/minimem/src/search/hybrid.ts +151 -0
  164. package/references/minimem/src/search/search.ts +256 -0
  165. package/references/minimem/src/server/__tests__/mcp.test.ts +347 -0
  166. package/references/minimem/src/server/__tests__/tools.test.ts +364 -0
  167. package/references/minimem/src/server/mcp.ts +326 -0
  168. package/references/minimem/src/server/tools.ts +720 -0
  169. package/references/minimem/src/session.ts +460 -0
  170. package/references/minimem/src/store/__tests__/manifest.test.ts +177 -0
  171. package/references/minimem/src/store/__tests__/materialize.test.ts +52 -0
  172. package/references/minimem/src/store/__tests__/store-graph.test.ts +228 -0
  173. package/references/minimem/src/store/index.ts +27 -0
  174. package/references/minimem/src/store/manifest.ts +203 -0
  175. package/references/minimem/src/store/materialize.ts +185 -0
  176. package/references/minimem/src/store/store-graph.ts +252 -0
  177. package/references/minimem/tsconfig.json +19 -0
  178. package/references/minimem/tsup.config.ts +26 -0
  179. package/references/minimem/vitest.config.ts +29 -0
  180. package/references/openteams/src/cli/generate.ts +23 -1
  181. package/references/openteams/src/generators/agent-prompt-generator.test.ts +94 -0
  182. package/references/openteams/src/generators/agent-prompt-generator.ts +42 -13
  183. package/references/openteams/src/generators/package-generator.ts +9 -1
  184. package/references/openteams/src/generators/skill-generator.test.ts +28 -0
  185. package/references/openteams/src/generators/skill-generator.ts +10 -4
  186. package/references/skill-tree/.claude/settings.json +6 -0
  187. package/references/skill-tree/.sudocode/issues.jsonl +19 -0
  188. package/references/skill-tree/.sudocode/specs.jsonl +3 -0
  189. package/references/skill-tree/CLAUDE.md +132 -0
  190. package/references/skill-tree/README.md +396 -0
  191. package/references/skill-tree/docs/GAPS_v1.md +221 -0
  192. package/references/skill-tree/docs/INTEGRATION_PLAN.md +467 -0
  193. package/references/skill-tree/docs/TODOS.md +91 -0
  194. package/references/skill-tree/docs/anthropic_skill_guide.md +1364 -0
  195. package/references/skill-tree/docs/design/federated-skill-trees.md +524 -0
  196. package/references/skill-tree/docs/design/multi-agent-sync.md +759 -0
  197. package/references/skill-tree/docs/scraper/BRAINSTORM.md +583 -0
  198. package/references/skill-tree/docs/scraper/POC_PLAN.md +420 -0
  199. package/references/skill-tree/docs/scraper/README.md +170 -0
  200. package/references/skill-tree/examples/basic-usage.ts +157 -0
  201. package/references/skill-tree/package-lock.json +1852 -0
  202. package/references/skill-tree/package.json +66 -0
  203. package/references/skill-tree/plan.md +78 -0
  204. package/references/skill-tree/scraper/README.md +123 -0
  205. package/references/skill-tree/scraper/docs/DESIGN.md +683 -0
  206. package/references/skill-tree/scraper/docs/PLAN.md +336 -0
  207. package/references/skill-tree/scraper/drizzle.config.ts +10 -0
  208. package/references/skill-tree/scraper/package-lock.json +6329 -0
  209. package/references/skill-tree/scraper/package.json +68 -0
  210. package/references/skill-tree/scraper/test/fixtures/invalid-skill/missing-description.md +7 -0
  211. package/references/skill-tree/scraper/test/fixtures/invalid-skill/missing-name.md +7 -0
  212. package/references/skill-tree/scraper/test/fixtures/minimal-skill/SKILL.md +27 -0
  213. package/references/skill-tree/scraper/test/fixtures/skill-json/SKILL.json +21 -0
  214. package/references/skill-tree/scraper/test/fixtures/skill-with-meta/SKILL.md +54 -0
  215. package/references/skill-tree/scraper/test/fixtures/skill-with-meta/_meta.json +24 -0
  216. package/references/skill-tree/scraper/test/fixtures/valid-skill/SKILL.md +93 -0
  217. package/references/skill-tree/scraper/test/fixtures/valid-skill/_meta.json +22 -0
  218. package/references/skill-tree/scraper/tsup.config.ts +14 -0
  219. package/references/skill-tree/scraper/vitest.config.ts +17 -0
  220. package/references/skill-tree/scripts/convert-to-vitest.ts +166 -0
  221. package/references/skill-tree/skills/skill-writer/SKILL.md +339 -0
  222. package/references/skill-tree/skills/skill-writer/references/examples.md +326 -0
  223. package/references/skill-tree/skills/skill-writer/references/patterns.md +210 -0
  224. package/references/skill-tree/skills/skill-writer/references/quality-checklist.md +123 -0
  225. package/references/skill-tree/test/run-all.ts +106 -0
  226. package/references/skill-tree/test/utils.ts +128 -0
  227. package/references/skill-tree/vitest.config.ts +16 -0
  228. package/references/swarmkit/src/commands/init/phases/configure.ts +0 -22
  229. package/references/swarmkit/src/commands/init/phases/global-setup.ts +5 -3
  230. package/references/swarmkit/src/commands/init/wizard.ts +2 -2
  231. package/references/swarmkit/src/packages/setup.test.ts +53 -7
  232. package/references/swarmkit/src/packages/setup.ts +37 -1
  233. package/scripts/bootstrap.mjs +26 -1
  234. package/scripts/generate-agents.mjs +5 -1
  235. package/scripts/map-hook.mjs +97 -64
  236. package/scripts/map-sidecar.mjs +179 -25
  237. package/scripts/team-loader.mjs +12 -41
  238. package/skills/swarm/SKILL.md +89 -25
  239. package/src/__tests__/agent-generator.test.mjs +6 -13
  240. package/src/__tests__/bootstrap.test.mjs +124 -1
  241. package/src/__tests__/config.test.mjs +200 -27
  242. package/src/__tests__/e2e-live-map.test.mjs +536 -0
  243. package/src/__tests__/e2e-mesh-sidecar.test.mjs +570 -0
  244. package/src/__tests__/e2e-native-task-hooks.test.mjs +376 -0
  245. package/src/__tests__/e2e-sidecar-bridge.test.mjs +477 -0
  246. package/src/__tests__/helpers.mjs +13 -0
  247. package/src/__tests__/inbox.test.mjs +22 -89
  248. package/src/__tests__/index.test.mjs +35 -9
  249. package/src/__tests__/integration.test.mjs +513 -0
  250. package/src/__tests__/map-events.test.mjs +514 -150
  251. package/src/__tests__/mesh-connection.test.mjs +308 -0
  252. package/src/__tests__/opentasks-client.test.mjs +517 -0
  253. package/src/__tests__/paths.test.mjs +185 -41
  254. package/src/__tests__/sidecar-client.test.mjs +35 -0
  255. package/src/__tests__/sidecar-server.test.mjs +124 -0
  256. package/src/__tests__/skilltree-client.test.mjs +80 -0
  257. package/src/agent-generator.mjs +104 -33
  258. package/src/bootstrap.mjs +150 -10
  259. package/src/config.mjs +81 -17
  260. package/src/context-output.mjs +58 -8
  261. package/src/inbox.mjs +9 -54
  262. package/src/index.mjs +39 -8
  263. package/src/map-connection.mjs +4 -3
  264. package/src/map-events.mjs +350 -80
  265. package/src/mesh-connection.mjs +148 -0
  266. package/src/opentasks-client.mjs +269 -0
  267. package/src/paths.mjs +182 -27
  268. package/src/sessionlog.mjs +14 -9
  269. package/src/sidecar-client.mjs +81 -27
  270. package/src/sidecar-server.mjs +175 -16
  271. package/src/skilltree-client.mjs +173 -0
  272. package/src/template.mjs +68 -4
  273. package/vitest.config.mjs +1 -0
@@ -1,70 +1,214 @@
1
- import { describe, it, expect } from "vitest";
1
+ import { describe, it, expect, vi, beforeEach, afterEach } from "vitest";
2
2
  import path from "path";
3
- import {
4
- SWARM_DIR, SOCKET_PATH, INBOX_PATH, PID_PATH, ROLES_PATH,
5
- CONFIG_PATH, TMP_DIR, TEAMS_DIR, MAP_DIR,
6
- teamDir, pluginDir,
7
- } from "../paths.mjs";
3
+ import os from "os";
4
+
5
+ // We need to test path resolution with different CWD states.
6
+ // Since paths.mjs computes values at module load time, we test the
7
+ // exported values AND mock fs.existsSync to test resolution logic.
8
+
9
+ describe("paths (current environment)", () => {
10
+ // These tests verify the exports work correctly in the test environment.
11
+ // The actual resolution depends on whether .swarm/claude-swarm/ exists in CWD.
12
+
13
+ let paths;
14
+
15
+ beforeEach(async () => {
16
+ // Fresh import each time (vitest module cache may apply)
17
+ paths = await import("../paths.mjs");
18
+ });
8
19
 
9
- describe("paths", () => {
10
20
  it("exports all expected path constants", () => {
11
- expect(typeof SWARM_DIR).toBe("string");
12
- expect(typeof CONFIG_PATH).toBe("string");
13
- expect(typeof TMP_DIR).toBe("string");
14
- expect(typeof TEAMS_DIR).toBe("string");
15
- expect(typeof MAP_DIR).toBe("string");
16
- expect(typeof SOCKET_PATH).toBe("string");
17
- expect(typeof INBOX_PATH).toBe("string");
18
- expect(typeof PID_PATH).toBe("string");
19
- expect(typeof ROLES_PATH).toBe("string");
21
+ expect(typeof paths.SWARM_DIR).toBe("string");
22
+ expect(typeof paths.CONFIG_PATH).toBe("string");
23
+ expect(typeof paths.TMP_DIR).toBe("string");
24
+ expect(typeof paths.TEAMS_DIR).toBe("string");
25
+ expect(typeof paths.MAP_DIR).toBe("string");
26
+ expect(typeof paths.SOCKET_PATH).toBe("string");
27
+ expect(typeof paths.PID_PATH).toBe("string");
28
+ expect(typeof paths.ROLES_PATH).toBe("string");
29
+ expect(typeof paths.IS_GLOBAL_PATHS).toBe("boolean");
20
30
  });
21
31
 
22
- it("all paths are under .swarm/claude-swarm/", () => {
23
- expect(CONFIG_PATH).toMatch(/^\.swarm\/claude-swarm\//);
24
- expect(TMP_DIR).toMatch(/^\.swarm\/claude-swarm\//);
25
- expect(TEAMS_DIR).toMatch(/^\.swarm\/claude-swarm\//);
26
- expect(MAP_DIR).toMatch(/^\.swarm\/claude-swarm\//);
27
- expect(SOCKET_PATH).toMatch(/^\.swarm\/claude-swarm\//);
28
- expect(INBOX_PATH).toMatch(/^\.swarm\/claude-swarm\//);
29
- expect(PID_PATH).toMatch(/^\.swarm\/claude-swarm\//);
30
- expect(ROLES_PATH).toMatch(/^\.swarm\/claude-swarm\//);
32
+ it("CONFIG_PATH is always project-relative", () => {
33
+ expect(paths.CONFIG_PATH).toBe(".swarm/claude-swarm/config.json");
31
34
  });
32
35
 
33
- it("CONFIG_PATH is .swarm/claude-swarm/config.json", () => {
34
- expect(CONFIG_PATH).toBe(".swarm/claude-swarm/config.json");
36
+ it("SWARM_DIR is always project-relative", () => {
37
+ expect(paths.SWARM_DIR).toBe(".swarm/claude-swarm");
35
38
  });
36
39
 
37
- it("TMP_DIR is .swarm/claude-swarm/tmp", () => {
38
- expect(TMP_DIR).toBe(".swarm/claude-swarm/tmp");
40
+ it("GLOBAL_CONFIG_DIR is under ~/.claude-swarm/", () => {
41
+ expect(paths.GLOBAL_CONFIG_DIR).toBe(path.join(os.homedir(), ".claude-swarm"));
39
42
  });
40
43
 
41
- it("TEAMS_DIR is under tmp/", () => {
42
- expect(TEAMS_DIR).toBe(".swarm/claude-swarm/tmp/teams");
44
+ it("GLOBAL_CONFIG_PATH is config.json under GLOBAL_CONFIG_DIR", () => {
45
+ expect(paths.GLOBAL_CONFIG_PATH).toBe(path.join(os.homedir(), ".claude-swarm", "config.json"));
43
46
  });
44
47
 
45
- it("MAP_DIR is under tmp/", () => {
46
- expect(MAP_DIR).toBe(".swarm/claude-swarm/tmp/map");
48
+ it("TEAMS_DIR is under TMP_DIR", () => {
49
+ expect(paths.TEAMS_DIR).toBe(path.join(paths.TMP_DIR, "teams"));
47
50
  });
48
51
 
49
- it("MAP runtime files are under tmp/map/", () => {
50
- expect(SOCKET_PATH).toContain(".swarm/claude-swarm/tmp/map/");
51
- expect(INBOX_PATH).toContain(".swarm/claude-swarm/tmp/map/");
52
- expect(PID_PATH).toContain(".swarm/claude-swarm/tmp/map/");
53
- expect(ROLES_PATH).toContain(".swarm/claude-swarm/tmp/map/");
52
+ it("MAP runtime files are under MAP_DIR", () => {
53
+ expect(paths.SOCKET_PATH).toBe(path.join(paths.MAP_DIR, "sidecar.sock"));
54
+ expect(paths.PID_PATH).toBe(path.join(paths.MAP_DIR, "sidecar.pid"));
55
+ expect(paths.ROLES_PATH).toBe(path.join(paths.MAP_DIR, "roles.json"));
56
+ expect(paths.SESSIONLOG_STATE_PATH).toBe(path.join(paths.MAP_DIR, "sessionlog-state.json"));
57
+ expect(paths.SIDECAR_LOG_PATH).toBe(path.join(paths.MAP_DIR, "sidecar.log"));
54
58
  });
55
59
 
56
60
  describe("teamDir", () => {
57
61
  it("returns per-template path under TEAMS_DIR", () => {
58
- expect(teamDir("gsd")).toBe(".swarm/claude-swarm/tmp/teams/gsd");
59
- expect(teamDir("bmad-method")).toBe(".swarm/claude-swarm/tmp/teams/bmad-method");
62
+ expect(paths.teamDir("gsd")).toBe(path.join(paths.TEAMS_DIR, "gsd"));
63
+ expect(paths.teamDir("bmad-method")).toBe(path.join(paths.TEAMS_DIR, "bmad-method"));
60
64
  });
61
65
  });
62
66
 
63
67
  describe("pluginDir", () => {
64
68
  it("resolves to the repository root (parent of src/)", () => {
65
- const dir = pluginDir();
69
+ const dir = paths.pluginDir();
66
70
  expect(dir).toContain("claude-code-swarm");
67
71
  expect(dir).not.toContain("src");
68
72
  });
69
73
  });
70
74
  });
75
+
76
+ describe("path resolution logic", () => {
77
+ // These tests verify the resolution rules by checking the relationship
78
+ // between IS_GLOBAL_PATHS and the computed paths.
79
+
80
+ let paths;
81
+
82
+ beforeEach(async () => {
83
+ paths = await import("../paths.mjs");
84
+ });
85
+
86
+ const globalBase = path.join(os.homedir(), ".claude", "claude-swarm");
87
+
88
+ it("when global: TMP_DIR is under ~/.claude/claude-swarm/tmp/", () => {
89
+ if (!paths.IS_GLOBAL_PATHS) return; // skip if project-level
90
+ expect(paths.TMP_DIR).toBe(path.join(globalBase, "tmp"));
91
+ });
92
+
93
+ it("when global: MAP_DIR includes CWD hash for isolation", () => {
94
+ if (!paths.IS_GLOBAL_PATHS) return;
95
+ // MAP_DIR should be: ~/.claude/claude-swarm/tmp/map/<12-char-hash>
96
+ const mapRelative = path.relative(path.join(globalBase, "tmp", "map"), paths.MAP_DIR);
97
+ expect(mapRelative).toMatch(/^[a-f0-9]{12}$/);
98
+ });
99
+
100
+ it("when global: TEAMS_DIR is shared (no CWD hash)", () => {
101
+ if (!paths.IS_GLOBAL_PATHS) return;
102
+ expect(paths.TEAMS_DIR).toBe(path.join(globalBase, "tmp", "teams"));
103
+ });
104
+
105
+ it("when project-level: TMP_DIR is under .swarm/claude-swarm/tmp", () => {
106
+ if (paths.IS_GLOBAL_PATHS) return; // skip if global
107
+ expect(paths.TMP_DIR).toBe(".swarm/claude-swarm/tmp");
108
+ });
109
+
110
+ it("when project-level: MAP_DIR has no CWD hash", () => {
111
+ if (paths.IS_GLOBAL_PATHS) return;
112
+ expect(paths.MAP_DIR).toBe(".swarm/claude-swarm/tmp/map");
113
+ });
114
+
115
+ it("when project-level: TEAMS_DIR is under project tmp", () => {
116
+ if (paths.IS_GLOBAL_PATHS) return;
117
+ expect(paths.TEAMS_DIR).toBe(".swarm/claude-swarm/tmp/teams");
118
+ });
119
+ });
120
+
121
+ describe("sessionPaths", () => {
122
+ let paths;
123
+
124
+ beforeEach(async () => {
125
+ paths = await import("../paths.mjs");
126
+ });
127
+
128
+ it("returns legacy paths when sessionId is null", () => {
129
+ const sp = paths.sessionPaths(null);
130
+ expect(sp.socketPath).toBe(paths.SOCKET_PATH);
131
+ expect(sp.pidPath).toBe(paths.PID_PATH);
132
+ expect(sp.sidecarLogPath).toBe(paths.SIDECAR_LOG_PATH);
133
+ expect(sp.sessionDir).toBeNull();
134
+ });
135
+
136
+ it("returns legacy paths when sessionId is undefined", () => {
137
+ const sp = paths.sessionPaths(undefined);
138
+ expect(sp.socketPath).toBe(paths.SOCKET_PATH);
139
+ expect(sp.sessionDir).toBeNull();
140
+ });
141
+
142
+ it("returns legacy paths when sessionId is empty string", () => {
143
+ const sp = paths.sessionPaths("");
144
+ expect(sp.socketPath).toBe(paths.SOCKET_PATH);
145
+ expect(sp.sessionDir).toBeNull();
146
+ });
147
+
148
+ it("returns session-scoped paths when sessionId is provided", () => {
149
+ const sp = paths.sessionPaths("abc123");
150
+ expect(sp.sessionDir).toBe(path.join(paths.MAP_DIR, "sessions", "abc123"));
151
+ expect(sp.socketPath).toBe(path.join(paths.MAP_DIR, "sessions", "abc123", "sidecar.sock"));
152
+ expect(sp.pidPath).toBe(path.join(paths.MAP_DIR, "sessions", "abc123", "sidecar.pid"));
153
+ expect(sp.sidecarLogPath).toBe(path.join(paths.MAP_DIR, "sessions", "abc123", "sidecar.log"));
154
+ });
155
+
156
+ it("hashes long session IDs (>12 chars) to 12 hex chars", () => {
157
+ const longId = "this-is-a-very-long-session-id-that-exceeds-12-chars";
158
+ const sp = paths.sessionPaths(longId);
159
+ const sessionDirName = path.basename(sp.sessionDir);
160
+ expect(sessionDirName).toMatch(/^[a-f0-9]{12}$/);
161
+ expect(sessionDirName.length).toBe(12);
162
+ });
163
+
164
+ it("uses short session IDs directly (<=12 chars)", () => {
165
+ const sp = paths.sessionPaths("short-id");
166
+ const sessionDirName = path.basename(sp.sessionDir);
167
+ expect(sessionDirName).toBe("short-id");
168
+ });
169
+
170
+ it("produces consistent hashes for the same long ID", () => {
171
+ const longId = "a-very-long-session-identifier-here";
172
+ const sp1 = paths.sessionPaths(longId);
173
+ const sp2 = paths.sessionPaths(longId);
174
+ expect(sp1.sessionDir).toBe(sp2.sessionDir);
175
+ });
176
+ });
177
+
178
+ describe("ensureSessionDir", () => {
179
+ let paths;
180
+ let fs;
181
+ let tmpDir;
182
+
183
+ beforeEach(async () => {
184
+ paths = await import("../paths.mjs");
185
+ fs = await import("fs");
186
+ tmpDir = fs.mkdtempSync(path.join(os.tmpdir(), "session-dir-test-"));
187
+ });
188
+
189
+ afterEach(async () => {
190
+ const fs = await import("fs");
191
+ try { fs.rmSync(tmpDir, { recursive: true, force: true }); } catch { /* ignore */ }
192
+ });
193
+
194
+ it("is exported as a function", () => {
195
+ expect(typeof paths.ensureSessionDir).toBe("function");
196
+ });
197
+ });
198
+
199
+ describe("listSessionDirs", () => {
200
+ let paths;
201
+
202
+ beforeEach(async () => {
203
+ paths = await import("../paths.mjs");
204
+ });
205
+
206
+ it("returns empty array when no sessions directory exists", () => {
207
+ const result = paths.listSessionDirs();
208
+ expect(Array.isArray(result)).toBe(true);
209
+ });
210
+
211
+ it("is exported as a function", () => {
212
+ expect(typeof paths.listSessionDirs).toBe("function");
213
+ });
214
+ });
@@ -65,9 +65,44 @@ describe("sidecar-client", () => {
65
65
  });
66
66
  });
67
67
 
68
+ describe("isSidecarAlive with custom pidPath", () => {
69
+ let tmpDir;
70
+
71
+ beforeEach(() => {
72
+ tmpDir = makeTmpDir();
73
+ });
74
+ afterEach(() => {
75
+ cleanupTmpDir(tmpDir);
76
+ });
77
+
78
+ it("returns true when PID file contains current process PID", () => {
79
+ const pidPath = path.join(tmpDir, "test.pid");
80
+ fs.writeFileSync(pidPath, String(process.pid));
81
+ expect(isSidecarAlive(pidPath)).toBe(true);
82
+ });
83
+
84
+ it("returns false when PID file contains non-existent PID", () => {
85
+ const pidPath = path.join(tmpDir, "test.pid");
86
+ fs.writeFileSync(pidPath, "999999999");
87
+ expect(isSidecarAlive(pidPath)).toBe(false);
88
+ });
89
+
90
+ it("returns false when custom pidPath does not exist", () => {
91
+ expect(isSidecarAlive(path.join(tmpDir, "nope.pid"))).toBe(false);
92
+ });
93
+ });
94
+
68
95
  describe("killSidecar", () => {
69
96
  it("does not throw when PID file is missing", () => {
70
97
  expect(() => killSidecar()).not.toThrow();
71
98
  });
99
+
100
+ it("does not throw when sessionId is null (legacy paths)", () => {
101
+ expect(() => killSidecar(null)).not.toThrow();
102
+ });
103
+
104
+ it("does not throw when sessionId is provided but no PID file exists", () => {
105
+ expect(() => killSidecar("nonexistent-session")).not.toThrow();
106
+ });
72
107
  });
73
108
  });
@@ -247,6 +247,130 @@ describe("sidecar-server", () => {
247
247
  });
248
248
  });
249
249
 
250
+ // ── Task event bridge (opentasks → MAP) ─────────────────────────────
251
+
252
+ describe("bridge-task-created", () => {
253
+ it("sends task.created event via conn.send with broadcast", async () => {
254
+ await handler({
255
+ action: "bridge-task-created",
256
+ task: { id: "task-1", title: "Fix bug", status: "open", assignee: "worker-1" },
257
+ agentId: "worker-1",
258
+ }, mockClient);
259
+ expect(mockConnection.send).toHaveBeenCalledWith(
260
+ { scope: "swarm:test" },
261
+ { type: "task.created", task: { id: "task-1", title: "Fix bug", status: "open", assignee: "worker-1" }, _origin: "worker-1" },
262
+ { relationship: "broadcast" }
263
+ );
264
+ });
265
+
266
+ it("responds {ok: true} even without connection", async () => {
267
+ const nullHandler = createCommandHandler(null, "swarm:test", registeredAgents);
268
+ await nullHandler({
269
+ action: "bridge-task-created",
270
+ task: { id: "t-1" },
271
+ }, mockClient);
272
+ const written = JSON.parse(mockClient.write.mock.calls[0][0]);
273
+ expect(written.ok).toBe(true);
274
+ });
275
+
276
+ it("defaults _origin to 'opentasks' when agentId missing", async () => {
277
+ await handler({
278
+ action: "bridge-task-created",
279
+ task: { id: "t-1" },
280
+ }, mockClient);
281
+ const [, payload] = mockConnection.send.mock.calls[0];
282
+ expect(payload._origin).toBe("opentasks");
283
+ });
284
+ });
285
+
286
+ describe("bridge-task-status", () => {
287
+ it("sends task.status event via conn.send", async () => {
288
+ await handler({
289
+ action: "bridge-task-status",
290
+ taskId: "task-1",
291
+ previous: "open",
292
+ current: "in_progress",
293
+ agentId: "worker-1",
294
+ }, mockClient);
295
+ expect(mockConnection.send).toHaveBeenCalledWith(
296
+ { scope: "swarm:test" },
297
+ { type: "task.status", taskId: "task-1", previous: "open", current: "in_progress", _origin: "worker-1" },
298
+ { relationship: "broadcast" }
299
+ );
300
+ });
301
+
302
+ it("also sends task.completed for terminal state 'completed'", async () => {
303
+ await handler({
304
+ action: "bridge-task-status",
305
+ taskId: "task-1",
306
+ previous: "open",
307
+ current: "completed",
308
+ agentId: "worker-1",
309
+ }, mockClient);
310
+ expect(mockConnection.send).toHaveBeenCalledTimes(2);
311
+ const [, secondPayload] = mockConnection.send.mock.calls[1];
312
+ expect(secondPayload.type).toBe("task.completed");
313
+ expect(secondPayload.taskId).toBe("task-1");
314
+ });
315
+
316
+ it("also sends task.completed for terminal state 'closed'", async () => {
317
+ await handler({
318
+ action: "bridge-task-status",
319
+ taskId: "task-1",
320
+ current: "closed",
321
+ }, mockClient);
322
+ expect(mockConnection.send).toHaveBeenCalledTimes(2);
323
+ const [, secondPayload] = mockConnection.send.mock.calls[1];
324
+ expect(secondPayload.type).toBe("task.completed");
325
+ });
326
+
327
+ it("does not send task.completed for non-terminal states", async () => {
328
+ await handler({
329
+ action: "bridge-task-status",
330
+ taskId: "task-1",
331
+ current: "in_progress",
332
+ }, mockClient);
333
+ expect(mockConnection.send).toHaveBeenCalledTimes(1);
334
+ });
335
+
336
+ it("responds {ok: true}", async () => {
337
+ await handler({
338
+ action: "bridge-task-status",
339
+ taskId: "task-1",
340
+ current: "completed",
341
+ }, mockClient);
342
+ const written = JSON.parse(mockClient.write.mock.calls[0][0]);
343
+ expect(written.ok).toBe(true);
344
+ });
345
+ });
346
+
347
+ describe("bridge-task-assigned", () => {
348
+ it("sends task.assigned event via conn.send", async () => {
349
+ await handler({
350
+ action: "bridge-task-assigned",
351
+ taskId: "task-1",
352
+ assignee: "gsd-executor",
353
+ agentId: "gsd-executor",
354
+ }, mockClient);
355
+ expect(mockConnection.send).toHaveBeenCalledWith(
356
+ { scope: "swarm:test" },
357
+ { type: "task.assigned", taskId: "task-1", agentId: "gsd-executor", _origin: "gsd-executor" },
358
+ { relationship: "broadcast" }
359
+ );
360
+ });
361
+
362
+ it("responds {ok: true} without connection", async () => {
363
+ const nullHandler = createCommandHandler(null, "swarm:test", registeredAgents);
364
+ await nullHandler({
365
+ action: "bridge-task-assigned",
366
+ taskId: "t-1",
367
+ assignee: "worker",
368
+ }, mockClient);
369
+ const written = JSON.parse(mockClient.write.mock.calls[0][0]);
370
+ expect(written.ok).toBe(true);
371
+ });
372
+ });
373
+
250
374
  // ── State ─────────────────────────────────────────────────────────────
251
375
 
252
376
  describe("state", () => {
@@ -0,0 +1,80 @@
1
+ import { describe, it, expect } from "vitest";
2
+ import { parseSkillTreeExtension } from "../skilltree-client.mjs";
3
+
4
+ describe("skilltree-client", () => {
5
+ describe("parseSkillTreeExtension", () => {
6
+ it("returns empty defaults and roles for manifest without skilltree", () => {
7
+ const result = parseSkillTreeExtension({ name: "test", roles: ["a"] });
8
+ expect(result).toEqual({ defaults: {}, roles: {} });
9
+ });
10
+
11
+ it("returns empty defaults and roles for null manifest", () => {
12
+ const result = parseSkillTreeExtension(null);
13
+ expect(result).toEqual({ defaults: {}, roles: {} });
14
+ });
15
+
16
+ it("returns empty defaults and roles for undefined manifest", () => {
17
+ const result = parseSkillTreeExtension(undefined);
18
+ expect(result).toEqual({ defaults: {}, roles: {} });
19
+ });
20
+
21
+ it("extracts defaults from skilltree extension", () => {
22
+ const manifest = {
23
+ name: "test",
24
+ roles: ["a", "b"],
25
+ skilltree: {
26
+ defaults: { profile: "implementation", maxSkills: 6 },
27
+ },
28
+ };
29
+ const result = parseSkillTreeExtension(manifest);
30
+ expect(result.defaults).toEqual({ profile: "implementation", maxSkills: 6 });
31
+ expect(result.roles).toEqual({});
32
+ });
33
+
34
+ it("extracts per-role overrides from skilltree extension", () => {
35
+ const manifest = {
36
+ name: "test",
37
+ roles: ["orchestrator", "executor", "verifier"],
38
+ skilltree: {
39
+ defaults: { profile: "implementation" },
40
+ roles: {
41
+ orchestrator: { profile: "code-review" },
42
+ executor: { profile: "implementation", tags: ["development"] },
43
+ verifier: { profile: "testing" },
44
+ },
45
+ },
46
+ };
47
+ const result = parseSkillTreeExtension(manifest);
48
+ expect(result.defaults).toEqual({ profile: "implementation" });
49
+ expect(result.roles.orchestrator).toEqual({ profile: "code-review" });
50
+ expect(result.roles.executor).toEqual({ profile: "implementation", tags: ["development"] });
51
+ expect(result.roles.verifier).toEqual({ profile: "testing" });
52
+ });
53
+
54
+ it("handles skilltree extension with only roles, no defaults", () => {
55
+ const manifest = {
56
+ name: "test",
57
+ roles: ["a"],
58
+ skilltree: {
59
+ roles: { a: { profile: "debugging" } },
60
+ },
61
+ };
62
+ const result = parseSkillTreeExtension(manifest);
63
+ expect(result.defaults).toEqual({});
64
+ expect(result.roles.a).toEqual({ profile: "debugging" });
65
+ });
66
+
67
+ it("handles skilltree extension with only defaults, no roles", () => {
68
+ const manifest = {
69
+ name: "test",
70
+ roles: ["a"],
71
+ skilltree: {
72
+ defaults: { tags: ["typescript"] },
73
+ },
74
+ };
75
+ const result = parseSkillTreeExtension(manifest);
76
+ expect(result.defaults).toEqual({ tags: ["typescript"] });
77
+ expect(result.roles).toEqual({});
78
+ });
79
+ });
80
+ });