@tekmidian/pai 0.54.1 → 0.56.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (251) hide show
  1. package/dist/{aibroker-client-CNfIBrl5.mjs → aibroker-client-B8Wtasez.mjs} +1 -1
  2. package/dist/{aibroker-client-DzkTpVjx.mjs → aibroker-client-BK1h--W_.mjs} +3 -3
  3. package/dist/{aibroker-client-DzkTpVjx.mjs.map → aibroker-client-BK1h--W_.mjs.map} +1 -1
  4. package/dist/{indexer-backend-CdvCJCkD.mjs → async-CLvuY7b-.mjs} +7 -7
  5. package/dist/async-CLvuY7b-.mjs.map +1 -0
  6. package/dist/auto-route-CF8WNaoy.mjs +100 -0
  7. package/dist/auto-route-CF8WNaoy.mjs.map +1 -0
  8. package/dist/{chain-CZKlI0hq.mjs → chain-26f7uGUs.mjs} +4 -4
  9. package/dist/{chain-CZKlI0hq.mjs.map → chain-26f7uGUs.mjs.map} +1 -1
  10. package/dist/cli/index.mjs +22 -23
  11. package/dist/cli/index.mjs.map +1 -1
  12. package/dist/cli/program.d.mts.map +1 -1
  13. package/dist/cli/program.mjs +22 -23
  14. package/dist/{clusters-Cw_P6lO-.mjs → clusters-Ci1mseh2.mjs} +4 -36
  15. package/dist/clusters-Ci1mseh2.mjs.map +1 -0
  16. package/dist/{config-B3EPzRjf.mjs → config-B93YS8tP.mjs} +3 -3
  17. package/dist/{config-B3EPzRjf.mjs.map → config-B93YS8tP.mjs.map} +1 -1
  18. package/dist/{config-Bn9jpblO.mjs → config-D1omG_UU.mjs} +3 -3
  19. package/dist/{config-Bn9jpblO.mjs.map → config-D1omG_UU.mjs.map} +1 -1
  20. package/dist/{config-RjeVGJ7K.mjs → config-DCYWFaEi.mjs} +1 -1
  21. package/dist/{context-handover-cache-CXyH6QsC.mjs → context-handover-cache-k2kWbE6G.mjs} +82 -99
  22. package/dist/context-handover-cache-k2kWbE6G.mjs.map +1 -0
  23. package/dist/daemon/index.mjs +18 -24
  24. package/dist/daemon/index.mjs.map +1 -1
  25. package/dist/daemon-BOWol-LF.mjs +19 -0
  26. package/dist/{daemon-aJTxzg20.mjs → daemon-BsuM0UdV.mjs} +97 -466
  27. package/dist/daemon-BsuM0UdV.mjs.map +1 -0
  28. package/dist/daemon-mcp/index.mjs +10 -10
  29. package/dist/daemon-mcp/index.mjs.map +1 -1
  30. package/dist/{detector-DWXkeTlk.mjs → detector-3g7XuU1r.mjs} +5 -12
  31. package/dist/detector-3g7XuU1r.mjs.map +1 -0
  32. package/dist/detector-DgDn56j-.mjs +3 -0
  33. package/dist/{embeddings-ClqKtLZ8.mjs → embeddings-BNv3TQC5.mjs} +1 -1
  34. package/dist/{embeddings-DvRKcQyt.mjs → embeddings-DfrYNIng.mjs} +1 -1
  35. package/dist/{embeddings-DvRKcQyt.mjs.map → embeddings-DfrYNIng.mjs.map} +1 -1
  36. package/dist/factory-C9wGUe30.mjs +3 -0
  37. package/dist/factory-DiuntvwG.mjs +250 -0
  38. package/dist/factory-DiuntvwG.mjs.map +1 -0
  39. package/dist/{fallback-CCFIpPha.mjs → fallback-CVe3sb94.mjs} +192 -134
  40. package/dist/fallback-CVe3sb94.mjs.map +1 -0
  41. package/dist/federation-db-CJDw_Pwq.mjs +3 -0
  42. package/dist/{db-DJ8rk0VM.mjs → federation-db-w8e-sKjp.mjs} +5 -5
  43. package/dist/federation-db-w8e-sKjp.mjs.map +1 -0
  44. package/dist/{helpers-crDEr6S2.mjs → helpers-D9WkIf9R.mjs} +1 -1
  45. package/dist/{helpers-crDEr6S2.mjs.map → helpers-D9WkIf9R.mjs.map} +1 -1
  46. package/dist/hooks/capture-all-events.mjs.map +1 -1
  47. package/dist/hooks/capture-session-summary.mjs.map +1 -1
  48. package/dist/hooks/capture-tool-output.mjs.map +1 -1
  49. package/dist/hooks/context-compression-hook.mjs.map +1 -1
  50. package/dist/hooks/load-core-context.mjs +5 -0
  51. package/dist/hooks/load-core-context.mjs.map +2 -2
  52. package/dist/hooks/load-project-context.mjs.map +1 -1
  53. package/dist/hooks/post-compact-inject.mjs.map +1 -1
  54. package/dist/hooks/route-agents-to-worker.mjs.map +1 -1
  55. package/dist/hooks/route-edits-to-worker.mjs +1 -1
  56. package/dist/hooks/route-edits-to-worker.mjs.map +2 -2
  57. package/dist/hooks/security-validator.mjs +8367 -19
  58. package/dist/hooks/security-validator.mjs.map +4 -4
  59. package/dist/hooks/stop-hook.mjs.map +2 -2
  60. package/dist/hooks/whisper-rules.mjs.map +2 -2
  61. package/dist/hooks/worker-proxy.mjs.map +1 -1
  62. package/dist/hooks/worker-status-line.mjs.map +2 -2
  63. package/dist/hooks/worker-supervision.mjs.map +1 -1
  64. package/dist/index.d.mts +327 -100
  65. package/dist/index.d.mts.map +1 -1
  66. package/dist/index.mjs +7 -11
  67. package/dist/indexer-backend-Cc-_NK3q.mjs +1 -0
  68. package/dist/indexer-backend-DcDtin1O.mjs +5 -0
  69. package/dist/{ipc-client-C6NryMMb.mjs → ipc-client-Dasm515c.mjs} +2 -2
  70. package/dist/{ipc-client-C6NryMMb.mjs.map → ipc-client-Dasm515c.mjs.map} +1 -1
  71. package/dist/kg-entity-Cj8Q6YmU.mjs +29 -0
  72. package/dist/kg-entity-Cj8Q6YmU.mjs.map +1 -0
  73. package/dist/{latent-ideas-BfL4nCQ3.mjs → latent-ideas-BRy6HNwg.mjs} +4 -4
  74. package/dist/{latent-ideas-BfL4nCQ3.mjs.map → latent-ideas-BRy6HNwg.mjs.map} +1 -1
  75. package/dist/{link-boost-C5PaNdzm.mjs → link-boost-BptbAPuv.mjs} +1 -1
  76. package/dist/{link-boost-C5PaNdzm.mjs.map → link-boost-BptbAPuv.mjs.map} +1 -1
  77. package/dist/{main-resolver-Bt1f6jJc.mjs → main-resolver-Dx-pxic1.mjs} +56 -31
  78. package/dist/main-resolver-Dx-pxic1.mjs.map +1 -0
  79. package/dist/main-resolver-uDtK9p6e.mjs +7 -0
  80. package/dist/merge-BncjJTfb.mjs +3 -0
  81. package/dist/merge-xoyfZQ4W.mjs +6 -0
  82. package/dist/merge-xoyfZQ4W.mjs.map +1 -0
  83. package/dist/{migrate-CfERwXhY.mjs → migrate-BD7D8EEh.mjs} +21 -54
  84. package/dist/migrate-BD7D8EEh.mjs.map +1 -0
  85. package/dist/{neighborhood-CETR_P4y.mjs → neighborhood-D8uscnGy.mjs} +4 -25
  86. package/dist/neighborhood-D8uscnGy.mjs.map +1 -0
  87. package/dist/{note-context-DCcRWBoN.mjs → note-context-CgffaUMh.mjs} +3 -23
  88. package/dist/note-context-CgffaUMh.mjs.map +1 -0
  89. package/dist/{pai-home-ChAOupZc.mjs → pai-home-CeOIYltV.mjs} +2 -2
  90. package/dist/pai-home-CeOIYltV.mjs.map +1 -0
  91. package/dist/{pai-marker-B20KqhA8.mjs → pai-marker-D1MMswkz.mjs} +1 -1
  92. package/dist/{pai-marker-B20KqhA8.mjs.map → pai-marker-D1MMswkz.mjs.map} +1 -1
  93. package/dist/{planner-D3keWys5.mjs → planner-ye7wrrfB.mjs} +7 -7
  94. package/dist/{planner-D3keWys5.mjs.map → planner-ye7wrrfB.mjs.map} +1 -1
  95. package/dist/{postgres-CUSXWgTG.mjs → postgres-nvU5pShF.mjs} +863 -4
  96. package/dist/postgres-nvU5pShF.mjs.map +1 -0
  97. package/dist/{program-B3eRMhff.mjs → program-DuC1RcaO.mjs} +2268 -1402
  98. package/dist/program-DuC1RcaO.mjs.map +1 -0
  99. package/dist/query-feedback-COYCnvHQ.mjs +3 -0
  100. package/dist/{query-feedback-CTs_jx6i.mjs → query-feedback-xz5axTj3.mjs} +3 -3
  101. package/dist/query-feedback-xz5axTj3.mjs.map +1 -0
  102. package/dist/{db-Ddns80yf.mjs → registry-db-Bkpbp7Gf.mjs} +5 -5
  103. package/dist/registry-db-Bkpbp7Gf.mjs.map +1 -0
  104. package/dist/registry-db-DwXpf7yF.mjs +3 -0
  105. package/dist/registry-postgres-B07-hmn8.mjs +795 -0
  106. package/dist/registry-postgres-B07-hmn8.mjs.map +1 -0
  107. package/dist/registry-sqlite-CkGND_tm.mjs +593 -0
  108. package/dist/registry-sqlite-CkGND_tm.mjs.map +1 -0
  109. package/dist/{reranker-B5qUTamW.mjs → reranker-BS5gqGeF.mjs} +1 -1
  110. package/dist/{reranker-HHmhZk6v.mjs → reranker-CRVawoEh.mjs} +1 -1
  111. package/dist/{reranker-HHmhZk6v.mjs.map → reranker-CRVawoEh.mjs.map} +1 -1
  112. package/dist/router-BXOf_e9e.mjs +3 -0
  113. package/dist/{router-DBAZfE7X.mjs → router-BdxdsAvs.mjs} +2 -2
  114. package/dist/{router-DBAZfE7X.mjs.map → router-BdxdsAvs.mjs.map} +1 -1
  115. package/dist/{run-DLG25bzt.mjs → run-CqRvXGjd.mjs} +309 -49
  116. package/dist/run-CqRvXGjd.mjs.map +1 -0
  117. package/dist/{runtime-paths-B0P1TvUr.mjs → runtime-paths-BRwDeBEg.mjs} +1 -1
  118. package/dist/{runtime-paths-B0P1TvUr.mjs.map → runtime-paths-BRwDeBEg.mjs.map} +1 -1
  119. package/dist/search-BQHL5wDr.mjs +3 -0
  120. package/dist/search-BaTbyWjK.mjs +107 -0
  121. package/dist/search-BaTbyWjK.mjs.map +1 -0
  122. package/dist/{server-CGiHdaUp.mjs → server-CGuZFOeR.mjs} +4 -4
  123. package/dist/{server-CGiHdaUp.mjs.map → server-CGuZFOeR.mjs.map} +1 -1
  124. package/dist/{session-keepalive-DklS8VmU.mjs → session-keepalive-BQ6AQ9ez.mjs} +7 -7
  125. package/dist/{session-keepalive-DklS8VmU.mjs.map → session-keepalive-BQ6AQ9ez.mjs.map} +1 -1
  126. package/dist/skills/Plan/SKILL.md +1 -1
  127. package/dist/{sources-KBGEA_E6.mjs → sources-BBe3AZ-W.mjs} +8 -27
  128. package/dist/sources-BBe3AZ-W.mjs.map +1 -0
  129. package/dist/sqlite-D9p5a4qe.mjs +935 -0
  130. package/dist/sqlite-D9p5a4qe.mjs.map +1 -0
  131. package/dist/state-CwlGAKgF.mjs +3 -0
  132. package/dist/{state-DwrsOoMi.mjs → state-n13uPK5B.mjs} +6 -5
  133. package/dist/state-n13uPK5B.mjs.map +1 -0
  134. package/dist/{stop-words-BaMEGVeY.mjs → stop-words-ClCwO8mZ.mjs} +1 -1
  135. package/dist/{stop-words-BaMEGVeY.mjs.map → stop-words-ClCwO8mZ.mjs.map} +1 -1
  136. package/dist/{themes-BVoAnLZj.mjs → themes-B2v7RNnA.mjs} +3 -3
  137. package/dist/{themes-BVoAnLZj.mjs.map → themes-B2v7RNnA.mjs.map} +1 -1
  138. package/dist/{tools-BEqiJt_j.mjs → tools-Diro2s8J.mjs} +157 -398
  139. package/dist/tools-Diro2s8J.mjs.map +1 -0
  140. package/dist/{tools-BwsOTmjh.mjs → tools-KgL1rkPD.mjs} +4 -4
  141. package/dist/{trace-Cy8quQTm.mjs → trace-BRflVyBi.mjs} +1 -1
  142. package/dist/{trace-Cy8quQTm.mjs.map → trace-BRflVyBi.mjs.map} +1 -1
  143. package/dist/{utils-BD8g8oSf.mjs → utils-BrX9FLeK.mjs} +1 -1
  144. package/dist/{utils-CJymHkYm.mjs → utils-C9HsYDpO.mjs} +1 -1
  145. package/dist/{utils-CJymHkYm.mjs.map → utils-C9HsYDpO.mjs.map} +1 -1
  146. package/dist/{vault-indexer-C5i-OAyr.mjs → vault-indexer-DHYBpRZ5.mjs} +2 -2
  147. package/dist/{vault-indexer-C5i-OAyr.mjs.map → vault-indexer-DHYBpRZ5.mjs.map} +1 -1
  148. package/dist/{wakeup-Yk5lQ0jb.mjs → wakeup-D0uvqLsE.mjs} +95 -4
  149. package/dist/wakeup-D0uvqLsE.mjs.map +1 -0
  150. package/dist/work-queue-worker-DEbWsrDQ.mjs +11 -0
  151. package/dist/{work-queue-worker-utPp4hUv.mjs → work-queue-worker-DufMOtrL.mjs} +9 -9
  152. package/dist/work-queue-worker-DufMOtrL.mjs.map +1 -0
  153. package/dist/{zettelkasten-DezbXghl.mjs → zettelkasten-BPxSOalU.mjs} +4 -4
  154. package/dist/{zettelkasten-DezbXghl.mjs.map → zettelkasten-BPxSOalU.mjs.map} +1 -1
  155. package/docker/init.sql +5 -1
  156. package/docs/commands/README.md +5 -0
  157. package/docs/commands/audit.md +1 -1
  158. package/docs/commands/backup.md +1 -1
  159. package/docs/commands/clear-names.md +1 -1
  160. package/docs/commands/config.md +4 -4
  161. package/docs/commands/daemon.md +1 -1
  162. package/docs/commands/db.md +15 -1
  163. package/docs/commands/end.md +1 -1
  164. package/docs/commands/help.md +1 -1
  165. package/docs/commands/hooks-db.md +47 -0
  166. package/docs/commands/identity.md +1 -1
  167. package/docs/commands/kg.md +1 -1
  168. package/docs/commands/launch.md +1 -1
  169. package/docs/commands/mcp.md +1 -1
  170. package/docs/commands/memory.md +1 -1
  171. package/docs/commands/notify.md +1 -1
  172. package/docs/commands/observation.md +1 -1
  173. package/docs/commands/obsidian.md +1 -1
  174. package/docs/commands/pause.md +1 -1
  175. package/docs/commands/project.md +1 -1
  176. package/docs/commands/projects.md +1 -1
  177. package/docs/commands/registry.md +1 -1
  178. package/docs/commands/restore.md +1 -1
  179. package/docs/commands/session.md +2 -2
  180. package/docs/commands/sessions.md +1 -1
  181. package/docs/commands/setup.md +1 -1
  182. package/docs/commands/shell-init.md +1 -1
  183. package/docs/commands/skill.md +1 -1
  184. package/docs/commands/task.md +1 -1
  185. package/docs/commands/topic.md +1 -1
  186. package/docs/commands/update.md +1 -1
  187. package/docs/commands/worker.md +1 -1
  188. package/docs/commands/zettel.md +1 -1
  189. package/docs/design/postgres-only.md +438 -0
  190. package/docs/design/registry-method-map.md +174 -0
  191. package/package.json +1 -1
  192. package/src/hooks/pre-compact.sh +2 -40
  193. package/src/hooks/session-hooks-db.test.ts +93 -0
  194. package/src/hooks/session-stop.sh +2 -26
  195. package/src/hooks/ts/lib/edit-gate.test.ts +9 -0
  196. package/src/hooks/ts/lib/edit-gate.ts +5 -2
  197. package/src/hooks/ts/lib/plutil-guard.test.ts +54 -0
  198. package/src/hooks/ts/lib/plutil-guard.ts +43 -0
  199. package/src/hooks/ts/lib/worker-git-guard.test.ts +134 -0
  200. package/src/hooks/ts/lib/worker-git-guard.ts +103 -0
  201. package/src/hooks/ts/pre-tool-use/security-validator.test.ts +68 -0
  202. package/src/hooks/ts/pre-tool-use/security-validator.ts +69 -21
  203. package/src/hooks/ts/session-start/load-core-context.ts +13 -0
  204. package/src/hooks/ts/session-start/session-start-worker-guard.test.ts +13 -0
  205. package/dist/auto-route-CcD0OWWB.mjs +0 -86
  206. package/dist/auto-route-CcD0OWWB.mjs.map +0 -1
  207. package/dist/clusters-Cw_P6lO-.mjs.map +0 -1
  208. package/dist/context-handover-cache-CXyH6QsC.mjs.map +0 -1
  209. package/dist/daemon-CknQ6YGZ.mjs +0 -25
  210. package/dist/daemon-aJTxzg20.mjs.map +0 -1
  211. package/dist/db-BOBfewFH.mjs +0 -3
  212. package/dist/db-DJ8rk0VM.mjs.map +0 -1
  213. package/dist/db-DbY2O8QO.mjs +0 -3
  214. package/dist/db-Ddns80yf.mjs.map +0 -1
  215. package/dist/detect-Bf2z-oKB.mjs +0 -86
  216. package/dist/detect-Bf2z-oKB.mjs.map +0 -1
  217. package/dist/detector-CkwkXuGh.mjs +0 -5
  218. package/dist/detector-DWXkeTlk.mjs.map +0 -1
  219. package/dist/factory-Ba8j9K7q.mjs +0 -3
  220. package/dist/factory-Bt7TWQje.mjs +0 -150
  221. package/dist/factory-Bt7TWQje.mjs.map +0 -1
  222. package/dist/fallback-CCFIpPha.mjs.map +0 -1
  223. package/dist/indexer-backend-CdvCJCkD.mjs.map +0 -1
  224. package/dist/kg-entity-BSW7ra8i.mjs +0 -176
  225. package/dist/kg-entity-BSW7ra8i.mjs.map +0 -1
  226. package/dist/main-resolver-Bt1f6jJc.mjs.map +0 -1
  227. package/dist/main-resolver-DYHhw5Ig.mjs +0 -7
  228. package/dist/migrate-CfERwXhY.mjs.map +0 -1
  229. package/dist/neighborhood-CETR_P4y.mjs.map +0 -1
  230. package/dist/note-context-DCcRWBoN.mjs.map +0 -1
  231. package/dist/pai-home-ChAOupZc.mjs.map +0 -1
  232. package/dist/postgres-CUSXWgTG.mjs.map +0 -1
  233. package/dist/program-B3eRMhff.mjs.map +0 -1
  234. package/dist/query-feedback-Bm6XvCmg.mjs +0 -3
  235. package/dist/query-feedback-CTs_jx6i.mjs.map +0 -1
  236. package/dist/router-CremHKTI.mjs +0 -3
  237. package/dist/run-DLG25bzt.mjs.map +0 -1
  238. package/dist/search-B69cQwDB.mjs +0 -303
  239. package/dist/search-B69cQwDB.mjs.map +0 -1
  240. package/dist/search-fb-5ovj4.mjs +0 -4
  241. package/dist/sources-KBGEA_E6.mjs.map +0 -1
  242. package/dist/sqlite-By6r2YhZ.mjs +0 -271
  243. package/dist/sqlite-By6r2YhZ.mjs.map +0 -1
  244. package/dist/state-Ca0ltLgI.mjs +0 -3
  245. package/dist/state-DwrsOoMi.mjs.map +0 -1
  246. package/dist/sync-Btcb6xA4.mjs +0 -310
  247. package/dist/sync-Btcb6xA4.mjs.map +0 -1
  248. package/dist/tools-BEqiJt_j.mjs.map +0 -1
  249. package/dist/wakeup-Yk5lQ0jb.mjs.map +0 -1
  250. package/dist/work-queue-worker-DsdJx-jn.mjs +0 -14
  251. package/dist/work-queue-worker-utPp4hUv.mjs.map +0 -1
