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
@@ -0,0 +1,228 @@
1
+ /**
2
+ * StoreGraph integration tests (requires node:sqlite)
3
+ *
4
+ * Run with: npx tsx --test src/store/__tests__/store-graph.test.ts
5
+ */
6
+
7
+ import { describe, it, before, after } from "node:test";
8
+ import assert from "node:assert/strict";
9
+ import fs from "node:fs/promises";
10
+ import path from "node:path";
11
+ import os from "node:os";
12
+
13
+ import { StoreGraph } from "../store-graph.js";
14
+ import type { StoreManifest } from "../manifest.js";
15
+
16
+ describe("StoreGraph", () => {
17
+ let tmpDir: string;
18
+ let storeADir: string;
19
+ let storeBDir: string;
20
+ let storeCDir: string;
21
+
22
+ before(async () => {
23
+ tmpDir = await fs.mkdtemp(path.join(os.tmpdir(), "minimem-storegraph-test-"));
24
+ storeADir = path.join(tmpDir, "store-a");
25
+ storeBDir = path.join(tmpDir, "store-b");
26
+ storeCDir = path.join(tmpDir, "store-c");
27
+
28
+ // Create store directories with minimal structure
29
+ for (const dir of [storeADir, storeBDir, storeCDir]) {
30
+ await fs.mkdir(path.join(dir, ".minimem"), { recursive: true });
31
+ await fs.writeFile(path.join(dir, "MEMORY.md"), `# Memory\n\nTest content for ${path.basename(dir)}\n`);
32
+ await fs.writeFile(
33
+ path.join(dir, ".minimem", "config.json"),
34
+ JSON.stringify({ embedding: { provider: "none" } }),
35
+ );
36
+ }
37
+
38
+ // Store C links to A and B
39
+ await fs.writeFile(
40
+ path.join(storeCDir, ".minimem", "links.json"),
41
+ JSON.stringify({ links: ["store-a", "store-b"] }),
42
+ );
43
+ });
44
+
45
+ after(async () => {
46
+ await fs.rm(tmpDir, { recursive: true, force: true });
47
+ });
48
+
49
+ function createManifest(): StoreManifest {
50
+ return {
51
+ stores: {
52
+ "store-a": { path: storeADir },
53
+ "store-b": { path: storeBDir, remote: "git@example.com:store-b.git" },
54
+ "store-c": { path: storeCDir },
55
+ },
56
+ };
57
+ }
58
+
59
+ function makeConfigFactory() {
60
+ return async (memoryDir: string) => ({
61
+ memoryDir,
62
+ embedding: { provider: "none" as const },
63
+ watch: { enabled: false },
64
+ });
65
+ }
66
+
67
+ it("creates from manifest", () => {
68
+ const manifest = createManifest();
69
+ const graph = StoreGraph.fromManifest(manifest);
70
+ assert.deepStrictEqual(
71
+ Object.keys(graph.getManifest().stores).sort(),
72
+ ["store-a", "store-b", "store-c"],
73
+ );
74
+ });
75
+
76
+ it("resolves a store with its linked stores", async () => {
77
+ const manifest = createManifest();
78
+ const graph = StoreGraph.fromManifest(manifest, {
79
+ configFactory: makeConfigFactory(),
80
+ });
81
+
82
+ try {
83
+ const instances = await graph.resolve("store-c");
84
+ assert.strictEqual(instances.length, 3);
85
+ assert.strictEqual(instances[0].name, "store-c");
86
+ assert.deepStrictEqual(
87
+ instances.map((i) => i.name).sort(),
88
+ ["store-a", "store-b", "store-c"],
89
+ );
90
+ } finally {
91
+ await graph.close();
92
+ }
93
+ });
94
+
95
+ it("resolves a store with no links as single instance", async () => {
96
+ const manifest = createManifest();
97
+ const graph = StoreGraph.fromManifest(manifest, {
98
+ configFactory: makeConfigFactory(),
99
+ });
100
+
101
+ try {
102
+ const instances = await graph.resolve("store-a");
103
+ assert.strictEqual(instances.length, 1);
104
+ assert.strictEqual(instances[0].name, "store-a");
105
+ } finally {
106
+ await graph.close();
107
+ }
108
+ });
109
+
110
+ it("resolves by directory path", async () => {
111
+ const manifest = createManifest();
112
+ const graph = StoreGraph.fromManifest(manifest, {
113
+ configFactory: makeConfigFactory(),
114
+ });
115
+
116
+ try {
117
+ const instances = await graph.resolveByPath(storeCDir);
118
+ assert.strictEqual(instances.length, 3);
119
+ assert.strictEqual(instances[0].name, "store-c");
120
+ } finally {
121
+ await graph.close();
122
+ }
123
+ });
124
+
125
+ it("resolves unknown directory as standalone", async () => {
126
+ const unknownDir = path.join(tmpDir, "unknown");
127
+ await fs.mkdir(path.join(unknownDir, ".minimem"), { recursive: true });
128
+ await fs.writeFile(path.join(unknownDir, "MEMORY.md"), "# Memory\n");
129
+ await fs.writeFile(
130
+ path.join(unknownDir, ".minimem", "config.json"),
131
+ JSON.stringify({ embedding: { provider: "none" } }),
132
+ );
133
+
134
+ const manifest = createManifest();
135
+ const graph = StoreGraph.fromManifest(manifest, {
136
+ configFactory: makeConfigFactory(),
137
+ });
138
+
139
+ try {
140
+ const instances = await graph.resolveByPath(unknownDir);
141
+ assert.strictEqual(instances.length, 1);
142
+ assert.strictEqual(instances[0].memoryDir, unknownDir);
143
+ } finally {
144
+ await graph.close();
145
+ }
146
+ });
147
+
148
+ it("throws for unknown store name", async () => {
149
+ const manifest = createManifest();
150
+ const graph = StoreGraph.fromManifest(manifest, {
151
+ configFactory: makeConfigFactory(),
152
+ });
153
+
154
+ try {
155
+ await assert.rejects(
156
+ () => graph.resolve("nonexistent"),
157
+ { message: /Store "nonexistent" not found/ },
158
+ );
159
+ } finally {
160
+ await graph.close();
161
+ }
162
+ });
163
+
164
+ it("skips unavailable linked stores gracefully", async () => {
165
+ const manifest: StoreManifest = {
166
+ stores: {
167
+ "store-a": { path: storeADir },
168
+ "store-b": { path: "/nonexistent/path" },
169
+ "store-c": { path: storeCDir },
170
+ },
171
+ };
172
+
173
+ const graph = StoreGraph.fromManifest(manifest, {
174
+ configFactory: makeConfigFactory(),
175
+ });
176
+
177
+ try {
178
+ const instances = await graph.resolve("store-c");
179
+ const names = instances.map((i) => i.name);
180
+ assert.ok(names.includes("store-c"));
181
+ assert.ok(names.includes("store-a"));
182
+ assert.ok(!names.includes("store-b"));
183
+ } finally {
184
+ await graph.close();
185
+ }
186
+ });
187
+
188
+ it("lists stores with their info", async () => {
189
+ const manifest = createManifest();
190
+ const graph = StoreGraph.fromManifest(manifest);
191
+
192
+ const stores = await graph.listStores();
193
+ assert.strictEqual(stores.length, 3);
194
+
195
+ const storeC = stores.find((s) => s.name === "store-c");
196
+ assert.ok(storeC);
197
+ assert.deepStrictEqual(storeC.links, ["store-a", "store-b"]);
198
+
199
+ const storeA = stores.find((s) => s.name === "store-a");
200
+ assert.ok(storeA);
201
+ assert.deepStrictEqual(storeA.links, []);
202
+ });
203
+
204
+ it("caches resolved instances", async () => {
205
+ const manifest = createManifest();
206
+ let createCount = 0;
207
+ const graph = StoreGraph.fromManifest(manifest, {
208
+ configFactory: async (memoryDir) => {
209
+ createCount++;
210
+ return {
211
+ memoryDir,
212
+ embedding: { provider: "none" as const },
213
+ watch: { enabled: false },
214
+ };
215
+ },
216
+ });
217
+
218
+ try {
219
+ await graph.resolve("store-c");
220
+ const firstCount = createCount;
221
+
222
+ await graph.resolve("store-a");
223
+ assert.strictEqual(createCount, firstCount);
224
+ } finally {
225
+ await graph.close();
226
+ }
227
+ });
228
+ });
@@ -0,0 +1,27 @@
1
+ export {
2
+ StoreGraph,
3
+ type StoreGraphOptions,
4
+ type ResolvedStore,
5
+ } from "./store-graph.js";
6
+
7
+ export {
8
+ loadManifest,
9
+ saveManifest,
10
+ loadStoreLinks,
11
+ saveStoreLinks,
12
+ resolveStore,
13
+ resolveStoreName,
14
+ getLinkedStoreNames,
15
+ getManifestPath,
16
+ type StoreManifest,
17
+ type StoreDefinition,
18
+ type StoreLinks,
19
+ } from "./manifest.js";
20
+
21
+ export {
22
+ materializeStore,
23
+ getRemoteCacheDir,
24
+ listCachedStores,
25
+ clearStoreCache,
26
+ type MaterializeResult,
27
+ } from "./materialize.js";
@@ -0,0 +1,203 @@
1
+ /**
2
+ * Store manifest: defines known stores and their relationships.
3
+ *
4
+ * Manifests can live in two places:
5
+ * 1. Global: ~/.config/minimem/stores.json (user-wide registry)
6
+ * 2. Per-store: .minimem/links.json (declares that store's dependencies)
7
+ *
8
+ * The global manifest is the source of truth for store metadata (path, remote).
9
+ * Per-store link files declare which other stores a store depends on.
10
+ */
11
+
12
+ import fs from "node:fs/promises";
13
+ import fsSync from "node:fs";
14
+ import path from "node:path";
15
+ import os from "node:os";
16
+
17
+ /**
18
+ * A single store definition in the global manifest.
19
+ */
20
+ export type StoreDefinition = {
21
+ /** Absolute path to the store's memory directory */
22
+ path: string;
23
+ /** Git remote URL for remote materialization */
24
+ remote?: string;
25
+ /** Human-readable description */
26
+ description?: string;
27
+ };
28
+
29
+ /**
30
+ * Global store manifest (~/.config/minimem/stores.json)
31
+ */
32
+ export type StoreManifest = {
33
+ stores: Record<string, StoreDefinition>;
34
+ };
35
+
36
+ /**
37
+ * Per-store links file (.minimem/links.json or .swarm/minimem/links.json)
38
+ * Declares which other stores this store depends on.
39
+ */
40
+ export type StoreLinks = {
41
+ /** Store names that this store links to (depth-1 only) */
42
+ links: string[];
43
+ };
44
+
45
+ const GLOBAL_MANIFEST_PATH = path.join(
46
+ os.homedir(),
47
+ ".config",
48
+ "minimem",
49
+ "stores.json",
50
+ );
51
+
52
+ const LINKS_FILENAME = "links.json";
53
+
54
+ /**
55
+ * Load the global store manifest.
56
+ * Returns an empty manifest if the file doesn't exist.
57
+ */
58
+ export async function loadManifest(
59
+ manifestPath?: string,
60
+ ): Promise<StoreManifest> {
61
+ const filePath = manifestPath ?? GLOBAL_MANIFEST_PATH;
62
+ try {
63
+ const content = await fs.readFile(filePath, "utf-8");
64
+ const parsed = JSON.parse(content) as StoreManifest;
65
+ // Expand ~ in paths
66
+ for (const [name, def] of Object.entries(parsed.stores ?? {})) {
67
+ parsed.stores[name] = {
68
+ ...def,
69
+ path: expandHome(def.path),
70
+ };
71
+ }
72
+ return { stores: parsed.stores ?? {} };
73
+ } catch {
74
+ return { stores: {} };
75
+ }
76
+ }
77
+
78
+ /**
79
+ * Save the global store manifest.
80
+ */
81
+ export async function saveManifest(
82
+ manifest: StoreManifest,
83
+ manifestPath?: string,
84
+ ): Promise<void> {
85
+ const filePath = manifestPath ?? GLOBAL_MANIFEST_PATH;
86
+ await fs.mkdir(path.dirname(filePath), { recursive: true });
87
+ await fs.writeFile(filePath, JSON.stringify(manifest, null, 2), "utf-8");
88
+ }
89
+
90
+ /**
91
+ * Load per-store links from a memory directory.
92
+ * Checks .minimem/links.json, .swarm/minimem/links.json, and links.json (contained layout).
93
+ */
94
+ export async function loadStoreLinks(memoryDir: string): Promise<StoreLinks> {
95
+ const candidates = [
96
+ path.join(memoryDir, ".minimem", LINKS_FILENAME),
97
+ path.join(memoryDir, ".swarm", "minimem", LINKS_FILENAME),
98
+ path.join(memoryDir, LINKS_FILENAME),
99
+ ];
100
+
101
+ for (const candidate of candidates) {
102
+ try {
103
+ const content = await fs.readFile(candidate, "utf-8");
104
+ const parsed = JSON.parse(content) as StoreLinks;
105
+ return { links: parsed.links ?? [] };
106
+ } catch {
107
+ continue;
108
+ }
109
+ }
110
+
111
+ return { links: [] };
112
+ }
113
+
114
+ /**
115
+ * Save per-store links to a memory directory.
116
+ * Writes to the first config subdirectory that exists.
117
+ */
118
+ export async function saveStoreLinks(
119
+ memoryDir: string,
120
+ links: StoreLinks,
121
+ ): Promise<void> {
122
+ // Find existing config subdirectory
123
+ const candidates = [
124
+ path.join(memoryDir, ".minimem"),
125
+ path.join(memoryDir, ".swarm", "minimem"),
126
+ memoryDir, // contained layout
127
+ ];
128
+
129
+ let targetDir = candidates[0]; // default to .minimem
130
+ for (const candidate of candidates) {
131
+ if (fsSync.existsSync(candidate)) {
132
+ targetDir = candidate;
133
+ break;
134
+ }
135
+ }
136
+
137
+ await fs.mkdir(targetDir, { recursive: true });
138
+ await fs.writeFile(
139
+ path.join(targetDir, LINKS_FILENAME),
140
+ JSON.stringify(links, null, 2),
141
+ "utf-8",
142
+ );
143
+ }
144
+
145
+ /**
146
+ * Resolve store name to its definition.
147
+ * Looks up the store in the global manifest.
148
+ */
149
+ export function resolveStore(
150
+ manifest: StoreManifest,
151
+ storeName: string,
152
+ ): StoreDefinition | null {
153
+ return manifest.stores[storeName] ?? null;
154
+ }
155
+
156
+ /**
157
+ * Resolve a directory path to its store name in the manifest.
158
+ * Returns the first store whose path matches.
159
+ */
160
+ export function resolveStoreName(
161
+ manifest: StoreManifest,
162
+ dirPath: string,
163
+ ): string | null {
164
+ const resolved = path.resolve(dirPath);
165
+ for (const [name, def] of Object.entries(manifest.stores)) {
166
+ if (path.resolve(def.path) === resolved) {
167
+ return name;
168
+ }
169
+ }
170
+ return null;
171
+ }
172
+
173
+ /**
174
+ * Get all linked store names for a given store (depth 1 only).
175
+ * Merges links from the global manifest and per-store links file.
176
+ */
177
+ export async function getLinkedStoreNames(
178
+ manifest: StoreManifest,
179
+ storeName: string,
180
+ ): Promise<string[]> {
181
+ const storeDef = manifest.stores[storeName];
182
+ if (!storeDef) return [];
183
+
184
+ // Load per-store links
185
+ const storeLinks = await loadStoreLinks(storeDef.path);
186
+
187
+ // Deduplicate
188
+ return [...new Set(storeLinks.links)];
189
+ }
190
+
191
+ /**
192
+ * Get the default global manifest path.
193
+ */
194
+ export function getManifestPath(): string {
195
+ return GLOBAL_MANIFEST_PATH;
196
+ }
197
+
198
+ function expandHome(filePath: string): string {
199
+ if (filePath.startsWith("~/")) {
200
+ return path.join(os.homedir(), filePath.slice(2));
201
+ }
202
+ return filePath;
203
+ }
@@ -0,0 +1,185 @@
1
+ /**
2
+ * Store materialization strategies.
3
+ *
4
+ * Two strategies for making a linked store accessible:
5
+ *
6
+ * A. Remote materialization: clone/fetch a git remote into a cache directory
7
+ * Used when the store isn't available locally.
8
+ *
9
+ * B. Symlink materialization: create temporary symlinks to a local store
10
+ * Used when the store is already present on disk.
11
+ * Symlinks go into a temp directory with unique subdirectory names
12
+ * to avoid path collisions between stores.
13
+ */
14
+
15
+ import fs from "node:fs/promises";
16
+ import fsSync from "node:fs";
17
+ import path from "node:path";
18
+ import os from "node:os";
19
+ import { execFile } from "node:child_process";
20
+ import { promisify } from "node:util";
21
+
22
+ import type { StoreDefinition } from "./manifest.js";
23
+
24
+ const execFileAsync = promisify(execFile);
25
+
26
+ const CACHE_BASE = path.join(os.homedir(), ".cache", "minimem", "stores");
27
+
28
+ export type MaterializeResult = {
29
+ /** The directory path to use for creating a Minimem instance */
30
+ path: string;
31
+ /** How the store was materialized */
32
+ strategy: "local" | "symlink" | "remote";
33
+ /** Cleanup function to call when done (removes symlinks, etc.) */
34
+ cleanup: () => Promise<void>;
35
+ };
36
+
37
+ /**
38
+ * Materialize a store so it can be accessed by a Minimem instance.
39
+ *
40
+ * Resolution order:
41
+ * 1. If the store path exists locally → use directly (no materialization needed)
42
+ * 2. If the store has a remote → clone/fetch into cache
43
+ * 3. Otherwise → return null (store unavailable)
44
+ */
45
+ export async function materializeStore(
46
+ storeName: string,
47
+ storeDef: StoreDefinition,
48
+ opts?: {
49
+ /** Force refresh from remote even if cache exists */
50
+ refresh?: boolean;
51
+ },
52
+ ): Promise<MaterializeResult | null> {
53
+ // Strategy B: local store exists — use symlink to temp dir
54
+ if (fsSync.existsSync(storeDef.path)) {
55
+ return materializeLocal(storeName, storeDef.path);
56
+ }
57
+
58
+ // Strategy A: remote store — clone/fetch into cache
59
+ if (storeDef.remote) {
60
+ return materializeRemote(storeName, storeDef, opts?.refresh ?? false);
61
+ }
62
+
63
+ // Store is unavailable
64
+ return null;
65
+ }
66
+
67
+ /**
68
+ * Strategy B: symlink materialization for local stores.
69
+ *
70
+ * Creates a temporary directory with a uniquely-named subdirectory
71
+ * containing a symlink to the source store. This avoids collisions
72
+ * when multiple stores are materialized simultaneously.
73
+ */
74
+ async function materializeLocal(
75
+ storeName: string,
76
+ storePath: string,
77
+ ): Promise<MaterializeResult> {
78
+ // Create temp dir: /tmp/minimem-stores-<random>/<storeName>/
79
+ const tmpBase = await fs.mkdtemp(
80
+ path.join(os.tmpdir(), "minimem-stores-"),
81
+ );
82
+ const symlinkDir = path.join(tmpBase, storeName);
83
+
84
+ // Create symlink: tmpBase/<storeName> → storePath
85
+ await fs.symlink(storePath, symlinkDir, "dir");
86
+
87
+ return {
88
+ // The Minimem instance should use the original path for its DB,
89
+ // but the symlink path can be used for discovery/resolution.
90
+ // We return the original path since Minimem works directly with it.
91
+ path: storePath,
92
+ strategy: "symlink",
93
+ cleanup: async () => {
94
+ try {
95
+ await fs.unlink(symlinkDir);
96
+ await fs.rmdir(tmpBase);
97
+ } catch {
98
+ // Best-effort cleanup
99
+ }
100
+ },
101
+ };
102
+ }
103
+
104
+ /**
105
+ * Strategy A: remote materialization via git clone/fetch.
106
+ *
107
+ * Clones the remote into ~/.cache/minimem/stores/<storeName>/
108
+ * On subsequent calls, does a git fetch + reset to update.
109
+ */
110
+ async function materializeRemote(
111
+ storeName: string,
112
+ storeDef: StoreDefinition,
113
+ refresh: boolean,
114
+ ): Promise<MaterializeResult | null> {
115
+ const cacheDir = path.join(CACHE_BASE, storeName);
116
+
117
+ try {
118
+ if (fsSync.existsSync(path.join(cacheDir, ".git"))) {
119
+ // Cache exists — optionally refresh
120
+ if (refresh) {
121
+ await gitFetch(cacheDir);
122
+ }
123
+ } else {
124
+ // Clone fresh
125
+ await gitClone(storeDef.remote!, cacheDir);
126
+ }
127
+
128
+ return {
129
+ path: cacheDir,
130
+ strategy: "remote",
131
+ cleanup: async () => {
132
+ // Remote cache is persistent — no cleanup needed per session.
133
+ // Users can manually clear ~/.cache/minimem/stores/ if desired.
134
+ },
135
+ };
136
+ } catch {
137
+ // Git operation failed — store unavailable
138
+ return null;
139
+ }
140
+ }
141
+
142
+ async function gitClone(remote: string, targetDir: string): Promise<void> {
143
+ await fs.mkdir(path.dirname(targetDir), { recursive: true });
144
+ await execFileAsync("git", ["clone", "--depth", "1", remote, targetDir], {
145
+ timeout: 60_000,
146
+ });
147
+ }
148
+
149
+ async function gitFetch(cacheDir: string): Promise<void> {
150
+ await execFileAsync("git", ["fetch", "--depth", "1", "origin"], {
151
+ cwd: cacheDir,
152
+ timeout: 60_000,
153
+ });
154
+ await execFileAsync("git", ["reset", "--hard", "origin/HEAD"], {
155
+ cwd: cacheDir,
156
+ timeout: 30_000,
157
+ });
158
+ }
159
+
160
+ /**
161
+ * Get the cache directory for remote stores.
162
+ */
163
+ export function getRemoteCacheDir(): string {
164
+ return CACHE_BASE;
165
+ }
166
+
167
+ /**
168
+ * List cached remote stores.
169
+ */
170
+ export async function listCachedStores(): Promise<string[]> {
171
+ try {
172
+ const entries = await fs.readdir(CACHE_BASE);
173
+ return entries;
174
+ } catch {
175
+ return [];
176
+ }
177
+ }
178
+
179
+ /**
180
+ * Clear the cache for a specific remote store.
181
+ */
182
+ export async function clearStoreCache(storeName: string): Promise<void> {
183
+ const cacheDir = path.join(CACHE_BASE, storeName);
184
+ await fs.rm(cacheDir, { recursive: true, force: true });
185
+ }