@@ -1,9 +1,6 @@
1
- import { n as cosineSimilarity } from "./embeddings-DvRKcQyt.mjs";
2
- import { t as STOP_WORDS } from "./stop-words-BaMEGVeY.mjs";
3
- import { c as touchChunksLastAccessed, i as populateSlugs, o as searchMemoryHybrid } from "./search-B69cQwDB.mjs";
4
- import { r as formatDetectionJson, t as detectProject } from "./detect-Bf2z-oKB.mjs";
5
- import { t as buildWakeupContext } from "./wakeup-Yk5lQ0jb.mjs";
6
- import { a as kgContradictions, i as kgAdd, n as updateEntityFeedbackWeight, o as kgInvalidate, s as kgQuery, t as listKgEntities } from "./kg-entity-BSW7ra8i.mjs";
1
+ import { i as populateSlugs } from "./search-BaTbyWjK.mjs";
2
+ import { a as formatDetectionJson, r as detectProject, t as buildWakeupContext } from "./wakeup-D0uvqLsE.mjs";
3
+ import { n as cosineSimilarity } from "./embeddings-DfrYNIng.mjs";
7
4
  import { existsSync, readFileSync, statSync } from "node:fs";
8
5
  import { isAbsolute, join, resolve } from "node:path";
9
6
 
@@ -11,29 +8,24 @@ import { isAbsolute, join, resolve } from "node:path";
11
8
  /**
12
9
  * Shared types and project-row helpers used across all MCP tool handler modules.
13
10
  */
14
- function lookupProjectId(registryDb, slug) {
15
- const bySlug = registryDb.prepare("SELECT id FROM projects WHERE slug = ?").get(slug);
11
+ async function lookupProjectId(registry, slug) {
12
+ const bySlug = await registry.getProjectBySlug(slug);
16
13
  if (bySlug) return bySlug.id;
17
- const byAlias = registryDb.prepare("SELECT project_id FROM aliases WHERE alias = ?").get(slug);
18
- if (byAlias) return byAlias.project_id;
19
- return null;
14
+ return await registry.resolveAlias(slug) ?? null;
20
15
  }
21
- function detectProjectFromPath(registryDb, fsPath) {
16
+ async function detectProjectFromPath(registry, fsPath) {
22
17
  const resolved = resolve(fsPath);
23
- const exact = registryDb.prepare("SELECT id, slug, display_name, root_path, type, status, created_at, updated_at FROM projects WHERE root_path = ?").get(resolved);
18
+ const exact = await registry.getProjectByRootPath(resolved);
24
19
  if (exact) return exact;
25
- const all = registryDb.prepare("SELECT id, slug, display_name, root_path, type, status, created_at, updated_at FROM projects ORDER BY LENGTH(root_path) DESC").all();
20
+ const all = await registry.listProjectsByPathLengthDesc();
26
21
  for (const project of all) if (resolved.startsWith(project.root_path + "/") || resolved === project.root_path) return project;
27
22
  return null;
28
23
  }
29
- function formatProject(registryDb, project) {
30
- const sessionCount = registryDb.prepare("SELECT COUNT(*) AS n FROM sessions WHERE project_id = ?").get(project.id).n;
31
- const lastSession = registryDb.prepare("SELECT date FROM sessions WHERE project_id = ? ORDER BY date DESC LIMIT 1").get(project.id);
32
- const tags = registryDb.prepare(`SELECT t.name FROM tags t
33
- JOIN project_tags pt ON pt.tag_id = t.id
34
- WHERE pt.project_id = ?
35
- ORDER BY t.name`).all(project.id).map((r) => r.name);
36
- const aliases = registryDb.prepare("SELECT alias FROM aliases WHERE project_id = ? ORDER BY alias").all(project.id).map((r) => r.alias);
24
+ async function formatProject(registry, project) {
25
+ const sessionCount = await registry.countSessionsForProject(project.id);
26
+ const lastSessionDate = await registry.getMostRecentSessionDate(project.id);
27
+ const tags = await registry.listTagsForProject(project.id);
28
+ const aliases = await registry.listAliasesForProject(project.id);
37
29
  const lines = [
38
30
  `slug: ${project.slug}`,
39
31
  `display_name: ${project.display_name}`,
@@ -42,7 +34,7 @@ function formatProject(registryDb, project) {
42
34
  `status: ${project.status}`,
43
35
  `sessions: ${sessionCount}`
44
36
  ];
45
- if (lastSession) lines.push(`last_session: ${lastSession.date}`);
37
+ if (lastSessionDate) lines.push(`last_session: ${lastSessionDate}`);
46
38
  if (tags.length) lines.push(`tags: ${tags.join(", ")}`);
47
39
  if (aliases.length) lines.push(`aliases: ${aliases.join(", ")}`);
48
40
  if (project.obsidian_link) lines.push(`obsidian_link: ${project.obsidian_link}`);
@@ -55,10 +47,10 @@ function formatProject(registryDb, project) {
55
47
  /**
56
48
  * MCP tool handlers: memory_search, memory_get
57
49
  */
58
- async function toolMemorySearch(registryDb, federation, params, searchDefaults) {
50
+ async function toolMemorySearch(registry, federation, params, searchDefaults) {
59
51
  try {
60
- const projectIds = params.project ? (() => {
61
- const id = lookupProjectId(registryDb, params.project);
52
+ const projectIds = params.project ? await (async () => {
53
+ const id = await lookupProjectId(registry, params.project);
62
54
  return id != null ? [id] : [];
63
55
  })() : void 0;
64
56
  if (params.project && (!projectIds || projectIds.length === 0)) return {
@@ -76,10 +68,9 @@ async function toolMemorySearch(registryDb, federation, params, searchDefaults)
76
68
  maxResults: params.limit ?? searchDefaults?.defaultLimit ?? 5
77
69
  };
78
70
  let results;
79
- const isBackend = (x) => "backendType" in x;
80
- if (isBackend(federation)) if (mode === "keyword") results = await federation.searchKeyword(params.query, searchOpts);
71
+ if (mode === "keyword") results = await federation.searchKeyword(params.query, searchOpts);
81
72
  else if (mode === "semantic" || mode === "hybrid") {
82
- const { generateEmbedding } = await import("./embeddings-ClqKtLZ8.mjs");
73
+ const { generateEmbedding } = await import("./embeddings-BNv3TQC5.mjs");
83
74
  const queryEmbedding = await generateEmbedding(params.query, true);
84
75
  if (mode === "semantic") results = await federation.searchSemantic(queryEmbedding, searchOpts);
85
76
  else {
@@ -93,20 +84,10 @@ async function toolMemorySearch(registryDb, federation, params, searchDefaults)
93
84
  results = combineHybridResults(kwResults, semResults, searchOpts.maxResults ?? 10);
94
85
  }
95
86
  } else results = await federation.searchKeyword(params.query, searchOpts);
96
- else {
97
- const { searchMemory, searchMemorySemantic } = await import("./search-fb-5ovj4.mjs");
98
- if (mode === "keyword") results = searchMemory(federation, params.query, searchOpts);
99
- else if (mode === "semantic" || mode === "hybrid") {
100
- const { generateEmbedding } = await import("./embeddings-ClqKtLZ8.mjs");
101
- const queryEmbedding = await generateEmbedding(params.query, true);
102
- if (mode === "semantic") results = searchMemorySemantic(federation, queryEmbedding, searchOpts);
103
- else results = searchMemoryHybrid(federation, params.query, queryEmbedding, searchOpts);
104
- } else results = searchMemory(federation, params.query, searchOpts);
105
- }
106
87
  try {
107
- const { applyLinkBoost } = await import("./link-boost-C5PaNdzm.mjs");
88
+ const { applyLinkBoost } = await import("./link-boost-BptbAPuv.mjs");
108
89
  const paths = [...new Set(results.map((r) => r.path))];
109
- if (isBackend(federation) && paths.length > 1) {
90
+ if (paths.length > 1) {
110
91
  const edges = [];
111
92
  for (const p of paths) for (const l of await federation.getLinksToTarget(p)) {
112
93
  if (l.confidence && l.confidence !== "EXTRACTED") continue;
@@ -120,22 +101,19 @@ async function toolMemorySearch(registryDb, federation, params, searchDefaults)
120
101
  } catch {}
121
102
  try {
122
103
  const chunkIds = results.map((r) => r.chunkId).filter((id) => id != null);
123
- if (chunkIds.length > 0) {
124
- const rawDb = !isBackend(federation) ? federation : federation.getSqliteDb?.() ?? null;
125
- if (rawDb) touchChunksLastAccessed(rawDb, chunkIds);
126
- }
104
+ if (chunkIds.length > 0) await federation.touchChunksLastAccessed(chunkIds);
127
105
  } catch {}
128
106
  const shouldRerank = params.rerank ?? searchDefaults?.rerank ?? true;
129
107
  if (shouldRerank && results.length > 0) {
130
- const { rerankResults } = await import("./reranker-B5qUTamW.mjs");
108
+ const { rerankResults } = await import("./reranker-BS5gqGeF.mjs");
131
109
  results = await rerankResults(params.query, results, { topK: searchOpts.maxResults ?? 5 });
132
110
  }
133
111
  const recencyDays = params.recencyBoost ?? searchDefaults?.recencyBoostDays ?? 0;
134
112
  if (recencyDays > 0 && results.length > 0) {
135
- const { applyRecencyBoost } = await import("./search-fb-5ovj4.mjs");
113
+ const { applyRecencyBoost } = await import("./search-BQHL5wDr.mjs");
136
114
  results = applyRecencyBoost(results, recencyDays);
137
115
  }
138
- const withSlugs = populateSlugs(results, registryDb);
116
+ const withSlugs = await populateSlugs(results, registry);
139
117
  if (withSlugs.length === 0) return { content: [{
140
118
  type: "text",
141
119
  text: `No results found for query: "${params.query}" (mode: ${mode})`
@@ -151,7 +129,7 @@ async function toolMemorySearch(registryDb, federation, params, searchDefaults)
151
129
  return `${header}\n${raw.length > snippetLength ? raw.slice(0, snippetLength) + "..." : raw}`;
152
130
  }).join(useCompact ? "\n" : "\n\n---\n\n");
153
131
  try {
154
- const { saveQueryResult } = await import("./query-feedback-Bm6XvCmg.mjs");
132
+ const { saveQueryResult } = await import("./query-feedback-COYCnvHQ.mjs");
155
133
  saveQueryResult({
156
134
  query: params.query,
157
135
  timestamp: Date.now(),
@@ -175,9 +153,9 @@ async function toolMemorySearch(registryDb, federation, params, searchDefaults)
175
153
  };
176
154
  }
177
155
  }
178
- function toolMemoryGet(registryDb, params) {
156
+ async function toolMemoryGet(registry, params) {
179
157
  try {
180
- const projectId = lookupProjectId(registryDb, params.project);
158
+ const projectId = await lookupProjectId(registry, params.project);
181
159
  if (projectId == null) return {
182
160
  content: [{
183
161
  type: "text",
@@ -185,7 +163,7 @@ function toolMemoryGet(registryDb, params) {
185
163
  }],
186
164
  isError: true
187
165
  };
188
- const project = registryDb.prepare("SELECT root_path FROM projects WHERE id = ?").get(projectId);
166
+ const project = await registry.getProjectById(projectId);
189
167
  if (!project) return {
190
168
  content: [{
191
169
  type: "text",
@@ -283,13 +261,13 @@ function combineHybridResults(keywordResults, semanticResults, maxResults, keywo
283
261
  * MCP tool handlers: project_info, project_list, project_detect,
284
262
  * project_health, project_todo
285
263
  */
286
- function toolProjectInfo(registryDb, params) {
264
+ async function toolProjectInfo(registry, params) {
287
265
  try {
288
266
  let project = null;
289
267
  if (params.slug) {
290
- const projectId = lookupProjectId(registryDb, params.slug);
291
- if (projectId != null) project = registryDb.prepare("SELECT id, slug, display_name, root_path, type, status, created_at, updated_at, archived_at, parent_id, obsidian_link FROM projects WHERE id = ?").get(projectId);
292
- } else project = detectProjectFromPath(registryDb, process.cwd());
268
+ const projectId = await lookupProjectId(registry, params.slug);
269
+ if (projectId != null) project = await registry.getProjectById(projectId);
270
+ } else project = await detectProjectFromPath(registry, process.cwd());
293
271
  if (!project) return {
294
272
  content: [{
295
273
  type: "text",
@@ -299,7 +277,7 @@ function toolProjectInfo(registryDb, params) {
299
277
  };
300
278
  return { content: [{
301
279
  type: "text",
302
- text: formatProject(registryDb, project)
280
+ text: await formatProject(registry, project)
303
281
  }] };
304
282
  } catch (e) {
305
283
  return {
@@ -311,26 +289,15 @@ function toolProjectInfo(registryDb, params) {
311
289
  };
312
290
  }
313
291
  }
314
- function toolProjectList(registryDb, params) {
292
+ async function toolProjectList(registry, params) {
315
293
  try {
316
- const conditions = [];
317
- const queryParams = [];
318
- if (params.status) {
319
- conditions.push("p.status = ?");
320
- queryParams.push(params.status);
321
- }
322
- if (params.tag) {
323
- conditions.push("p.id IN (SELECT pt.project_id FROM project_tags pt JOIN tags t ON pt.tag_id = t.id WHERE t.name = ?)");
324
- queryParams.push(params.tag);
325
- }
326
- const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
327
294
  const limit = params.limit ?? 50;
328
- queryParams.push(limit);
329
- const projects = registryDb.prepare(`SELECT p.id, p.slug, p.display_name, p.root_path, p.type, p.status, p.updated_at
330
- FROM projects p
331
- ${where}
332
- ORDER BY p.updated_at DESC
333
- LIMIT ?`).all(...queryParams);
295
+ const projects = await registry.listProjects({
296
+ status: params.status,
297
+ tagName: params.tag,
298
+ orderBy: "updated_desc",
299
+ limit
300
+ });
334
301
  if (projects.length === 0) return { content: [{
335
302
  type: "text",
336
303
  text: "No projects found matching the given filters."
@@ -350,9 +317,9 @@ function toolProjectList(registryDb, params) {
350
317
  };
351
318
  }
352
319
  }
353
- function toolProjectDetect(registryDb, params) {
320
+ async function toolProjectDetect(params) {
354
321
  try {
355
- const detection = detectProject(registryDb, params.cwd);
322
+ const detection = await detectProject(params.cwd);
356
323
  if (!detection) return { content: [{
357
324
  type: "text",
358
325
  text: `No registered project found for path: ${params.cwd ?? process.cwd()}\n\nRun 'pai project add .' to register this directory.`
@@ -371,16 +338,13 @@ function toolProjectDetect(registryDb, params) {
371
338
  };
372
339
  }
373
340
  }
374
- async function toolProjectHealth(registryDb, params) {
341
+ async function toolProjectHealth(registry, params) {
375
342
  try {
376
343
  const { existsSync: fsExists, readdirSync, statSync } = await import("node:fs");
377
344
  const { join: pathJoin, basename: pathBasename } = await import("node:path");
378
345
  const { homedir } = await import("node:os");
379
- const { encodeDir: enc } = await import("./utils-BD8g8oSf.mjs");
380
- const rows = registryDb.prepare(`SELECT p.id, p.slug, p.display_name, p.root_path, p.encoded_dir, p.status, p.type,
381
- (SELECT COUNT(*) FROM sessions s WHERE s.project_id = p.id) AS session_count
382
- FROM projects p
383
- ORDER BY p.slug ASC`).all();
346
+ const { encodeDir: enc } = await import("./utils-BrX9FLeK.mjs");
347
+ const rows = (await registry.listProjectsWithSessionStats()).slice().sort((a, b) => a.slug.localeCompare(b.slug));
384
348
  const home = homedir();
385
349
  const claudeProjects = pathJoin(home, ".claude", "projects");
386
350
  function suggestMoved(rootPath) {
@@ -539,12 +503,12 @@ function parseTodoContent(raw) {
539
503
  hasContinue: true
540
504
  };
541
505
  }
542
- function toolProjectTodo(registryDb, params) {
506
+ async function toolProjectTodo(registry, params) {
543
507
  try {
544
508
  let rootPath;
545
509
  let projectSlug;
546
510
  if (params.project) {
547
- const projectId = lookupProjectId(registryDb, params.project);
511
+ const projectId = await lookupProjectId(registry, params.project);
548
512
  if (projectId == null) return {
549
513
  content: [{
550
514
  type: "text",
@@ -552,7 +516,7 @@ function toolProjectTodo(registryDb, params) {
552
516
  }],
553
517
  isError: true
554
518
  };
555
- const row = registryDb.prepare("SELECT root_path, slug FROM projects WHERE id = ?").get(projectId);
519
+ const row = await registry.getProjectById(projectId);
556
520
  if (!row) return {
557
521
  content: [{
558
522
  type: "text",
@@ -563,7 +527,7 @@ function toolProjectTodo(registryDb, params) {
563
527
  rootPath = row.root_path;
564
528
  projectSlug = row.slug;
565
529
  } else {
566
- const project = detectProjectFromPath(registryDb, process.cwd());
530
+ const project = await detectProjectFromPath(registry, process.cwd());
567
531
  if (!project) return { content: [{
568
532
  type: "text",
569
533
  text: `No PAI project found matching the current directory: ${process.cwd()}\n\nProvide a project slug or run 'pai project add .' to register this directory.`
@@ -621,9 +585,9 @@ function toolProjectTodo(registryDb, params) {
621
585
 
622
586
  //#endregion
623
587
  //#region src/mcp/tools/sessions.ts
624
- function toolSessionList(registryDb, params) {
588
+ async function toolSessionList(registry, params) {
625
589
  try {
626
- const projectId = lookupProjectId(registryDb, params.project);
590
+ const projectId = await lookupProjectId(registry, params.project);
627
591
  if (projectId == null) return {
628
592
  content: [{
629
593
  type: "text",
@@ -631,19 +595,12 @@ function toolSessionList(registryDb, params) {
631
595
  }],
632
596
  isError: true
633
597
  };
634
- const conditions = ["project_id = ?"];
635
- const queryParams = [projectId];
636
- if (params.status) {
637
- conditions.push("status = ?");
638
- queryParams.push(params.status);
639
- }
640
598
  const limit = params.limit ?? 10;
641
- queryParams.push(limit);
642
- const sessions = registryDb.prepare(`SELECT number, date, title, filename, status
643
- FROM sessions
644
- WHERE ${conditions.join(" AND ")}
645
- ORDER BY number DESC
646
- LIMIT ?`).all(...queryParams);
599
+ const sessions = await registry.listSessions({
600
+ projectId,
601
+ status: params.status,
602
+ limit
603
+ });
647
604
  if (sessions.length === 0) return { content: [{
648
605
  type: "text",
649
606
  text: `No sessions found for project: ${params.project}`
@@ -674,10 +631,10 @@ function toolSessionList(registryDb, params) {
674
631
  * Call this at session start (e.g., from CLAUDE.md or a session-start hook)
675
632
  * to automatically route the session to the correct project.
676
633
  */
677
- async function toolSessionRoute(registryDb, federation, params) {
634
+ async function toolSessionRoute(registry, federation, params) {
678
635
  try {
679
- const { autoRoute, formatAutoRouteJson } = await import("./auto-route-CcD0OWWB.mjs");
680
- const result = await autoRoute(registryDb, federation, params.cwd, params.context);
636
+ const { autoRoute, formatAutoRouteJson } = await import("./auto-route-CF8WNaoy.mjs");
637
+ const result = await autoRoute(registry, federation, params.cwd, params.context);
681
638
  if (!result) return { content: [{
682
639
  type: "text",
683
640
  text: [
@@ -706,16 +663,9 @@ async function toolSessionRoute(registryDb, federation, params) {
706
663
 
707
664
  //#endregion
708
665
  //#region src/mcp/tools/registry.ts
709
- function toolRegistrySearch(registryDb, params) {
666
+ async function toolRegistrySearch(registry, params) {
710
667
  try {
711
- const q = `%${params.query}%`;
712
- const projects = registryDb.prepare(`SELECT id, slug, display_name, root_path, type, status, updated_at
713
- FROM projects
714
- WHERE slug LIKE ?
715
- OR display_name LIKE ?
716
- OR root_path LIKE ?
717
- ORDER BY updated_at DESC
718
- LIMIT 20`).all(q, q, q);
668
+ const projects = await registry.searchProjects(params.query, 20);
719
669
  if (projects.length === 0) return { content: [{
720
670
  type: "text",
721
671
  text: `No projects found matching: "${params.query}"`
@@ -740,7 +690,7 @@ function toolRegistrySearch(registryDb, params) {
740
690
  //#region src/mcp/tools/zettel.ts
741
691
  async function toolZettelExplore(backend, params) {
742
692
  try {
743
- const { zettelExplore } = await import("./zettelkasten-DezbXghl.mjs");
693
+ const { zettelExplore } = await import("./zettelkasten-BPxSOalU.mjs");
744
694
  const result = await zettelExplore(backend, {
745
695
  startNote: params.start_note,
746
696
  depth: params.depth,
@@ -763,7 +713,7 @@ async function toolZettelExplore(backend, params) {
763
713
  }
764
714
  async function toolZettelHealth(backend, params) {
765
715
  try {
766
- const { zettelHealth } = await import("./zettelkasten-DezbXghl.mjs");
716
+ const { zettelHealth } = await import("./zettelkasten-BPxSOalU.mjs");
767
717
  const result = await zettelHealth(backend, {
768
718
  scope: params.scope,
769
719
  projectPath: params.project_path,
@@ -786,7 +736,7 @@ async function toolZettelHealth(backend, params) {
786
736
  }
787
737
  async function toolZettelSurprise(backend, params) {
788
738
  try {
789
- const { zettelSurprise } = await import("./zettelkasten-DezbXghl.mjs");
739
+ const { zettelSurprise } = await import("./zettelkasten-BPxSOalU.mjs");
790
740
  const results = await zettelSurprise(backend, {
791
741
  referencePath: params.reference_path,
792
742
  vaultProjectId: params.vault_project_id,
@@ -810,7 +760,7 @@ async function toolZettelSurprise(backend, params) {
810
760
  }
811
761
  async function toolZettelSuggest(backend, params) {
812
762
  try {
813
- const { zettelSuggest } = await import("./zettelkasten-DezbXghl.mjs");
763
+ const { zettelSuggest } = await import("./zettelkasten-BPxSOalU.mjs");
814
764
  const results = await zettelSuggest(backend, {
815
765
  notePath: params.note_path,
816
766
  vaultProjectId: params.vault_project_id,
@@ -833,7 +783,7 @@ async function toolZettelSuggest(backend, params) {
833
783
  }
834
784
  async function toolZettelConverse(backend, params) {
835
785
  try {
836
- const { zettelConverse } = await import("./zettelkasten-DezbXghl.mjs");
786
+ const { zettelConverse } = await import("./zettelkasten-BPxSOalU.mjs");
837
787
  const result = await zettelConverse(backend, {
838
788
  question: params.question,
839
789
  vaultProjectId: params.vault_project_id,
@@ -841,7 +791,7 @@ async function toolZettelConverse(backend, params) {
841
791
  limit: params.limit
842
792
  });
843
793
  try {
844
- const { saveQueryResult } = await import("./query-feedback-Bm6XvCmg.mjs");
794
+ const { saveQueryResult } = await import("./query-feedback-COYCnvHQ.mjs");
845
795
  saveQueryResult({
846
796
  query: params.question,
847
797
  timestamp: Date.now(),
@@ -869,7 +819,7 @@ async function toolZettelConverse(backend, params) {
869
819
  }
870
820
  async function toolZettelThemes(backend, params) {
871
821
  try {
872
- const { zettelThemes } = await import("./zettelkasten-DezbXghl.mjs");
822
+ const { zettelThemes } = await import("./zettelkasten-BPxSOalU.mjs");
873
823
  const result = await zettelThemes(backend, {
874
824
  vaultProjectId: params.vault_project_id,
875
825
  lookbackDays: params.lookback_days,
@@ -893,7 +843,7 @@ async function toolZettelThemes(backend, params) {
893
843
  }
894
844
  async function toolZettelGodNotes(backend, params) {
895
845
  try {
896
- const { zettelGodNotes } = await import("./zettelkasten-DezbXghl.mjs");
846
+ const { zettelGodNotes } = await import("./zettelkasten-BPxSOalU.mjs");
897
847
  const result = await zettelGodNotes(backend, {
898
848
  limit: params.limit,
899
849
  minInbound: params.min_inbound
@@ -914,7 +864,7 @@ async function toolZettelGodNotes(backend, params) {
914
864
  }
915
865
  async function toolZettelCommunities(backend, params) {
916
866
  try {
917
- const { zettelCommunities } = await import("./zettelkasten-DezbXghl.mjs");
867
+ const { zettelCommunities } = await import("./zettelkasten-BPxSOalU.mjs");
918
868
  const result = await zettelCommunities(backend, {
919
869
  minSize: params.min_size,
920
870
  maxCommunities: params.max_communities,
@@ -937,7 +887,7 @@ async function toolZettelCommunities(backend, params) {
937
887
 
938
888
  //#endregion
939
889
  //#region src/mcp/tools/kg.ts
940
- async function toolKgAdd(pool, params) {
890
+ async function toolKgAdd(backend, params) {
941
891
  try {
942
892
  if (!params.subject || !params.predicate || !params.object) return {
943
893
  content: [{
@@ -946,7 +896,7 @@ async function toolKgAdd(pool, params) {
946
896
  }],
947
897
  isError: true
948
898
  };
949
- const triple = await kgAdd(pool, params);
899
+ const triple = await backend.addKgTriple(params);
950
900
  return { content: [{
951
901
  type: "text",
952
902
  text: JSON.stringify(triple, null, 2)
@@ -961,7 +911,7 @@ async function toolKgAdd(pool, params) {
961
911
  };
962
912
  }
963
913
  }
964
- async function toolKgQuery(pool, params) {
914
+ async function toolKgQuery(backend, params) {
965
915
  try {
966
916
  const asOf = params.as_of ? new Date(params.as_of) : void 0;
967
917
  if (asOf && isNaN(asOf.getTime())) return {
@@ -971,7 +921,7 @@ async function toolKgQuery(pool, params) {
971
921
  }],
972
922
  isError: true
973
923
  };
974
- const triples = await kgQuery(pool, {
924
+ const triples = await backend.queryKgTriples({
975
925
  subject: params.subject,
976
926
  predicate: params.predicate,
977
927
  object: params.object,
@@ -993,7 +943,7 @@ async function toolKgQuery(pool, params) {
993
943
  };
994
944
  }
995
945
  }
996
- async function toolKgInvalidate(pool, params) {
946
+ async function toolKgInvalidate(backend, params) {
997
947
  try {
998
948
  if (params.triple_id === void 0 || params.triple_id === null) return {
999
949
  content: [{
@@ -1002,7 +952,7 @@ async function toolKgInvalidate(pool, params) {
1002
952
  }],
1003
953
  isError: true
1004
954
  };
1005
- await kgInvalidate(pool, params.triple_id);
955
+ await backend.invalidateKgTriple(params.triple_id);
1006
956
  return { content: [{
1007
957
  type: "text",
1008
958
  text: JSON.stringify({
@@ -1020,7 +970,7 @@ async function toolKgInvalidate(pool, params) {
1020
970
  };
1021
971
  }
1022
972
  }
1023
- async function toolKgContradictions(pool, params) {
973
+ async function toolKgContradictions(backend, params) {
1024
974
  try {
1025
975
  if (!params.subject) return {
1026
976
  content: [{
@@ -1029,7 +979,7 @@ async function toolKgContradictions(pool, params) {
1029
979
  }],
1030
980
  isError: true
1031
981
  };
1032
- const contradictions = await kgContradictions(pool, params.subject);
982
+ const contradictions = await backend.getKgContradictions(params.subject);
1033
983
  return { content: [{
1034
984
  type: "text",
1035
985
  text: JSON.stringify(contradictions, null, 2)
@@ -1048,19 +998,19 @@ async function toolKgContradictions(pool, params) {
1048
998
  //#endregion
1049
999
  //#region src/mcp/tools/wakeup.ts
1050
1000
  const DEFAULT_TOKEN_BUDGET = 800;
1051
- function toolMemoryWakeup(registryDb, params) {
1001
+ async function toolMemoryWakeup(registry, params) {
1052
1002
  try {
1053
1003
  const tokenBudget = params.token_budget ?? DEFAULT_TOKEN_BUDGET;
1054
1004
  let rootPath;
1055
1005
  if (params.project) {
1056
- const bySlug = registryDb.prepare("SELECT root_path FROM projects WHERE slug = ?").get(params.project);
1006
+ const bySlug = await registry.getProjectBySlug(params.project);
1057
1007
  if (bySlug) rootPath = bySlug.root_path;
1058
1008
  else {
1059
- const detected = detectProjectFromPath(registryDb, params.project);
1009
+ const detected = await detectProjectFromPath(registry, params.project);
1060
1010
  if (detected) rootPath = detected.root_path;
1061
1011
  }
1062
1012
  } else {
1063
- const detected = detectProjectFromPath(registryDb, process.cwd());
1013
+ const detected = await detectProjectFromPath(registry, process.cwd());
1064
1014
  if (detected) rootPath = detected.root_path;
1065
1015
  }
1066
1016
  const wakeupBlock = buildWakeupContext(rootPath, tokenBudget);
@@ -1089,18 +1039,20 @@ function toolMemoryWakeup(registryDb, params) {
1089
1039
  * Build a taxonomy of stored memory — what projects exist, how much is stored,
1090
1040
  * and what has been active recently.
1091
1041
  *
1092
- * Registry queries (projects, sessions) are synchronous (better-sqlite3).
1042
+ * Registry queries (projects, sessions) go through RegistryBackend (async).
1093
1043
  * Storage backend queries (files, chunks) are async.
1094
1044
  */
1095
- async function getTaxonomy(registryDb, storage, options = {}) {
1045
+ async function getTaxonomy(registry, storage, options = {}) {
1096
1046
  const includeArchived = options.include_archived ?? false;
1097
1047
  const limit = options.limit ?? 50;
1098
- const statusFilter = includeArchived ? "status IN ('active', 'archived', 'migrating')" : "status = 'active'";
1099
- const projectRows = registryDb.prepare(`SELECT id, slug, display_name, status, created_at, updated_at
1100
- FROM projects
1101
- WHERE ${statusFilter}
1102
- ORDER BY updated_at DESC
1103
- LIMIT ?`).all(limit);
1048
+ const projectRows = includeArchived ? await registry.listProjects({
1049
+ orderBy: "updated_desc",
1050
+ limit
1051
+ }) : await registry.listProjects({
1052
+ status: "active",
1053
+ orderBy: "updated_desc",
1054
+ limit
1055
+ });
1104
1056
  if (projectRows.length === 0) return {
1105
1057
  projects: [],
1106
1058
  totals: {
@@ -1112,48 +1064,27 @@ async function getTaxonomy(registryDb, storage, options = {}) {
1112
1064
  recent_activity: []
1113
1065
  };
1114
1066
  const projectIds = projectRows.map((p) => p.id);
1067
+ const projectIdSet = new Set(projectIds);
1115
1068
  const sessionCountsByProject = /* @__PURE__ */ new Map();
1116
1069
  const lastSessionDateByProject = /* @__PURE__ */ new Map();
1117
1070
  for (const projectId of projectIds) {
1118
- const countRow = registryDb.prepare("SELECT COUNT(*) AS n FROM sessions WHERE project_id = ?").get(projectId);
1119
- sessionCountsByProject.set(projectId, countRow.n);
1120
- const lastRow = registryDb.prepare("SELECT date FROM sessions WHERE project_id = ? ORDER BY number DESC LIMIT 1").get(projectId);
1121
- lastSessionDateByProject.set(projectId, lastRow?.date ?? null);
1071
+ sessionCountsByProject.set(projectId, await registry.countSessionsForProject(projectId));
1072
+ lastSessionDateByProject.set(projectId, await registry.getMostRecentSessionDate(projectId));
1122
1073
  }
1123
1074
  const tagsByProject = /* @__PURE__ */ new Map();
1124
- for (const projectId of projectIds) {
1125
- const tags = registryDb.prepare(`SELECT t.name
1126
- FROM tags t
1127
- JOIN project_tags pt ON pt.tag_id = t.id
1128
- WHERE pt.project_id = ?
1129
- ORDER BY t.name`).all(projectId);
1130
- tagsByProject.set(projectId, tags.map((t) => t.name));
1131
- }
1075
+ for (const projectId of projectIds) tagsByProject.set(projectId, await registry.listTagsForProject(projectId));
1132
1076
  const noteCountsByProject = /* @__PURE__ */ new Map();
1133
1077
  const chunkCountsByProject = /* @__PURE__ */ new Map();
1134
- const isBackend = (x) => x.backendType === "sqlite";
1135
- if (isBackend(storage)) {
1136
- const rawDb = storage.getRawDb?.();
1137
- if (rawDb) for (const projectId of projectIds) {
1138
- const noteRow = rawDb.prepare("SELECT COUNT(*) AS n FROM memory_files WHERE project_id = ?").get(projectId);
1139
- noteCountsByProject.set(projectId, noteRow.n);
1140
- const chunkRow = rawDb.prepare("SELECT COUNT(*) AS n FROM memory_chunks WHERE project_id = ?").get(projectId);
1141
- chunkCountsByProject.set(projectId, chunkRow.n);
1142
- }
1143
- } else for (const projectId of projectIds) {
1144
- noteCountsByProject.set(projectId, 0);
1145
- chunkCountsByProject.set(projectId, 0);
1078
+ for (const projectId of projectIds) {
1079
+ const projectStats = await storage.getProjectStats(projectId);
1080
+ noteCountsByProject.set(projectId, projectStats.files);
1081
+ chunkCountsByProject.set(projectId, projectStats.chunks);
1146
1082
  }
1147
1083
  const stats = await storage.getStats();
1148
- const totalProjects = registryDb.prepare(`SELECT COUNT(*) AS n FROM projects WHERE ${statusFilter}`).get().n;
1149
- const totalSessions = registryDb.prepare("SELECT COUNT(*) AS n FROM sessions").get().n;
1150
- const recentActivity = registryDb.prepare(`SELECT s.date, s.title, p.slug
1151
- FROM sessions s
1152
- JOIN projects p ON p.id = s.project_id
1153
- WHERE p.${statusFilter.replace("status", "p.status")}
1154
- ORDER BY s.created_at DESC
1155
- LIMIT 10`).all().map((row) => ({
1156
- project_slug: row.slug,
1084
+ const totalProjects = includeArchived ? await registry.countProjects() : await registry.countProjects({ status: "active" });
1085
+ const totalSessions = [...sessionCountsByProject.values()].reduce((a, b) => a + b, 0);
1086
+ const recentActivity = (await registry.listSessions({ limit: Math.max(50, limit) })).filter((s) => projectIdSet.has(s.project_id)).slice(0, 10).map((row) => ({
1087
+ project_slug: row.project_slug,
1157
1088
  action: `session: ${row.title || "(untitled)"}`,
1158
1089
  timestamp: row.date
1159
1090
  }));
@@ -1178,9 +1109,9 @@ async function getTaxonomy(registryDb, storage, options = {}) {
1178
1109
 
1179
1110
  //#endregion
1180
1111
  //#region src/mcp/tools/taxonomy.ts
1181
- async function toolMemoryTaxonomy(registryDb, storage, params = {}) {
1112
+ async function toolMemoryTaxonomy(registry, storage, params = {}) {
1182
1113
  try {
1183
- const result = await getTaxonomy(registryDb, storage, {
1114
+ const result = await getTaxonomy(registry, storage, {
1184
1115
  include_archived: params.include_archived,
1185
1116
  limit: params.limit
1186
1117
  });
@@ -1219,209 +1150,24 @@ async function toolMemoryTaxonomy(registryDb, storage, params = {}) {
1219
1150
  //#endregion
1220
1151
  //#region src/memory/tunnels.ts
1221
1152
  /**
1222
- * tunnels.ts — cross-project concept detection ("palace graph / tunnel detection")
1223
- *
1224
- * A "tunnel" is a concept (word or short phrase) that appears in chunks from
1225
- * at least two distinct projects. These serendipitous cross-project connections
1226
- * are surfaced so the user can discover unexpected relationships between their
1227
- * work streams.
1228
- *
1229
- * Algorithm:
1230
- * 1. Pull the top-N most frequent significant terms from memory_chunks via BM25 FTS.
1231
- * We use the FTS5 vocab table (if available) or fall back to term frequency
1232
- * aggregation over the raw text via a trigram approach.
1233
- * 2. For each candidate term, count how many distinct projects have at least one
1234
- * chunk containing it and aggregate occurrence stats.
1235
- * 3. Filter by min_projects and min_occurrences, sort by project breadth then
1236
- * frequency, return top limit results.
1237
- *
1238
- * Backend support:
1239
- * - SQLite — uses `memory_fts` MATCH to count per-project occurrences.
1240
- * - Postgres — uses `memory_chunks` tsvector + ts_stat for term extraction and
1241
- * per-project term frequency counting via plainto_tsquery.
1242
- */
1243
- /**
1244
- * Extract candidate terms from the SQLite FTS5 index using the vocabulary
1245
- * approach: iterate the fts5vocab table (if it exists) for the most common
1246
- * terms, then per-term count distinct projects.
1247
- */
1248
- async function findTunnelsSqlite(db, slugMap, opts) {
1249
- const projectIds = Object.keys(slugMap).map(Number);
1250
- if (projectIds.length < 2) return {
1251
- tunnels: [],
1252
- projects_analyzed: projectIds.length,
1253
- total_concepts_evaluated: 0
1254
- };
1255
- let candidateTerms = [];
1256
- try {
1257
- candidateTerms = db.prepare(`SELECT term, SUM(doc) AS doc_count, SUM(cnt) AS total_cnt
1258
- FROM memory_fts_v
1259
- GROUP BY term
1260
- HAVING SUM(cnt) >= ?
1261
- ORDER BY SUM(doc) DESC
1262
- LIMIT 500`).all(opts.min_occurrences).map((r) => r.term).filter((t) => t.length >= 3 && !STOP_WORDS.has(t));
1263
- } catch {
1264
- const sampleRows = db.prepare(`SELECT LOWER(text) AS text FROM memory_chunks
1265
- WHERE LENGTH(text) > 20
1266
- ORDER BY RANDOM()
1267
- LIMIT 2000`).all();
1268
- const freq = /* @__PURE__ */ new Map();
1269
- for (const { text } of sampleRows) {
1270
- const tokens = text.split(/[\s\p{P}]+/u).filter(Boolean).filter((t) => t.length >= 3 && !STOP_WORDS.has(t));
1271
- for (const t of tokens) freq.set(t, (freq.get(t) ?? 0) + 1);
1272
- }
1273
- candidateTerms = [...freq.entries()].filter(([, n]) => n >= opts.min_occurrences).sort((a, b) => b[1] - a[1]).slice(0, 200).map(([t]) => t);
1274
- }
1275
- if (candidateTerms.length === 0) return {
1276
- tunnels: [],
1277
- projects_analyzed: projectIds.length,
1278
- total_concepts_evaluated: 0
1279
- };
1280
- const tunnels = [];
1281
- for (const term of candidateTerms) try {
1282
- const rows = db.prepare(`SELECT c.project_id, COUNT(*) AS cnt,
1283
- MIN(c.updated_at) AS first_seen,
1284
- MAX(c.updated_at) AS last_seen
1285
- FROM memory_fts f
1286
- JOIN memory_chunks c ON c.id = f.id
1287
- WHERE memory_fts MATCH ?
1288
- AND c.project_id IN (${projectIds.map(() => "?").join(", ")})
1289
- GROUP BY c.project_id`).all(`"${term.replace(/"/g, "\"\"")}"`, ...projectIds);
1290
- if (rows.length < opts.min_projects) continue;
1291
- const totalOccurrences = rows.reduce((s, r) => s + Number(r.cnt), 0);
1292
- if (totalOccurrences < opts.min_occurrences) continue;
1293
- const projects = rows.map((r) => slugMap[r.project_id] ?? String(r.project_id)).filter(Boolean);
1294
- const firstSeen = Math.min(...rows.map((r) => r.first_seen));
1295
- const lastSeen = Math.max(...rows.map((r) => r.last_seen));
1296
- tunnels.push({
1297
- concept: term,
1298
- projects,
1299
- occurrences: totalOccurrences,
1300
- first_seen: firstSeen,
1301
- last_seen: lastSeen
1302
- });
1303
- } catch {
1304
- continue;
1305
- }
1306
- tunnels.sort((a, b) => {
1307
- const byProjects = b.projects.length - a.projects.length;
1308
- if (byProjects !== 0) return byProjects;
1309
- return b.occurrences - a.occurrences;
1310
- });
1311
- return {
1312
- tunnels: tunnels.slice(0, opts.limit),
1313
- projects_analyzed: projectIds.length,
1314
- total_concepts_evaluated: candidateTerms.length
1315
- };
1316
- }
1317
- /**
1318
- * Use Postgres ts_stat() + plainto_tsquery to efficiently find terms that
1319
- * appear across multiple projects.
1320
- */
1321
- async function findTunnelsPostgres(pool, slugMap, opts) {
1322
- const projectIds = Object.keys(slugMap).map(Number);
1323
- if (projectIds.length < 2) return {
1324
- tunnels: [],
1325
- projects_analyzed: projectIds.length,
1326
- total_concepts_evaluated: 0
1327
- };
1328
- let candidateTerms = (await pool.query(`SELECT word, ndoc, nentry
1329
- FROM ts_stat(
1330
- 'SELECT to_tsvector(''simple'', text) FROM memory_chunks WHERE project_id = ANY($1)'
1331
- )
1332
- WHERE length(word) >= 3
1333
- AND nentry >= $2
1334
- ORDER BY ndoc DESC
1335
- LIMIT 500`, [projectIds, opts.min_occurrences])).rows.map((r) => r.word).filter((t) => !STOP_WORDS.has(t));
1336
- if (candidateTerms.length === 0) return {
1337
- tunnels: [],
1338
- projects_analyzed: projectIds.length,
1339
- total_concepts_evaluated: 0
1340
- };
1341
- candidateTerms = candidateTerms.slice(0, 200);
1342
- const valuesClause = candidateTerms.map((t, i) => `($${i + 2}::text)`).join(", ");
1343
- const batchResult = await pool.query(`SELECT v.concept, c.project_id::text, COUNT(*) AS cnt,
1344
- MIN(c.updated_at) AS first_seen,
1345
- MAX(c.updated_at) AS last_seen
1346
- FROM (VALUES ${valuesClause}) AS v(concept)
1347
- JOIN memory_chunks c
1348
- ON to_tsvector('simple', c.text) @@ plainto_tsquery('simple', v.concept)
1349
- AND c.project_id = ANY($1)
1350
- GROUP BY v.concept, c.project_id`, [projectIds, ...candidateTerms]);
1351
- const byConceptMap = /* @__PURE__ */ new Map();
1352
- for (const row of batchResult.rows) {
1353
- const existing = byConceptMap.get(row.concept) ?? {
1354
- projects: /* @__PURE__ */ new Set(),
1355
- occurrences: 0,
1356
- firstSeen: Infinity,
1357
- lastSeen: -Infinity
1358
- };
1359
- existing.projects.add(parseInt(row.project_id, 10));
1360
- existing.occurrences += parseInt(row.cnt, 10);
1361
- const fs = parseInt(row.first_seen, 10);
1362
- const ls = parseInt(row.last_seen, 10);
1363
- if (fs < existing.firstSeen) existing.firstSeen = fs;
1364
- if (ls > existing.lastSeen) existing.lastSeen = ls;
1365
- byConceptMap.set(row.concept, existing);
1366
- }
1367
- const tunnels = [];
1368
- for (const [concept, data] of byConceptMap) {
1369
- if (data.projects.size < opts.min_projects) continue;
1370
- if (data.occurrences < opts.min_occurrences) continue;
1371
- const projects = [...data.projects].map((id) => slugMap[id] ?? String(id)).filter(Boolean);
1372
- tunnels.push({
1373
- concept,
1374
- projects,
1375
- occurrences: data.occurrences,
1376
- first_seen: data.firstSeen === Infinity ? 0 : data.firstSeen,
1377
- last_seen: data.lastSeen === -Infinity ? 0 : data.lastSeen
1378
- });
1379
- }
1380
- tunnels.sort((a, b) => {
1381
- const byProjects = b.projects.length - a.projects.length;
1382
- if (byProjects !== 0) return byProjects;
1383
- return b.occurrences - a.occurrences;
1384
- });
1385
- return {
1386
- tunnels: tunnels.slice(0, opts.limit),
1387
- projects_analyzed: projectIds.length,
1388
- total_concepts_evaluated: candidateTerms.length
1389
- };
1390
- }
1391
- /**
1392
1153
  * Find cross-project concept tunnels.
1393
1154
  *
1394
1155
  * Works with both SQLite and Postgres storage backends.
1395
- * Requires the `registryDb` (better-sqlite3) for project slug resolution.
1156
+ * Requires the RegistryBackend for project slug resolution.
1396
1157
  *
1397
1158
  * @param backend Active PAI storage backend.
1398
- * @param registryDb Registry database for project slug resolution.
1159
+ * @param registry Registry backend for project slug resolution.
1399
1160
  * @param options Filter and limit options.
1400
1161
  */
1401
- async function findTunnels(backend, registryDb, options) {
1402
- const opts = {
1403
- min_projects: options?.min_projects ?? 2,
1404
- min_occurrences: options?.min_occurrences ?? 3,
1405
- limit: options?.limit ?? 20
1406
- };
1407
- const projectRows = registryDb.prepare("SELECT id, slug FROM projects WHERE status != 'archived'").all();
1408
- const slugMap = {};
1409
- for (const { id, slug } of projectRows) slugMap[id] = slug;
1410
- if (backend.backendType === "postgres") {
1411
- const pool = backend.getPool?.();
1412
- if (!pool) throw new Error("findTunnels: Postgres backend does not expose getPool()");
1413
- return findTunnelsPostgres(pool, slugMap, opts);
1414
- }
1415
- const rawDb = backend.getRawDb?.();
1416
- if (!rawDb) throw new Error("findTunnels: SQLite backend does not expose getRawDb()");
1417
- return findTunnelsSqlite(rawDb, slugMap, opts);
1162
+ async function findTunnels(backend, registry, options) {
1163
+ return backend.findTunnels(registry, options);
1418
1164
  }
1419
1165
 
1420
1166
  //#endregion
1421
1167
  //#region src/mcp/tools/tunnels.ts
1422
- async function toolMemoryTunnels(registryDb, backend, params) {
1168
+ async function toolMemoryTunnels(registry, backend, params) {
1423
1169
  try {
1424
- const result = await findTunnels(backend, registryDb, {
1170
+ const result = await findTunnels(backend, registry, {
1425
1171
  min_projects: params.min_projects,
1426
1172
  min_occurrences: params.min_occurrences,
1427
1173
  limit: params.limit
@@ -1446,11 +1192,13 @@ async function toolMemoryTunnels(registryDb, backend, params) {
1446
1192
  const FEEDBACK_ALPHA = .1;
1447
1193
  /**
1448
1194
  * Apply relevance feedback to memory chunks and associated KG entities.
1195
+ * Both memory_chunks.relevance_score and kg_entities.feedback_weight go
1196
+ * through the passed StorageBackend, so this works on either backend.
1449
1197
  *
1450
- * @param db Federation SQLite database
1451
- * @param params Feedback parameters
1198
+ * @param backend StorageBackend for both the chunk and kg_entities portions
1199
+ * @param params Feedback parameters
1452
1200
  */
1453
- function toolMemoryFeedback(db, params) {
1201
+ async function toolMemoryFeedback(backend, params) {
1454
1202
  try {
1455
1203
  if (!Array.isArray(params.chunk_ids) || params.chunk_ids.length === 0) return {
1456
1204
  content: [{
@@ -1468,8 +1216,7 @@ function toolMemoryFeedback(db, params) {
1468
1216
  isError: true
1469
1217
  };
1470
1218
  const normalizedRating = (rating - 1) / 4;
1471
- const placeholders = params.chunk_ids.map(() => "?").join(", ");
1472
- const chunks = db.prepare(`SELECT id, text, relevance_score FROM memory_chunks WHERE id IN (${placeholders})`).all(...params.chunk_ids);
1219
+ const chunks = await backend.getChunksForFeedback(params.chunk_ids);
1473
1220
  if (chunks.length === 0) return {
1474
1221
  content: [{
1475
1222
  type: "text",
@@ -1479,19 +1226,19 @@ function toolMemoryFeedback(db, params) {
1479
1226
  };
1480
1227
  let updatedChunks = 0;
1481
1228
  const combinedText = [];
1482
- const updateStmt = db.prepare("UPDATE memory_chunks SET relevance_score = ? WHERE id = ?");
1483
1229
  for (const chunk of chunks) {
1484
- const oldScore = chunk.relevance_score ?? .5;
1230
+ const oldScore = chunk.relevanceScore ?? .5;
1485
1231
  const newScore = oldScore + FEEDBACK_ALPHA * (normalizedRating - oldScore);
1486
- updateStmt.run(newScore, chunk.id);
1232
+ await backend.updateChunkRelevanceScore(chunk.id, newScore);
1487
1233
  updatedChunks++;
1488
1234
  combinedText.push(chunk.text);
1489
1235
  }
1490
- const entities = listKgEntities(db, params.tenant_id ?? "default", void 0, 500);
1236
+ const tenantId = params.tenant_id ?? "default";
1237
+ const entities = await backend.listKgEntities(tenantId, void 0, 500);
1491
1238
  const text = combinedText.join("\n").toLowerCase();
1492
1239
  let updatedEntities = 0;
1493
1240
  for (const entity of entities) if (text.includes(entity.name.toLowerCase())) {
1494
- updateEntityFeedbackWeight(db, entity.entity_id, normalizedRating, FEEDBACK_ALPHA);
1241
+ await backend.updateEntityFeedbackWeight(entity.entity_id, normalizedRating, FEEDBACK_ALPHA);
1495
1242
  updatedEntities++;
1496
1243
  }
1497
1244
  return { content: [{
@@ -1517,13 +1264,28 @@ function toolMemoryFeedback(db, params) {
1517
1264
  //#endregion
1518
1265
  //#region src/memory/kg-search.ts
1519
1266
  /**
1520
- * Find entity names (from the federation.db kg_entities table) that appear
1267
+ * kg-search.ts — Graph-completion retrieval (MR1).
1268
+ *
1269
+ * Implements graphCompletionSearch: a multi-phase retrieval strategy that
1270
+ * combines vector search with knowledge-graph neighborhood expansion.
1271
+ *
1272
+ * Algorithm:
1273
+ * Phase 1: Wide vector search (50 candidates) using queryVec
1274
+ * Phase 2: Extract entity mentions from retrieved chunks (substring match vs kg_entities)
1275
+ * Phase 3: BFS neighborhood expansion in kg_triples (1-2 hops from matched entities)
1276
+ * Phase 4: Re-rank all collected triples against query embedding using cosine similarity
1277
+ *
1278
+ * The result is a ranked list of KgTriple objects most relevant to the query,
1279
+ * augmented with graph-derived context that pure vector search would miss.
1280
+ */
1281
+ /**
1282
+ * Find entity names (from the kg_entities table) that appear
1521
1283
  * as substrings in the given text chunks.
1522
1284
  *
1523
1285
  * Returns a deduplicated list of matching entity names.
1524
1286
  */
1525
- function extractEntityMentions(federationDb, chunks, tenantId = "default") {
1526
- const entities = listKgEntities(federationDb, tenantId, void 0, 500);
1287
+ async function extractEntityMentions(federation, chunks, tenantId = "default") {
1288
+ const entities = await federation.listKgEntities(tenantId, void 0, 500);
1527
1289
  if (entities.length === 0) return [];
1528
1290
  const combinedText = chunks.map((c) => c.snippet).join("\n").toLowerCase();
1529
1291
  const matched = /* @__PURE__ */ new Set();
@@ -1538,18 +1300,18 @@ function extractEntityMentions(federationDb, chunks, tenantId = "default") {
1538
1300
  *
1539
1301
  * Returns all unique triples found in the BFS expansion.
1540
1302
  */
1541
- async function bfsExpand(pool, entityNames, hops, projectId) {
1303
+ async function bfsExpand(federation, entityNames, hops, projectId) {
1542
1304
  const seen = /* @__PURE__ */ new Set();
1543
1305
  const tripleMap = /* @__PURE__ */ new Map();
1544
1306
  const frontier = new Set(entityNames);
1545
1307
  for (let hop = 0; hop < hops; hop++) {
1546
1308
  const nextFrontier = /* @__PURE__ */ new Set();
1547
1309
  for (const name of frontier) {
1548
- const asSubject = await kgQuery(pool, {
1310
+ const asSubject = await federation.queryKgTriples({
1549
1311
  subject: name,
1550
1312
  project_id: projectId
1551
1313
  });
1552
- const asObject = await kgQuery(pool, {
1314
+ const asObject = await federation.queryKgTriples({
1553
1315
  object: name,
1554
1316
  project_id: projectId
1555
1317
  });
@@ -1578,7 +1340,7 @@ async function bfsExpand(pool, entityNames, hops, projectId) {
1578
1340
  */
1579
1341
  async function rerankTriples(triples, queryVec, maxTriples) {
1580
1342
  if (triples.length === 0) return [];
1581
- const { generateEmbedding } = await import("./embeddings-ClqKtLZ8.mjs");
1343
+ const { generateEmbedding } = await import("./embeddings-BNv3TQC5.mjs");
1582
1344
  return (await Promise.all(triples.map(async (triple) => {
1583
1345
  const text = `${triple.subject} ${triple.predicate} ${triple.object}`;
1584
1346
  try {
@@ -1598,12 +1360,11 @@ async function rerankTriples(triples, queryVec, maxTriples) {
1598
1360
  /**
1599
1361
  * Graph-completion retrieval combining vector search with KG neighborhood BFS.
1600
1362
  *
1601
- * @param federationDb SQLite federation.db (for chunk/entity lookup)
1602
- * @param pool Postgres connection pool (for kg_triples queries)
1363
+ * @param federation Active StorageBackend (for chunk/entity/kg_triples lookup)
1603
1364
  * @param queryVec Pre-computed embedding for the search query
1604
1365
  * @param opts Configuration options
1605
1366
  */
1606
- async function graphCompletionSearch(federationDb, pool, queryVec, opts = {}) {
1367
+ async function graphCompletionSearch(federation, queryVec, opts = {}) {
1607
1368
  const { seedCount = 50, hops = 1, maxTriples = 20, projectId, tenantId = "default", searchOpts } = opts;
1608
1369
  const seedSearchOpts = {
1609
1370
  ...searchOpts,
@@ -1612,8 +1373,7 @@ async function graphCompletionSearch(federationDb, pool, queryVec, opts = {}) {
1612
1373
  };
1613
1374
  let seedChunks = [];
1614
1375
  try {
1615
- const { searchMemorySemantic } = await import("./search-fb-5ovj4.mjs");
1616
- seedChunks = searchMemorySemantic(federationDb, queryVec, seedSearchOpts);
1376
+ seedChunks = await federation.searchSemantic(queryVec, seedSearchOpts);
1617
1377
  } catch (e) {
1618
1378
  process.stderr.write(`[kg-search] Phase 1 seed search error: ${e}\n`);
1619
1379
  }
@@ -1622,7 +1382,7 @@ async function graphCompletionSearch(federationDb, pool, queryVec, opts = {}) {
1622
1382
  seedChunks: [],
1623
1383
  expandedEntities: []
1624
1384
  };
1625
- const expandedEntities = extractEntityMentions(federationDb, seedChunks, tenantId);
1385
+ const expandedEntities = await extractEntityMentions(federation, seedChunks, tenantId);
1626
1386
  if (expandedEntities.length === 0) return {
1627
1387
  triples: [],
1628
1388
  seedChunks,
@@ -1630,7 +1390,7 @@ async function graphCompletionSearch(federationDb, pool, queryVec, opts = {}) {
1630
1390
  };
1631
1391
  let expandedTriples = [];
1632
1392
  try {
1633
- expandedTriples = await bfsExpand(pool, [...expandedEntities], hops, projectId);
1393
+ expandedTriples = await bfsExpand(federation, [...expandedEntities], hops, projectId);
1634
1394
  } catch (e) {
1635
1395
  process.stderr.write(`[kg-search] Phase 3 BFS expansion error: ${e}\n`);
1636
1396
  }
@@ -1661,11 +1421,10 @@ async function graphCompletionSearch(federationDb, pool, queryVec, opts = {}) {
1661
1421
  /**
1662
1422
  * Graph-completion search: vector search + KG neighborhood BFS.
1663
1423
  *
1664
- * @param federationDb SQLite federation.db for chunk/entity operations
1665
- * @param pool Postgres pool for kg_triples BFS expansion
1424
+ * @param federation Active StorageBackend for chunk/entity/kg_triples operations
1666
1425
  * @param params Tool parameters
1667
1426
  */
1668
- async function toolMemoryKgSearch(federationDb, pool, params) {
1427
+ async function toolMemoryKgSearch(federation, params) {
1669
1428
  try {
1670
1429
  if (!params.query || typeof params.query !== "string" || params.query.trim() === "") return {
1671
1430
  content: [{
@@ -1674,8 +1433,8 @@ async function toolMemoryKgSearch(federationDb, pool, params) {
1674
1433
  }],
1675
1434
  isError: true
1676
1435
  };
1677
- const { generateEmbedding } = await import("./embeddings-ClqKtLZ8.mjs");
1678
- const result = await graphCompletionSearch(federationDb, pool, await generateEmbedding(params.query, true), {
1436
+ const { generateEmbedding } = await import("./embeddings-BNv3TQC5.mjs");
1437
+ const result = await graphCompletionSearch(federation, await generateEmbedding(params.query, true), {
1679
1438
  seedCount: params.wide_k ?? 50,
1680
1439
  hops: params.neighborhood_depth ?? 1,
1681
1440
  maxTriples: params.top_k ?? 20,
@@ -1711,4 +1470,4 @@ async function toolMemoryKgSearch(federationDb, pool, params) {
1711
1470
 
1712
1471
  //#endregion
1713
1472
  export { formatProject as A, toolProjectInfo as C, toolMemoryGet as D, combineHybridResults as E, toolMemorySearch as O, toolProjectHealth as S, toolProjectTodo as T, toolZettelThemes as _, toolMemoryWakeup as a, toolSessionRoute as b, toolKgInvalidate as c, toolZettelConverse as d, toolZettelExplore as f, toolZettelSurprise as g, toolZettelSuggest as h, toolMemoryTaxonomy as i, lookupProjectId as j, detectProjectFromPath as k, toolKgQuery as l, toolZettelHealth as m, toolMemoryFeedback as n, toolKgAdd as o, toolZettelGodNotes as p, toolMemoryTunnels as r, toolKgContradictions as s, toolMemoryKgSearch as t, toolZettelCommunities as u, toolRegistrySearch as v, toolProjectList as w, toolProjectDetect as x, toolSessionList as y };
1714
- //# sourceMappingURL=tools-BEqiJt_j.mjs.map
1473
+ //# sourceMappingURL=tools-Diro2s8J.mjs.map