@tekmidian/pai 0.63.0 → 0.64.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 (150) hide show
  1. package/ARCHITECTURE.md +3 -0
  2. package/README.md +19 -0
  3. package/dist/{aibroker-client-BYG-GtAg.mjs → aibroker-client-BpfuGPe5.mjs} +1 -1
  4. package/dist/{aibroker-client-BK1h--W_.mjs → aibroker-client-DRld7g6z.mjs} +2 -2
  5. package/dist/{aibroker-client-BK1h--W_.mjs.map → aibroker-client-DRld7g6z.mjs.map} +1 -1
  6. package/dist/{async-DNVWtQl2.mjs → async-CY_cd_8j.mjs} +5 -4
  7. package/dist/async-CY_cd_8j.mjs.map +1 -0
  8. package/dist/{auto-route-CF8WNaoy.mjs → auto-route-CPlam4iv.mjs} +2 -2
  9. package/dist/{auto-route-CF8WNaoy.mjs.map → auto-route-CPlam4iv.mjs.map} +1 -1
  10. package/dist/{chain-CUfE6WYK.mjs → chain-o-g8AgE3.mjs} +4 -4
  11. package/dist/{chain-CUfE6WYK.mjs.map → chain-o-g8AgE3.mjs.map} +1 -1
  12. package/dist/chunker-BH4i-F2b.mjs +300 -0
  13. package/dist/chunker-BH4i-F2b.mjs.map +1 -0
  14. package/dist/cli/index.mjs +20 -20
  15. package/dist/cli/program.mjs +20 -20
  16. package/dist/{clusters-Ci1mseh2.mjs → clusters-DsMf20PP.mjs} +2 -2
  17. package/dist/{clusters-Ci1mseh2.mjs.map → clusters-DsMf20PP.mjs.map} +1 -1
  18. package/dist/{config-CQCmH_gV.mjs → config-2wkz744W.mjs} +3 -3
  19. package/dist/{config-CQCmH_gV.mjs.map → config-2wkz744W.mjs.map} +1 -1
  20. package/dist/{config-DjXq_W42.mjs → config-CUTg6VDq.mjs} +1 -1
  21. package/dist/{context-handover-cache-DFNnlWv2.mjs → context-handover-cache-BOpjLsKe.mjs} +6 -6
  22. package/dist/{context-handover-cache-DFNnlWv2.mjs.map → context-handover-cache-BOpjLsKe.mjs.map} +1 -1
  23. package/dist/daemon/index.mjs +16 -16
  24. package/dist/{daemon-Cex3dT9f.mjs → daemon-BYieXduQ.mjs} +31 -30
  25. package/dist/{daemon-Cex3dT9f.mjs.map → daemon-BYieXduQ.mjs.map} +1 -1
  26. package/dist/daemon-mcp/index.mjs +20 -11
  27. package/dist/daemon-mcp/index.mjs.map +1 -1
  28. package/dist/daemon-q3xjHwa4.mjs +19 -0
  29. package/dist/{detector-3g7XuU1r.mjs → detector-BAlmrLQ0.mjs} +2 -2
  30. package/dist/{detector-3g7XuU1r.mjs.map → detector-BAlmrLQ0.mjs.map} +1 -1
  31. package/dist/detector-C-YmZsOi.mjs +3 -0
  32. package/dist/{embeddings-DfrYNIng.mjs → embeddings-DK9XfQic.mjs} +1 -1
  33. package/dist/{embeddings-DfrYNIng.mjs.map → embeddings-DK9XfQic.mjs.map} +1 -1
  34. package/dist/{embeddings-BNv3TQC5.mjs → embeddings-Xg8XLf2K.mjs} +1 -1
  35. package/dist/{factory-UO9_DFMN.mjs → factory-BMK0tC1b.mjs} +1 -1
  36. package/dist/{factory-BozZa81q.mjs → factory-C4We3xR_.mjs} +13 -13
  37. package/dist/{factory-BozZa81q.mjs.map → factory-C4We3xR_.mjs.map} +1 -1
  38. package/dist/{fallback-ktbR0aR7.mjs → fallback-ECiqCuh6.mjs} +5 -5
  39. package/dist/{fallback-ktbR0aR7.mjs.map → fallback-ECiqCuh6.mjs.map} +1 -1
  40. package/dist/federation-db-BZ8PyxFe.mjs +3 -0
  41. package/dist/{federation-db-w8e-sKjp.mjs → federation-db-CUgy0WYs.mjs} +2 -2
  42. package/dist/{federation-db-w8e-sKjp.mjs.map → federation-db-CUgy0WYs.mjs.map} +1 -1
  43. package/dist/{helpers-Dj-B314s.mjs → helpers-BRCJg0G3.mjs} +2 -208
  44. package/dist/helpers-BRCJg0G3.mjs.map +1 -0
  45. package/dist/index.d.mts +26 -8
  46. package/dist/index.d.mts.map +1 -1
  47. package/dist/index.mjs +5 -4
  48. package/dist/indexer-backend-CJ0RGgMz.mjs +5 -0
  49. package/dist/{ipc-client-Dasm515c.mjs → ipc-client-EhWY8XL0.mjs} +2 -2
  50. package/dist/{ipc-client-Dasm515c.mjs.map → ipc-client-EhWY8XL0.mjs.map} +1 -1
  51. package/dist/{kg-entity-Cj8Q6YmU.mjs → kg-entity-1uqCnk4u.mjs} +1 -1
  52. package/dist/{kg-entity-Cj8Q6YmU.mjs.map → kg-entity-1uqCnk4u.mjs.map} +1 -1
  53. package/dist/{latent-ideas-BRy6HNwg.mjs → latent-ideas-CSuKfiq3.mjs} +4 -4
  54. package/dist/{latent-ideas-BRy6HNwg.mjs.map → latent-ideas-CSuKfiq3.mjs.map} +1 -1
  55. package/dist/{link-boost-BptbAPuv.mjs → link-boost-BtjzfE3c.mjs} +1 -1
  56. package/dist/{link-boost-BptbAPuv.mjs.map → link-boost-BtjzfE3c.mjs.map} +1 -1
  57. package/dist/{main-resolver-BIft_FVF.mjs → main-resolver-DyOrwNYw.mjs} +6 -6
  58. package/dist/{main-resolver-BIft_FVF.mjs.map → main-resolver-DyOrwNYw.mjs.map} +1 -1
  59. package/dist/main-resolver-vN09bkXv.mjs +7 -0
  60. package/dist/merge-CXGz5wXz.mjs +3 -0
  61. package/dist/{merge-xoyfZQ4W.mjs → merge-D58n_-LZ.mjs} +1 -1
  62. package/dist/{merge-xoyfZQ4W.mjs.map → merge-D58n_-LZ.mjs.map} +1 -1
  63. package/dist/{neighborhood-D8uscnGy.mjs → neighborhood-DNuRelRB.mjs} +2 -2
  64. package/dist/{neighborhood-D8uscnGy.mjs.map → neighborhood-DNuRelRB.mjs.map} +1 -1
  65. package/dist/{note-context-CgffaUMh.mjs → note-context-D9JZ4-7o.mjs} +1 -1
  66. package/dist/{note-context-CgffaUMh.mjs.map → note-context-D9JZ4-7o.mjs.map} +1 -1
  67. package/dist/{pai-home-CeOIYltV.mjs → pai-home-UncxWxlX.mjs} +1 -1
  68. package/dist/{pai-home-CeOIYltV.mjs.map → pai-home-UncxWxlX.mjs.map} +1 -1
  69. package/dist/{planner-DAQMTMjP.mjs → planner-DgC3oSvb.mjs} +7 -7
  70. package/dist/{planner-DAQMTMjP.mjs.map → planner-DgC3oSvb.mjs.map} +1 -1
  71. package/dist/{postgres-CxD6_Dca.mjs → postgres-CcsRKir-.mjs} +6 -5
  72. package/dist/{postgres-CxD6_Dca.mjs.map → postgres-CcsRKir-.mjs.map} +1 -1
  73. package/dist/{program-De1GVVM4.mjs → program-JhF2GgJY.mjs} +66 -66
  74. package/dist/program-JhF2GgJY.mjs.map +1 -0
  75. package/dist/query-feedback-BPHFFSu1.mjs +3 -0
  76. package/dist/{query-feedback-xz5axTj3.mjs → query-feedback-BPa0dISE.mjs} +2 -2
  77. package/dist/{query-feedback-xz5axTj3.mjs.map → query-feedback-BPa0dISE.mjs.map} +1 -1
  78. package/dist/{registry-db-Bkpbp7Gf.mjs → registry-db-DCzdI4sC.mjs} +2 -2
  79. package/dist/{registry-db-Bkpbp7Gf.mjs.map → registry-db-DCzdI4sC.mjs.map} +1 -1
  80. package/dist/registry-db-Hphlj3H4.mjs +3 -0
  81. package/dist/{registry-postgres-B07-hmn8.mjs → registry-postgres-BUnkHKYs.mjs} +2 -2
  82. package/dist/{registry-postgres-B07-hmn8.mjs.map → registry-postgres-BUnkHKYs.mjs.map} +1 -1
  83. package/dist/{registry-sqlite-CkGND_tm.mjs → registry-sqlite-CKYNwkUv.mjs} +2 -2
  84. package/dist/{registry-sqlite-CkGND_tm.mjs.map → registry-sqlite-CKYNwkUv.mjs.map} +1 -1
  85. package/dist/{reranker-BS5gqGeF.mjs → reranker-CFiEzHQu.mjs} +1 -1
  86. package/dist/{reranker-CRVawoEh.mjs → reranker-DKv80KO5.mjs} +1 -1
  87. package/dist/{reranker-CRVawoEh.mjs.map → reranker-DKv80KO5.mjs.map} +1 -1
  88. package/dist/{router-DjFZIoDl.mjs → router-CGZATlvm.mjs} +1 -1
  89. package/dist/{router-DjFZIoDl.mjs.map → router-CGZATlvm.mjs.map} +1 -1
  90. package/dist/router-CoA8Uy1m.mjs +3 -0
  91. package/dist/{run-CZVCCvXE.mjs → run-W2rV_9j0.mjs} +5 -5
  92. package/dist/{run-CZVCCvXE.mjs.map → run-W2rV_9j0.mjs.map} +1 -1
  93. package/dist/{run-env-BE5xyCwy.mjs → run-env-Br4Bc7Vz.mjs} +3 -3
  94. package/dist/{run-env-BE5xyCwy.mjs.map → run-env-Br4Bc7Vz.mjs.map} +1 -1
  95. package/dist/{runtime-paths-BRwDeBEg.mjs → runtime-paths-QGQJAekd.mjs} +1 -1
  96. package/dist/{runtime-paths-BRwDeBEg.mjs.map → runtime-paths-QGQJAekd.mjs.map} +1 -1
  97. package/dist/{search-BaTbyWjK.mjs → search-BOSphCJ1.mjs} +2 -2
  98. package/dist/{search-BaTbyWjK.mjs.map → search-BOSphCJ1.mjs.map} +1 -1
  99. package/dist/search-Bf3Kub3F.mjs +3 -0
  100. package/dist/{server-BY6AedmC.mjs → server-B71rem4q.mjs} +3 -3
  101. package/dist/{server-BY6AedmC.mjs.map → server-B71rem4q.mjs.map} +1 -1
  102. package/dist/{session-keepalive-CWGzLBVU.mjs → session-keepalive-Dgil9hjw.mjs} +7 -7
  103. package/dist/{session-keepalive-CWGzLBVU.mjs.map → session-keepalive-Dgil9hjw.mjs.map} +1 -1
  104. package/dist/{sources-BBe3AZ-W.mjs → sources-LcGptLA-.mjs} +1 -1
  105. package/dist/{sources-BBe3AZ-W.mjs.map → sources-LcGptLA-.mjs.map} +1 -1
  106. package/dist/{sqlite-CfKfuggn.mjs → sqlite-CAJcw2zL.mjs} +8 -7
  107. package/dist/sqlite-CAJcw2zL.mjs.map +1 -0
  108. package/dist/{state-n13uPK5B.mjs → state-CHltNjXI.mjs} +1 -1
  109. package/dist/{state-n13uPK5B.mjs.map → state-CHltNjXI.mjs.map} +1 -1
  110. package/dist/{state-CwlGAKgF.mjs → state-S9wlKarB.mjs} +1 -1
  111. package/dist/{stop-words-ClCwO8mZ.mjs → stop-words-BdQuaE9K.mjs} +1 -1
  112. package/dist/{stop-words-ClCwO8mZ.mjs.map → stop-words-BdQuaE9K.mjs.map} +1 -1
  113. package/dist/{themes-B2v7RNnA.mjs → themes-D1FQFthd.mjs} +3 -3
  114. package/dist/{themes-B2v7RNnA.mjs.map → themes-D1FQFthd.mjs.map} +1 -1
  115. package/dist/{tools-DFdPbX-X.mjs → tools-BFC-113F.mjs} +84 -68
  116. package/dist/tools-BFC-113F.mjs.map +1 -0
  117. package/dist/tools-BW7OXf-N.mjs +6 -0
  118. package/dist/{trace-BRflVyBi.mjs → trace-IDKK1VFs.mjs} +1 -1
  119. package/dist/{trace-BRflVyBi.mjs.map → trace-IDKK1VFs.mjs.map} +1 -1
  120. package/dist/{vault-indexer-mCFus_AM.mjs → vault-indexer-y6YC2-mh.mjs} +4 -3
  121. package/dist/vault-indexer-y6YC2-mh.mjs.map +1 -0
  122. package/dist/{wakeup-DO1O4udw.mjs → wakeup-CNZQZzsY.mjs} +3 -3
  123. package/dist/{wakeup-DO1O4udw.mjs.map → wakeup-CNZQZzsY.mjs.map} +1 -1
  124. package/dist/work-queue-worker-CkShUa0z.mjs +11 -0
  125. package/dist/{work-queue-worker-BbrC3I_R.mjs → work-queue-worker-Dy5FT2ak.mjs} +4 -4
  126. package/dist/{work-queue-worker-BbrC3I_R.mjs.map → work-queue-worker-Dy5FT2ak.mjs.map} +1 -1
  127. package/dist/{zettelkasten-BPxSOalU.mjs → zettelkasten-Dx-63BEk.mjs} +4 -4
  128. package/dist/{zettelkasten-BPxSOalU.mjs.map → zettelkasten-Dx-63BEk.mjs.map} +1 -1
  129. package/gemini-extension.json +1 -1
  130. package/package.json +1 -1
  131. package/pai-plugin.json +1 -0
  132. package/dist/async-DNVWtQl2.mjs.map +0 -1
  133. package/dist/daemon-C8-uzOuV.mjs +0 -19
  134. package/dist/detector-DgDn56j-.mjs +0 -3
  135. package/dist/federation-db-CJDw_Pwq.mjs +0 -3
  136. package/dist/helpers-Dj-B314s.mjs.map +0 -1
  137. package/dist/indexer-backend-BN9rpp9O.mjs +0 -5
  138. package/dist/main-resolver-C9-poMdi.mjs +0 -7
  139. package/dist/merge-BncjJTfb.mjs +0 -3
  140. package/dist/program-De1GVVM4.mjs.map +0 -1
  141. package/dist/query-feedback-COYCnvHQ.mjs +0 -3
  142. package/dist/registry-db-DwXpf7yF.mjs +0 -3
  143. package/dist/router-C5hPk9YP.mjs +0 -3
  144. package/dist/search-BQHL5wDr.mjs +0 -3
  145. package/dist/sqlite-CfKfuggn.mjs.map +0 -1
  146. package/dist/tools-B52IvaU6.mjs +0 -6
  147. package/dist/tools-DFdPbX-X.mjs.map +0 -1
  148. package/dist/vault-indexer-mCFus_AM.mjs.map +0 -1
  149. package/dist/work-queue-worker-z-AATZ2t.mjs +0 -11
  150. /package/dist/{indexer-backend-Cc-_NK3q.mjs → indexer-backend-DotpxHJd.mjs} +0 -0
@@ -1,3 +0,0 @@
1
- import { n as queriesDirPath, r as saveQueryResult, t as migrateQueriesDir } from "./query-feedback-xz5axTj3.mjs";
2
-
3
- export { saveQueryResult };
@@ -1,3 +0,0 @@
1
- import { n as openRegistry, r as registryDbPath, t as oldRegistryPath } from "./registry-db-Bkpbp7Gf.mjs";
2
-
3
- export { openRegistry };
@@ -1,3 +0,0 @@
1
- import { t as routeNotification } from "./router-DjFZIoDl.mjs";
2
-
3
- export { routeNotification };
@@ -1,3 +0,0 @@
1
- import { i as populateSlugs, n as buildFtsQuery, r as isQuerySyntaxError, t as applyRecencyBoost } from "./search-BaTbyWjK.mjs";
2
-
3
- export { applyRecencyBoost };
@@ -1 +0,0 @@
1
- {"version":3,"file":"sqlite-CfKfuggn.mjs","names":[],"sources":["../src/storage/sqlite/indexer.ts","../src/storage/sqlite/tunnels.ts","../src/storage/sqlite/sources.ts","../src/storage/sqlite/search.ts","../src/storage/sqlite/kg-entity.ts","../src/storage/sqlite.ts"],"sourcesContent":["/**\n * Synchronous (SQLite) indexer — moved from memory/indexer/sync.ts so the\n * only file that opens prepared statements against the federation database\n * lives under src/storage/ (design doc docs/design/postgres-only.md, unit 7).\n *\n * Scans project memory/ and Notes/ directories, chunks markdown files, and\n * inserts the resulting chunks into the federation memory store for BM25\n * search. Uses raw better-sqlite3 Database directly for maximum SQLite\n * performance (synchronous transactions, no serialisation overhead) — the\n * reason SQLiteBackend keeps this path instead of routing through the\n * generic (StorageBackend) indexer that Postgres uses.\n */\n\nimport { readFileSync, statSync, existsSync } from \"node:fs\";\nimport { join, relative, basename } from \"node:path\";\nimport type { Database } from \"better-sqlite3\";\nimport type { RegistryBackend } from \"../registry-interface.js\";\nimport { chunkMarkdown } from \"../../memory/chunker.js\";\nimport {\n sha256File,\n chunkId,\n detectTier,\n walkMdFiles,\n walkContentFiles,\n isPathTooBroadForContentScan,\n parseSessionTitleChunk,\n yieldToEventLoop,\n INDEX_YIELD_EVERY,\n} from \"../../memory/indexer/helpers.js\";\nimport type { IndexResult } from \"../../memory/indexer/types.js\";\n\n// ---------------------------------------------------------------------------\n// Single-file indexing\n// ---------------------------------------------------------------------------\n\n/**\n * Index a single file into the federation database.\n *\n * @returns true if the file was re-indexed (changed or new), false if skipped.\n */\nfunction indexFile(\n db: Database,\n projectId: number,\n rootPath: string,\n relativePath: string,\n source: string,\n tier: string,\n): boolean {\n const absPath = join(rootPath, relativePath);\n\n let content: string;\n let stat: ReturnType<typeof statSync>;\n try {\n content = readFileSync(absPath, \"utf8\");\n stat = statSync(absPath);\n } catch {\n // File unreadable or missing — skip silently\n return false;\n }\n\n const hash = sha256File(content);\n const mtime = Math.floor(stat.mtimeMs);\n const size = stat.size;\n\n const existing = db\n .prepare(\"SELECT hash FROM memory_files WHERE project_id = ? AND path = ?\")\n .get(projectId, relativePath) as { hash: string } | undefined;\n\n if (existing?.hash === hash) {\n return false;\n }\n\n const oldChunkIds = db\n .prepare(\"SELECT id FROM memory_chunks WHERE project_id = ? AND path = ?\")\n .all(projectId, relativePath) as Array<{ id: string }>;\n\n const deleteFts = db.prepare(\"DELETE FROM memory_fts WHERE id = ?\");\n const deleteChunk = db.prepare(\n \"DELETE FROM memory_chunks WHERE project_id = ? AND path = ?\",\n );\n\n db.transaction(() => {\n for (const row of oldChunkIds) {\n deleteFts.run(row.id);\n }\n deleteChunk.run(projectId, relativePath);\n })();\n\n const chunks = chunkMarkdown(content);\n\n const insertChunk = db.prepare(`\n INSERT INTO memory_chunks (id, project_id, source, tier, path, start_line, end_line, hash, text, updated_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n `);\n\n const insertFts = db.prepare(`\n INSERT INTO memory_fts (text, id, project_id, path, source, tier, start_line, end_line)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?)\n `);\n\n const upsertFile = db.prepare(`\n INSERT INTO memory_files (project_id, path, source, tier, hash, mtime, size)\n VALUES (?, ?, ?, ?, ?, ?, ?)\n ON CONFLICT(project_id, path) DO UPDATE SET\n source = excluded.source,\n tier = excluded.tier,\n hash = excluded.hash,\n mtime = excluded.mtime,\n size = excluded.size\n `);\n\n const updatedAt = Date.now();\n\n db.transaction(() => {\n for (let i = 0; i < chunks.length; i++) {\n const chunk = chunks[i]!;\n const id = chunkId(projectId, relativePath, i, chunk.startLine, chunk.endLine);\n insertChunk.run(\n id, projectId, source, tier, relativePath,\n chunk.startLine, chunk.endLine, chunk.hash, chunk.text, updatedAt,\n );\n insertFts.run(\n chunk.text, id, projectId, relativePath, source, tier,\n chunk.startLine, chunk.endLine,\n );\n }\n upsertFile.run(projectId, relativePath, source, tier, hash, mtime, size);\n })();\n\n return true;\n}\n\n// ---------------------------------------------------------------------------\n// Project-level indexing\n// ---------------------------------------------------------------------------\n\n/**\n * Index all memory, Notes, and content files for a single registered project.\n */\nasync function indexProject(\n db: Database,\n projectId: number,\n rootPath: string,\n claudeNotesDir?: string | null,\n): Promise<IndexResult> {\n const result: IndexResult = {\n filesProcessed: 0,\n chunksCreated: 0,\n filesSkipped: 0,\n };\n\n const filesToIndex: Array<{ absPath: string; rootBase: string; source: string; tier: string }> = [];\n\n const rootMemoryMd = join(rootPath, \"MEMORY.md\");\n if (existsSync(rootMemoryMd)) {\n filesToIndex.push({ absPath: rootMemoryMd, rootBase: rootPath, source: \"memory\", tier: \"evergreen\" });\n }\n\n const memoryDir = join(rootPath, \"memory\");\n for (const absPath of walkMdFiles(memoryDir)) {\n const relPath = relative(rootPath, absPath);\n const tier = detectTier(relPath);\n filesToIndex.push({ absPath, rootBase: rootPath, source: \"memory\", tier });\n }\n\n const notesDir = join(rootPath, \"Notes\");\n for (const absPath of walkMdFiles(notesDir)) {\n filesToIndex.push({ absPath, rootBase: rootPath, source: \"notes\", tier: \"session\" });\n }\n\n // Synthetic session-title chunks for Notes files with the standard filename format:\n // \"NNNN - YYYY-MM-DD - Descriptive Title.md\"\n {\n const titleInsertChunk = db.prepare(`\n INSERT OR IGNORE INTO memory_chunks (id, project_id, source, tier, path, start_line, end_line, hash, text, updated_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n `);\n const titleInsertFts = db.prepare(`\n INSERT OR IGNORE INTO memory_fts (text, id, project_id, path, source, tier, start_line, end_line)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?)\n `);\n const updatedAt = Date.now();\n for (const absPath of walkMdFiles(notesDir)) {\n const fileName = basename(absPath);\n const text = parseSessionTitleChunk(fileName);\n if (!text) continue;\n const relPath = relative(rootPath, absPath);\n const syntheticPath = `${relPath}::title`;\n const id = chunkId(projectId, syntheticPath, 0, 0, 0);\n const hash = sha256File(text);\n db.transaction(() => {\n titleInsertChunk.run(id, projectId, \"notes\", \"session\", syntheticPath, 0, 0, hash, text, updatedAt);\n titleInsertFts.run(text, id, projectId, syntheticPath, \"notes\", \"session\", 0, 0);\n })();\n }\n }\n\n if (!isPathTooBroadForContentScan(rootPath)) {\n for (const absPath of walkContentFiles(rootPath)) {\n filesToIndex.push({ absPath, rootBase: rootPath, source: \"content\", tier: \"topic\" });\n }\n }\n\n if (claudeNotesDir && claudeNotesDir !== notesDir) {\n for (const absPath of walkMdFiles(claudeNotesDir)) {\n filesToIndex.push({ absPath, rootBase: claudeNotesDir, source: \"notes\", tier: \"session\" });\n }\n\n {\n const updatedAt = Date.now();\n const titleInsertChunk2 = db.prepare(`\n INSERT OR IGNORE INTO memory_chunks (id, project_id, source, tier, path, start_line, end_line, hash, text, updated_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n `);\n const titleInsertFts2 = db.prepare(`\n INSERT OR IGNORE INTO memory_fts (text, id, project_id, path, source, tier, start_line, end_line)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?)\n `);\n for (const absPath of walkMdFiles(claudeNotesDir)) {\n const fileName = basename(absPath);\n const text = parseSessionTitleChunk(fileName);\n if (!text) continue;\n const relPath = relative(claudeNotesDir, absPath);\n const syntheticPath = `${relPath}::title`;\n const id = chunkId(projectId, syntheticPath, 0, 0, 0);\n const hash = sha256File(text);\n db.transaction(() => {\n titleInsertChunk2.run(id, projectId, \"notes\", \"session\", syntheticPath, 0, 0, hash, text, updatedAt);\n titleInsertFts2.run(text, id, projectId, syntheticPath, \"notes\", \"session\", 0, 0);\n })();\n }\n }\n\n if (claudeNotesDir.endsWith(\"/Notes\")) {\n const claudeProjectDir = claudeNotesDir.slice(0, -\"/Notes\".length);\n const claudeMemoryDir = join(claudeProjectDir, \"memory\");\n\n const claudeMemoryMd = join(claudeProjectDir, \"MEMORY.md\");\n if (existsSync(claudeMemoryMd)) {\n filesToIndex.push({\n absPath: claudeMemoryMd,\n rootBase: claudeProjectDir,\n source: \"memory\",\n tier: \"evergreen\",\n });\n }\n\n for (const absPath of walkMdFiles(claudeMemoryDir)) {\n const relPath = relative(claudeProjectDir, absPath);\n const tier = detectTier(relPath);\n filesToIndex.push({ absPath, rootBase: claudeProjectDir, source: \"memory\", tier });\n }\n }\n }\n\n await yieldToEventLoop();\n\n let filesSinceYield = 0;\n\n for (const { absPath, rootBase, source, tier } of filesToIndex) {\n if (filesSinceYield >= INDEX_YIELD_EVERY) {\n await yieldToEventLoop();\n filesSinceYield = 0;\n }\n filesSinceYield++;\n\n const relPath = relative(rootBase, absPath);\n const changed = indexFile(db, projectId, rootBase, relPath, source, tier);\n\n if (changed) {\n const count = db\n .prepare(\"SELECT COUNT(*) as n FROM memory_chunks WHERE project_id = ? AND path = ?\")\n .get(projectId, relPath) as { n: number };\n\n result.filesProcessed++;\n result.chunksCreated += count.n;\n } else {\n result.filesSkipped++;\n }\n }\n\n // Prune stale paths: remove DB entries for files that no longer exist on disk.\n const livePaths = new Set<string>();\n for (const { absPath, rootBase } of filesToIndex) {\n livePaths.add(relative(rootBase, absPath));\n }\n\n const dbChunkPaths = db\n .prepare(\"SELECT DISTINCT path FROM memory_chunks WHERE project_id = ?\")\n .all(projectId) as Array<{ path: string }>;\n\n const stalePaths: string[] = [];\n for (const row of dbChunkPaths) {\n const basePath = row.path.endsWith(\"::title\")\n ? row.path.slice(0, -\"::title\".length)\n : row.path;\n if (!livePaths.has(basePath)) {\n stalePaths.push(row.path);\n }\n }\n\n if (stalePaths.length > 0) {\n const deleteChunksFts = db.prepare(\"DELETE FROM memory_fts WHERE id = ?\");\n const deleteChunks = db.prepare(\n \"DELETE FROM memory_chunks WHERE project_id = ? AND path = ?\",\n );\n const deleteFile = db.prepare(\n \"DELETE FROM memory_files WHERE project_id = ? AND path = ?\",\n );\n\n db.transaction(() => {\n for (const stalePath of stalePaths) {\n const chunkIds = db\n .prepare(\"SELECT id FROM memory_chunks WHERE project_id = ? AND path = ?\")\n .all(projectId, stalePath) as Array<{ id: string }>;\n for (const { id } of chunkIds) {\n deleteChunksFts.run(id);\n }\n deleteChunks.run(projectId, stalePath);\n deleteFile.run(projectId, stalePath);\n }\n })();\n }\n\n return result;\n}\n\n// ---------------------------------------------------------------------------\n// Global indexing (all registered projects)\n// ---------------------------------------------------------------------------\n\n/**\n * Index all active projects registered in the registry DB.\n *\n * Async: yields to the event loop between each project so that the daemon's\n * Unix socket server can process IPC requests (e.g. status) while indexing.\n */\nexport async function indexAllSqlite(\n db: Database,\n registry: RegistryBackend,\n): Promise<{ projects: number; result: IndexResult }> {\n const projects = await registry.listProjects({ status: \"active\" });\n\n const totals: IndexResult = {\n filesProcessed: 0,\n chunksCreated: 0,\n filesSkipped: 0,\n };\n\n for (const project of projects) {\n await yieldToEventLoop();\n const r = await indexProject(db, project.id, project.root_path, project.claude_notes_dir);\n totals.filesProcessed += r.filesProcessed;\n totals.chunksCreated += r.chunksCreated;\n totals.filesSkipped += r.filesSkipped;\n }\n\n return { projects: projects.length, result: totals };\n}\n","/**\n * SQLite implementation of cross-project concept \"tunnel\" detection — moved\n * from memory/tunnels.ts so the only file that runs raw FTS5/vocab SQL\n * against the federation database lives under src/storage/.\n *\n * Algorithm: pull candidate terms from the FTS5 vocabulary shadow table (or\n * sample chunk text when the vocab view is unavailable), then for each\n * candidate count distinct projects and total occurrences.\n */\n\nimport type { Database } from \"better-sqlite3\";\nimport { STOP_WORDS } from \"../../utils/stop-words.js\";\nimport type { RegistryBackend } from \"../registry-interface.js\";\nimport type { Tunnel, FindTunnelsOptions, FindTunnelsResult } from \"../../memory/tunnels.js\";\n\ninterface ProjectSlugMap {\n [id: number]: string;\n}\n\nasync function findTunnelsForSlugMap(\n db: Database,\n slugMap: ProjectSlugMap,\n opts: Required<FindTunnelsOptions>,\n): Promise<FindTunnelsResult> {\n const projectIds = Object.keys(slugMap).map(Number);\n if (projectIds.length < 2) {\n return { tunnels: [], projects_analyzed: projectIds.length, total_concepts_evaluated: 0 };\n }\n\n // Step 1 — collect candidate terms via FTS5 vocabulary shadow table.\n let candidateTerms: string[] = [];\n\n try {\n const vocabRows = db\n .prepare(\n `SELECT term, SUM(doc) AS doc_count, SUM(cnt) AS total_cnt\n FROM memory_fts_v\n GROUP BY term\n HAVING SUM(cnt) >= ?\n ORDER BY SUM(doc) DESC\n LIMIT 500`\n )\n .all(opts.min_occurrences) as Array<{ term: string; doc_count: number; total_cnt: number }>;\n\n candidateTerms = vocabRows\n .map((r) => r.term)\n .filter((t) => t.length >= 3 && !STOP_WORDS.has(t));\n } catch {\n // Vocab table not available — fall back to sampling common words from chunks.\n const sampleRows = db\n .prepare(\n `SELECT LOWER(text) AS text FROM memory_chunks\n WHERE LENGTH(text) > 20\n ORDER BY RANDOM()\n LIMIT 2000`\n )\n .all() as Array<{ text: string }>;\n\n const freq = new Map<string, number>();\n for (const { text } of sampleRows) {\n const tokens = text\n .split(/[\\s\\p{P}]+/u)\n .filter(Boolean)\n .filter((t: string) => t.length >= 3 && !STOP_WORDS.has(t));\n for (const t of tokens) {\n freq.set(t, (freq.get(t) ?? 0) + 1);\n }\n }\n candidateTerms = [...freq.entries()]\n .filter(([, n]) => n >= opts.min_occurrences)\n .sort((a, b) => b[1] - a[1])\n .slice(0, 200)\n .map(([t]) => t);\n }\n\n if (candidateTerms.length === 0) {\n return { tunnels: [], projects_analyzed: projectIds.length, total_concepts_evaluated: 0 };\n }\n\n // Step 2 — for each candidate, count distinct projects and occurrences.\n const tunnels: Tunnel[] = [];\n\n for (const term of candidateTerms) {\n try {\n const rows = db\n .prepare(\n `SELECT c.project_id, COUNT(*) AS cnt,\n MIN(c.updated_at) AS first_seen,\n MAX(c.updated_at) AS last_seen\n FROM memory_fts f\n JOIN memory_chunks c ON c.id = f.id\n WHERE memory_fts MATCH ?\n AND c.project_id IN (${projectIds.map(() => \"?\").join(\", \")})\n GROUP BY c.project_id`\n )\n .all(`\"${term.replace(/\"/g, '\"\"')}\"`, ...projectIds) as Array<{\n project_id: number;\n cnt: number;\n first_seen: number;\n last_seen: number;\n }>;\n\n if (rows.length < opts.min_projects) continue;\n\n const totalOccurrences = rows.reduce((s, r) => s + Number(r.cnt), 0);\n if (totalOccurrences < opts.min_occurrences) continue;\n\n const projects = rows\n .map((r) => slugMap[r.project_id] ?? String(r.project_id))\n .filter(Boolean);\n const firstSeen = Math.min(...rows.map((r) => r.first_seen));\n const lastSeen = Math.max(...rows.map((r) => r.last_seen));\n\n tunnels.push({\n concept: term,\n projects,\n occurrences: totalOccurrences,\n first_seen: firstSeen,\n last_seen: lastSeen,\n });\n } catch {\n // Skip problematic terms\n continue;\n }\n }\n\n tunnels.sort((a, b) => {\n const byProjects = b.projects.length - a.projects.length;\n if (byProjects !== 0) return byProjects;\n return b.occurrences - a.occurrences;\n });\n\n return {\n tunnels: tunnels.slice(0, opts.limit),\n projects_analyzed: projectIds.length,\n total_concepts_evaluated: candidateTerms.length,\n };\n}\n\n/** Find cross-project concept tunnels against a SQLite federation database. */\nexport async function findTunnelsSqlite(\n db: Database,\n registry: RegistryBackend,\n options?: FindTunnelsOptions,\n): Promise<FindTunnelsResult> {\n const opts: Required<FindTunnelsOptions> = {\n min_projects: options?.min_projects ?? 2,\n min_occurrences: options?.min_occurrences ?? 3,\n limit: options?.limit ?? 20,\n };\n\n const projectRows = await registry.listProjects({ excludeArchived: true });\n const slugMap: ProjectSlugMap = {};\n for (const { id, slug } of projectRows) {\n slugMap[id] = slug;\n }\n\n return findTunnelsForSlugMap(db, slugMap, opts);\n}\n","/**\n * SQLite implementation of the `pai memory sources` aggregate report — moved\n * from cli/commands/memory/sources.ts so the only file running ad-hoc\n * aggregate SQL against the federation database lives under src/storage/.\n */\n\nimport type { Database } from \"better-sqlite3\";\nimport type { MemorySourcesReport } from \"../interface.js\";\n\nexport function getMemorySourcesReportSqlite(db: Database): MemorySourcesReport {\n const composition = db\n .prepare(\n `SELECT source, tier, COUNT(*) AS chunks,\n SUM(CASE WHEN embedding IS NOT NULL THEN 1 ELSE 0 END) AS embedded\n FROM memory_chunks GROUP BY source, tier ORDER BY COUNT(*) DESC`\n )\n .all() as Array<{ source: string; tier: string; chunks: number; embedded: number }>;\n\n const paths = db\n .prepare(`SELECT path, COUNT(*) AS chunks FROM memory_chunks GROUP BY path`)\n .all() as Array<{ path: string; chunks: number }>;\n\n const churn = db\n .prepare(\n `SELECT date(updated_at/1000,'unixepoch') AS day, COUNT(*) AS chunks,\n SUM(CASE WHEN embedding IS NOT NULL THEN 1 ELSE 0 END) AS embedded\n FROM memory_chunks GROUP BY day ORDER BY day DESC LIMIT 10`\n )\n .all() as Array<{ day: string; chunks: number; embedded: number }>;\n\n return { composition, paths, churn };\n}\n","/**\n * SQLite search implementation — moved from memory/search.ts so the only\n * file running raw FTS5/vector queries against the federation database\n * lives under src/storage/. Query building, slug population, and recency\n * boosting are pure logic with no SQL and stay in memory/search.ts.\n *\n * BM25 uses SQLite's FTS5 extension. Semantic search requires embeddings to\n * have been generated first via the indexer's embed pass.\n */\n\nimport type { Database } from \"better-sqlite3\";\nimport { deserializeEmbedding, cosineSimilarity } from \"../../memory/embeddings.js\";\nimport {\n buildFtsQuery,\n isQuerySyntaxError,\n type SearchResult,\n type SearchOptions,\n} from \"../../memory/search.js\";\n\n// ---------------------------------------------------------------------------\n// Keyword (BM25) search\n// ---------------------------------------------------------------------------\n\n/**\n * Search across all indexed memory using FTS5 BM25 ranking.\n *\n * Results are ordered by BM25 score (most relevant first).\n * FTS5 bm25() returns negative values; closer to 0 = more relevant.\n * We negate the score so callers get positive values where higher = better.\n */\nexport function searchMemory(\n db: Database,\n query: string,\n opts?: SearchOptions,\n): SearchResult[] {\n const maxResults = opts?.maxResults ?? 10;\n const ftsQuery = buildFtsQuery(query);\n\n const conditions: string[] = [];\n const params: (string | number)[] = [ftsQuery];\n\n if (opts?.projectIds && opts.projectIds.length > 0) {\n const placeholders = opts.projectIds.map(() => \"?\").join(\", \");\n conditions.push(`c.project_id IN (${placeholders})`);\n params.push(...opts.projectIds);\n }\n\n if (opts?.sources && opts.sources.length > 0) {\n const placeholders = opts.sources.map(() => \"?\").join(\", \");\n conditions.push(`c.source IN (${placeholders})`);\n params.push(...opts.sources);\n }\n\n if (opts?.tiers && opts.tiers.length > 0) {\n const placeholders = opts.tiers.map(() => \"?\").join(\", \");\n conditions.push(`c.tier IN (${placeholders})`);\n params.push(...opts.tiers);\n }\n\n const whereClause = conditions.length > 0\n ? \"AND \" + conditions.join(\" AND \")\n : \"\";\n\n params.push(maxResults);\n\n const sql = `\n SELECT\n c.id,\n c.project_id,\n c.path,\n c.start_line,\n c.end_line,\n c.text AS snippet,\n c.tier,\n c.source,\n c.updated_at,\n c.last_accessed_at,\n c.relevance_score,\n bm25(memory_fts) AS bm25_score\n FROM memory_fts\n JOIN memory_chunks c ON memory_fts.id = c.id\n WHERE memory_fts MATCH ?\n ${whereClause}\n ORDER BY bm25_score\n LIMIT ?\n `;\n\n let rows: Array<{\n id: string;\n project_id: number;\n path: string;\n start_line: number;\n end_line: number;\n snippet: string;\n tier: string;\n source: string;\n updated_at: number;\n last_accessed_at: number | null;\n relevance_score: number | null;\n bm25_score: number;\n }>;\n\n try {\n rows = db.prepare(sql).all(...params) as typeof rows;\n } catch (e) {\n // FTS5 MATCH throws on a malformed query, and for THAT an empty result is the\n // honest answer — nothing matches a query that cannot be parsed.\n //\n // Everything else is a failure of the store: a missing table, a corrupt\n // index, a locked database. Those used to return [] as well, which made an\n // unusable index byte-identical to a genuine miss. The Postgres path had the\n // same defect and it cost a real wrong answer on 2026-08-04 — the backend was\n // down for two hours, every search reported \"No results found\", and a sibling\n // session told the owner a DMARC note did not exist. See\n // storage/postgres/search.ts.\n if (!isQuerySyntaxError(e)) {\n throw new Error(\n `Memory keyword search failed — the index is unusable, so this is NOT an ` +\n `empty result set. Cause: ${e instanceof Error ? e.message : String(e)}`\n );\n }\n return [];\n }\n\n const minScore = opts?.minScore ?? 0.0;\n\n return rows\n .map((row) => {\n const baseScore = -row.bm25_score;\n const relevanceScore = row.relevance_score ?? 0.5;\n const score = baseScore * (0.5 + relevanceScore);\n return {\n chunkId: row.id,\n projectId: row.project_id,\n path: row.path,\n startLine: row.start_line,\n endLine: row.end_line,\n snippet: row.snippet,\n score,\n tier: row.tier,\n source: row.source,\n updatedAt: row.updated_at,\n lastAccessedAt: row.last_accessed_at ?? undefined,\n };\n })\n .filter((r) => r.score >= minScore);\n}\n\n// ---------------------------------------------------------------------------\n// Semantic search\n// ---------------------------------------------------------------------------\n\n/**\n * Search chunks using brute-force cosine similarity over stored embeddings.\n *\n * Only chunks that have a non-null embedding BLOB are considered.\n *\n * @param queryEmbedding Pre-computed Float32Array for the search query.\n */\nexport function searchMemorySemantic(\n db: Database,\n queryEmbedding: Float32Array,\n opts?: SearchOptions,\n): SearchResult[] {\n const maxResults = opts?.maxResults ?? 10;\n\n const conditions: string[] = [\"embedding IS NOT NULL\"];\n const params: (string | number)[] = [];\n\n if (opts?.projectIds && opts.projectIds.length > 0) {\n const placeholders = opts.projectIds.map(() => \"?\").join(\", \");\n conditions.push(`project_id IN (${placeholders})`);\n params.push(...opts.projectIds);\n }\n\n if (opts?.sources && opts.sources.length > 0) {\n const placeholders = opts.sources.map(() => \"?\").join(\", \");\n conditions.push(`source IN (${placeholders})`);\n params.push(...opts.sources);\n }\n\n if (opts?.tiers && opts.tiers.length > 0) {\n const placeholders = opts.tiers.map(() => \"?\").join(\", \");\n conditions.push(`tier IN (${placeholders})`);\n params.push(...opts.tiers);\n }\n\n const where = \"WHERE \" + conditions.join(\" AND \");\n\n // Hard cap for SQLite semantic path — prevents OOM on large corpora.\n // Use Postgres for production semantic search.\n const sql = `\n SELECT id, project_id, path, start_line, end_line, text, tier, source, embedding, updated_at, last_accessed_at, relevance_score\n FROM memory_chunks\n ${where}\n LIMIT 5000\n `;\n\n const rows = db.prepare(sql).all(...params) as Array<{\n id: string;\n project_id: number;\n path: string;\n start_line: number;\n end_line: number;\n text: string;\n tier: string;\n source: string;\n embedding: Buffer;\n updated_at: number;\n last_accessed_at: number | null;\n relevance_score: number | null;\n }>;\n\n if (rows.length === 0) return [];\n\n const scored = rows.map((row) => {\n const vec = deserializeEmbedding(row.embedding);\n const baseScore = cosineSimilarity(queryEmbedding, vec);\n const relevanceScore = row.relevance_score ?? 0.5;\n const score = baseScore * (0.5 + relevanceScore);\n return {\n chunkId: row.id,\n projectId: row.project_id,\n path: row.path,\n startLine: row.start_line,\n endLine: row.end_line,\n snippet: row.text,\n score,\n tier: row.tier,\n source: row.source,\n updatedAt: row.updated_at,\n lastAccessedAt: row.last_accessed_at ?? undefined,\n };\n });\n\n const minScore = opts?.minScore ?? -Infinity;\n\n return scored\n .filter((r) => r.score >= minScore)\n .sort((a, b) => b.score - a.score)\n .slice(0, maxResults);\n}\n\n// ---------------------------------------------------------------------------\n// Hybrid search\n// ---------------------------------------------------------------------------\n\n/**\n * Combine BM25 keyword search and semantic search using normalized scores.\n *\n * Both score sets are min-max normalized to [0,1] before combining, so neither\n * dominates the other regardless of their raw scales.\n */\nexport function searchMemoryHybrid(\n db: Database,\n query: string,\n queryEmbedding: Float32Array,\n opts?: SearchOptions & { keywordWeight?: number; semanticWeight?: number },\n): SearchResult[] {\n const maxResults = opts?.maxResults ?? 10;\n const kw = opts?.keywordWeight ?? 0.5;\n const sw = opts?.semanticWeight ?? 0.5;\n\n const keywordResults = searchMemory(db, query, { ...opts, maxResults: 50 });\n const semanticResults = searchMemorySemantic(db, queryEmbedding, { ...opts, maxResults: 50 });\n\n if (keywordResults.length === 0 && semanticResults.length === 0) return [];\n\n const keyFor = (r: SearchResult) =>\n `${r.projectId}:${r.path}:${r.startLine}:${r.endLine}`;\n\n function minMaxNormalize(items: SearchResult[]): Map<string, number> {\n if (items.length === 0) return new Map();\n const min = Math.min(...items.map((r) => r.score));\n const max = Math.max(...items.map((r) => r.score));\n const range = max - min;\n const m = new Map<string, number>();\n for (const r of items) {\n m.set(keyFor(r), range === 0 ? 1 : (r.score - min) / range);\n }\n return m;\n }\n\n const kwNorm = minMaxNormalize(keywordResults);\n const semNorm = minMaxNormalize(semanticResults);\n\n const allKeys = new Set<string>([\n ...keywordResults.map(keyFor),\n ...semanticResults.map(keyFor),\n ]);\n\n const metaMap = new Map<string, SearchResult>();\n for (const r of [...keywordResults, ...semanticResults]) {\n metaMap.set(keyFor(r), r);\n }\n\n const combined: Array<SearchResult & { combinedScore: number }> = [];\n for (const key of allKeys) {\n const meta = metaMap.get(key)!;\n const kwScore = kwNorm.get(key) ?? 0;\n const semScore = semNorm.get(key) ?? 0;\n const combinedScore = kw * kwScore + sw * semScore;\n combined.push({ ...meta, score: combinedScore, combinedScore });\n }\n\n return combined\n .sort((a, b) => b.score - a.score)\n .slice(0, maxResults)\n .map(({ combinedScore: _unused, ...r }) => r);\n}\n\n// ---------------------------------------------------------------------------\n// Access timestamp tracking (QW2)\n// ---------------------------------------------------------------------------\n\n/**\n * Update last_accessed_at for a set of chunk IDs to the current timestamp.\n *\n * Best-effort: errors are silently ignored so search is never blocked.\n */\nexport function touchChunksLastAccessed(db: Database, chunkIds: string[]): void {\n if (chunkIds.length === 0) return;\n try {\n const now = Date.now();\n const placeholders = chunkIds.map(() => \"?\").join(\", \");\n db.prepare(\n `UPDATE memory_chunks SET last_accessed_at = ? WHERE id IN (${placeholders})`\n ).run(now, ...chunkIds);\n } catch {\n // non-critical — do not block search results\n }\n}\n","/**\n * SQLite kg_entities CRUD — moved from memory/kg-entity.ts so the only file\n * running raw SQL against kg_entities lives under src/storage/. The pure\n * content-addressing helpers (entityContentId/edgeContentId) and the\n * KgEntity/KgEntityUpsertParams types they share with PostgresBackend stay\n * in memory/kg-entity.ts.\n */\n\nimport type { Database } from \"better-sqlite3\";\nimport { entityContentId, type KgEntity, type KgEntityUpsertParams } from \"../../memory/kg-entity.js\";\n\n/**\n * Upsert a KG entity in the federation SQLite database.\n *\n * If the entity already exists for this tenant:\n * - Updates last_seen to now\n * - Increments mention_count\n * - Updates description if provided (overwrites older description)\n *\n * Returns the entity_id for use as a foreign key in kg_triples.\n */\nexport function upsertKgEntitySqlite(\n db: Database,\n params: KgEntityUpsertParams\n): string {\n const tenantId = params.tenantId ?? \"default\";\n const entityId = entityContentId(params.name, tenantId);\n const now = Date.now();\n\n db.prepare(`\n INSERT INTO kg_entities\n (entity_id, tenant_id, name, type, description, first_seen, last_seen, mention_count, feedback_weight)\n VALUES\n (?, ?, ?, ?, ?, ?, ?, 1, 0.5)\n ON CONFLICT(entity_id) DO UPDATE SET\n last_seen = excluded.last_seen,\n mention_count = mention_count + 1,\n description = COALESCE(excluded.description, description),\n type = CASE WHEN excluded.type != 'unknown' THEN excluded.type ELSE type END\n `).run(\n entityId,\n tenantId,\n params.name,\n params.type ?? \"unknown\",\n params.description ?? null,\n now,\n now\n );\n\n return entityId;\n}\n\n/**\n * Look up a KG entity by name within a tenant.\n * Returns null if the entity does not exist.\n */\nexport function findKgEntitySqlite(\n db: Database,\n name: string,\n tenantId = \"default\"\n): KgEntity | null {\n const entityId = entityContentId(name, tenantId);\n const row = db.prepare(\n \"SELECT * FROM kg_entities WHERE entity_id = ? AND tenant_id = ?\"\n ).get(entityId, tenantId) as KgEntity | undefined;\n return row ?? null;\n}\n\n/**\n * List KG entities for a tenant, optionally filtered by type.\n */\nexport function listKgEntitiesSqlite(\n db: Database,\n tenantId = \"default\",\n type?: string,\n limit = 100\n): KgEntity[] {\n if (type) {\n return db.prepare(\n \"SELECT * FROM kg_entities WHERE tenant_id = ? AND type = ? ORDER BY mention_count DESC LIMIT ?\"\n ).all(tenantId, type, limit) as KgEntity[];\n }\n return db.prepare(\n \"SELECT * FROM kg_entities WHERE tenant_id = ? ORDER BY mention_count DESC LIMIT ?\"\n ).all(tenantId, limit) as KgEntity[];\n}\n\n/**\n * Apply an EMA (Exponential Moving Average) feedback update to an entity's weight.\n *\n * EMA formula: new_weight = old_weight + alpha * (target - old_weight)\n */\nexport function updateEntityFeedbackWeightSqlite(\n db: Database,\n entityId: string,\n normalizedRating: number,\n alpha = 0.1\n): void {\n const row = db.prepare(\n \"SELECT feedback_weight FROM kg_entities WHERE entity_id = ?\"\n ).get(entityId) as { feedback_weight: number } | undefined;\n\n if (!row) return;\n\n const newWeight = row.feedback_weight + alpha * (normalizedRating - row.feedback_weight);\n db.prepare(\n \"UPDATE kg_entities SET feedback_weight = ? WHERE entity_id = ?\"\n ).run(newWeight, entityId);\n}\n","/**\n * SQLiteBackend — wraps the existing better-sqlite3 federation.db\n * behind the StorageBackend interface.\n *\n * This is a thin adapter. The heavy lifting is all in the existing\n * memory/indexer.ts and memory/search.ts code; we just provide a\n * backend-agnostic surface so the daemon and tools can call either\n * SQLite or Postgres transparently.\n */\n\nimport type { Database } from \"better-sqlite3\";\nimport type {\n StorageBackend, ChunkRow, FileRow, FederationStats,\n MemorySourcesReport, FindTunnelsOptions, FindTunnelsResult,\n KgStats, ObservationRow, ObservationWithCwd, StoreObservationInput, QueryObservationsOptions,\n ObservationStats, SessionSummaryRow, StoreSessionSummaryInput,\n SkillTelemetryRow, RecordSkillInvocationInput, QuerySkillTelemetryOptions,\n} from \"./interface.js\";\nimport type { SearchResult, SearchOptions } from \"../memory/search.js\";\nimport type { KgEntity, KgEntityUpsertParams } from \"../memory/kg-entity.js\";\nimport type { KgAddParams, KgQueryParams, KgTriple, KgContradiction } from \"../memory/kg.js\";\nimport type { IndexResult } from \"../memory/indexer/types.js\";\nimport type { RegistryBackend } from \"./registry-interface.js\";\nimport { indexAllSqlite } from \"./sqlite/indexer.js\";\nimport { findTunnelsSqlite } from \"./sqlite/tunnels.js\";\nimport { getMemorySourcesReportSqlite } from \"./sqlite/sources.js\";\nimport { searchMemory, searchMemorySemantic, touchChunksLastAccessed } from \"./sqlite/search.js\";\nimport {\n upsertKgEntitySqlite,\n findKgEntitySqlite,\n listKgEntitiesSqlite,\n updateEntityFeedbackWeightSqlite,\n} from \"./sqlite/kg-entity.js\";\n\nexport class SQLiteBackend implements StorageBackend {\n readonly backendType = \"sqlite\" as const;\n readonly supportsPostgresFeatures = false;\n\n private db: Database;\n\n constructor(db: Database) {\n this.db = db;\n }\n\n /**\n * Expose the raw better-sqlite3 Database handle. Storage-internal only —\n * used by db-admin.ts for `pai db query|tables|schema` (the one place\n * outside this class allowed to reach for it). Every other operation must\n * be a StorageBackend method; nothing outside src/storage/ may call this.\n */\n getRawDb(): Database {\n return this.db;\n }\n\n // -------------------------------------------------------------------------\n // Lifecycle\n // -------------------------------------------------------------------------\n\n async close(): Promise<void> {\n try {\n this.db.close();\n } catch {\n // ignore\n }\n }\n\n async getStats(): Promise<FederationStats> {\n const files = (\n this.db.prepare(\"SELECT COUNT(*) AS n FROM memory_files\").get() as { n: number }\n ).n;\n const chunks = (\n this.db.prepare(\"SELECT COUNT(*) AS n FROM memory_chunks\").get() as { n: number }\n ).n;\n return { files, chunks };\n }\n\n async getProjectStats(projectId: number): Promise<FederationStats> {\n const files = (\n this.db.prepare(\"SELECT COUNT(*) AS n FROM memory_files WHERE project_id = ?\").get(projectId) as { n: number }\n ).n;\n const chunks = (\n this.db.prepare(\"SELECT COUNT(*) AS n FROM memory_chunks WHERE project_id = ?\").get(projectId) as { n: number }\n ).n;\n return { files, chunks };\n }\n\n // -------------------------------------------------------------------------\n // File tracking\n // -------------------------------------------------------------------------\n\n async getFileHash(projectId: number, path: string): Promise<string | undefined> {\n const row = this.db\n .prepare(\"SELECT hash FROM memory_files WHERE project_id = ? AND path = ?\")\n .get(projectId, path) as { hash: string } | undefined;\n return row?.hash;\n }\n\n async upsertFile(file: FileRow): Promise<void> {\n this.db\n .prepare(\n `INSERT INTO memory_files (project_id, path, source, tier, hash, mtime, size)\n VALUES (?, ?, ?, ?, ?, ?, ?)\n ON CONFLICT(project_id, path) DO UPDATE SET\n source = excluded.source,\n tier = excluded.tier,\n hash = excluded.hash,\n mtime = excluded.mtime,\n size = excluded.size`\n )\n .run(file.projectId, file.path, file.source, file.tier, file.hash, file.mtime, file.size);\n }\n\n // -------------------------------------------------------------------------\n // Chunk management\n // -------------------------------------------------------------------------\n\n async getChunkIds(projectId: number, path: string): Promise<string[]> {\n const rows = this.db\n .prepare(\"SELECT id FROM memory_chunks WHERE project_id = ? AND path = ?\")\n .all(projectId, path) as Array<{ id: string }>;\n return rows.map((r) => r.id);\n }\n\n async deleteChunksForFile(projectId: number, path: string): Promise<void> {\n const ids = await this.getChunkIds(projectId, path);\n const deleteFts = this.db.prepare(\"DELETE FROM memory_fts WHERE id = ?\");\n const deleteChunks = this.db.prepare(\n \"DELETE FROM memory_chunks WHERE project_id = ? AND path = ?\"\n );\n this.db.transaction(() => {\n for (const id of ids) {\n deleteFts.run(id);\n }\n deleteChunks.run(projectId, path);\n })();\n }\n\n async insertChunks(chunks: ChunkRow[]): Promise<void> {\n if (chunks.length === 0) return;\n\n const insertChunk = this.db.prepare(\n `INSERT INTO memory_chunks (id, project_id, source, tier, path, start_line, end_line, hash, text, updated_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`\n );\n const insertFts = this.db.prepare(\n `INSERT INTO memory_fts (text, id, project_id, path, source, tier, start_line, end_line)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?)`\n );\n\n this.db.transaction(() => {\n for (const c of chunks) {\n insertChunk.run(\n c.id,\n c.projectId,\n c.source,\n c.tier,\n c.path,\n c.startLine,\n c.endLine,\n c.hash,\n c.text,\n c.updatedAt\n );\n insertFts.run(\n c.text,\n c.id,\n c.projectId,\n c.path,\n c.source,\n c.tier,\n c.startLine,\n c.endLine\n );\n }\n })();\n }\n\n async getDistinctChunkPaths(projectId: number): Promise<string[]> {\n const rows = this.db\n .prepare(\"SELECT DISTINCT path FROM memory_chunks WHERE project_id = ?\")\n .all(projectId) as Array<{ path: string }>;\n return rows.map((r) => r.path);\n }\n\n async deletePaths(projectId: number, paths: string[]): Promise<void> {\n if (paths.length === 0) return;\n const deleteFts = this.db.prepare(\"DELETE FROM memory_fts WHERE id = ?\");\n const deleteChunks = this.db.prepare(\n \"DELETE FROM memory_chunks WHERE project_id = ? AND path = ?\"\n );\n const deleteFile = this.db.prepare(\n \"DELETE FROM memory_files WHERE project_id = ? AND path = ?\"\n );\n this.db.transaction(() => {\n for (const path of paths) {\n const ids = this.db\n .prepare(\"SELECT id FROM memory_chunks WHERE project_id = ? AND path = ?\")\n .all(projectId, path) as Array<{ id: string }>;\n for (const { id } of ids) {\n deleteFts.run(id);\n }\n deleteChunks.run(projectId, path);\n deleteFile.run(projectId, path);\n }\n })();\n }\n\n async getUnembeddedChunkIds(\n projectId?: number,\n limit?: number,\n after?: { projectId: number; id: string } | null,\n ): Promise<Array<{ id: string; text: string; project_id: number; path: string }>> {\n const conditions = [\"embedding IS NULL\"];\n const params: (string | number)[] = [];\n\n if (projectId !== undefined) {\n conditions.push(\"project_id = ?\");\n params.push(projectId);\n }\n\n // Once paginating, the priority ORDER BY below no longer applies — pages\n // must be ordered by the same (project_id, id) the cursor seeks on.\n const paginating = after !== undefined;\n if (after) {\n conditions.push(\"(project_id > ? OR (project_id = ? AND id > ?))\");\n params.push(after.projectId, after.projectId, after.id);\n }\n\n const where = \"WHERE \" + conditions.join(\" AND \");\n // Prioritize real knowledge notes over PAI session/job-search noise.\n // CASE expression assigns lower priority numbers to knowledge paths.\n const orderBy = paginating\n ? \"project_id, id\"\n : `CASE\n WHEN path LIKE '🧠 Ideaverse/%' THEN 0\n WHEN path LIKE 'Z - Zettelkasten/%' THEN 0\n WHEN path LIKE '💼 Business/%' THEN 0\n WHEN path LIKE '📆 Meetings/%' THEN 1\n WHEN path LIKE '💡 Insights/%' THEN 1\n WHEN path LIKE '👨‍💻 People/%' THEN 1\n WHEN path LIKE 'University/%' THEN 1\n WHEN path LIKE 'Copilot/%' THEN 1\n WHEN path LIKE '🗓️ Daily Notes/%' THEN 2\n WHEN path LIKE 'PAI/%' THEN 3\n WHEN path LIKE '09-job-search/%' THEN 4\n WHEN path LIKE 'seriousletter/%' THEN 4\n WHEN path LIKE 'Attachments/%' THEN 5\n ELSE 2\n END, id`;\n const rows = this.db\n .prepare(`SELECT id, text, project_id, path FROM memory_chunks ${where}\n ORDER BY ${orderBy}${limit !== undefined ? \" LIMIT ?\" : \"\"}`)\n .all(...params, ...(limit !== undefined ? [limit] : [])) as Array<{ id: string; text: string; project_id: number; path: string }>;\n return rows;\n }\n\n async updateEmbedding(chunkId: string, embedding: Buffer): Promise<void> {\n this.db\n .prepare(\"UPDATE memory_chunks SET embedding = ? WHERE id = ?\")\n .run(embedding, chunkId);\n }\n\n // -------------------------------------------------------------------------\n // Search\n // -------------------------------------------------------------------------\n\n async searchKeyword(query: string, opts?: SearchOptions): Promise<SearchResult[]> {\n return searchMemory(this.db, query, opts);\n }\n\n async searchSemantic(queryEmbedding: Float32Array, opts?: SearchOptions): Promise<SearchResult[]> {\n return searchMemorySemantic(this.db, queryEmbedding, opts);\n }\n\n // -------------------------------------------------------------------------\n // Vault operations — not supported on SQLite backend (use Postgres)\n // -------------------------------------------------------------------------\n\n private vaultNotSupported(): never {\n throw new Error(\"Vault operations require the Postgres backend\");\n }\n\n async upsertVaultFile(): Promise<void> { this.vaultNotSupported(); }\n async deleteVaultFile(): Promise<void> { this.vaultNotSupported(); }\n async getVaultFile(): Promise<null> { this.vaultNotSupported(); }\n async getVaultFileByInode(): Promise<null> { this.vaultNotSupported(); }\n async getAllVaultFiles(): Promise<never[]> { this.vaultNotSupported(); }\n async getRecentVaultFiles(): Promise<never[]> { this.vaultNotSupported(); }\n async countVaultFiles(): Promise<number> { this.vaultNotSupported(); }\n async upsertVaultAliases(): Promise<void> { this.vaultNotSupported(); }\n async deleteVaultAliases(): Promise<void> { this.vaultNotSupported(); }\n async replaceLinksForSources(): Promise<void> { this.vaultNotSupported(); }\n async getLinksFromSource(): Promise<never[]> { this.vaultNotSupported(); }\n async getLinksToTarget(): Promise<never[]> { this.vaultNotSupported(); }\n async getVaultLinkGraph(): Promise<never[]> { this.vaultNotSupported(); }\n async upsertVaultHealth(): Promise<void> { this.vaultNotSupported(); }\n async getVaultHealth(): Promise<null> { this.vaultNotSupported(); }\n async getOrphans(): Promise<never[]> { this.vaultNotSupported(); }\n async getDeadLinks(): Promise<never[]> { this.vaultNotSupported(); }\n async upsertNameIndex(): Promise<void> { this.vaultNotSupported(); }\n async replaceNameIndex(): Promise<void> { this.vaultNotSupported(); }\n async resolveVaultName(): Promise<never[]> { this.vaultNotSupported(); }\n async searchVaultNameIndex(): Promise<never[]> { this.vaultNotSupported(); }\n async getVaultFilesByPaths(): Promise<never[]> { this.vaultNotSupported(); }\n async getVaultFilesByPathsAfter(): Promise<never[]> { this.vaultNotSupported(); }\n async getVaultLinksFromPaths(): Promise<never[]> { this.vaultNotSupported(); }\n async getChunksWithEmbeddings(): Promise<never[]> { this.vaultNotSupported(); }\n async getChunksForPath(): Promise<never[]> { this.vaultNotSupported(); }\n async searchChunksByText(): Promise<never[]> { this.vaultNotSupported(); }\n async countVaultFilesWithPrefix(): Promise<number> { this.vaultNotSupported(); }\n async countVaultFilesAfter(): Promise<number> { this.vaultNotSupported(); }\n async countVaultLinksWithPrefix(): Promise<number> { this.vaultNotSupported(); }\n async countVaultLinksAfter(): Promise<number> { this.vaultNotSupported(); }\n async getDeadLinksWithLineNumbers(): Promise<never[]> { this.vaultNotSupported(); }\n async getDeadLinksWithPrefix(): Promise<never[]> { this.vaultNotSupported(); }\n async getDeadLinksAfter(): Promise<never[]> { this.vaultNotSupported(); }\n async getOrphansWithPrefix(): Promise<never[]> { this.vaultNotSupported(); }\n async getOrphansAfter(): Promise<never[]> { this.vaultNotSupported(); }\n async getLowConnectivity(): Promise<never[]> { this.vaultNotSupported(); }\n async getLowConnectivityWithPrefix(): Promise<never[]> { this.vaultNotSupported(); }\n async getLowConnectivityAfter(): Promise<never[]> { this.vaultNotSupported(); }\n async getAllVaultFilePaths(): Promise<never[]> { this.vaultNotSupported(); }\n async getVaultFilePathsWithPrefix(): Promise<never[]> { this.vaultNotSupported(); }\n async getVaultFilePathsAfter(): Promise<never[]> { this.vaultNotSupported(); }\n async getVaultLinkEdges(): Promise<never[]> { this.vaultNotSupported(); }\n async getVaultLinkEdgesWithPrefix(): Promise<never[]> { this.vaultNotSupported(); }\n async getVaultLinkEdgesAfter(): Promise<never[]> { this.vaultNotSupported(); }\n async getVaultAlias(): Promise<null> { this.vaultNotSupported(); }\n\n // -------------------------------------------------------------------------\n // Knowledge-graph entities (kg_entities) — wraps storage/sqlite/kg-entity.ts\n // -------------------------------------------------------------------------\n\n async upsertKgEntity(params: KgEntityUpsertParams): Promise<string> {\n return upsertKgEntitySqlite(this.db, params);\n }\n\n async findKgEntity(name: string, tenantId = \"default\"): Promise<KgEntity | null> {\n return findKgEntitySqlite(this.db, name, tenantId);\n }\n\n async listKgEntities(tenantId = \"default\", type?: string, limit = 100): Promise<KgEntity[]> {\n return listKgEntitiesSqlite(this.db, tenantId, type, limit);\n }\n\n async updateEntityFeedbackWeight(entityId: string, normalizedRating: number, alpha = 0.1): Promise<void> {\n return updateEntityFeedbackWeightSqlite(this.db, entityId, normalizedRating, alpha);\n }\n\n // -------------------------------------------------------------------------\n // Chunk feedback / access tracking\n // -------------------------------------------------------------------------\n\n async getChunksForFeedback(chunkIds: string[]): Promise<Array<{ id: string; text: string; relevanceScore: number | null }>> {\n if (chunkIds.length === 0) return [];\n const placeholders = chunkIds.map(() => \"?\").join(\", \");\n const rows = this.db\n .prepare(`SELECT id, text, relevance_score FROM memory_chunks WHERE id IN (${placeholders})`)\n .all(...chunkIds) as Array<{ id: string; text: string; relevance_score: number | null }>;\n return rows.map((r) => ({ id: r.id, text: r.text, relevanceScore: r.relevance_score }));\n }\n\n async updateChunkRelevanceScore(chunkId: string, score: number): Promise<void> {\n this.db.prepare(\"UPDATE memory_chunks SET relevance_score = ? WHERE id = ?\").run(score, chunkId);\n }\n\n async touchChunksLastAccessed(chunkIds: string[]): Promise<void> {\n touchChunksLastAccessed(this.db, chunkIds);\n }\n\n // -------------------------------------------------------------------------\n // Global indexing\n // -------------------------------------------------------------------------\n\n async indexAll(registry: RegistryBackend): Promise<{ projects: number; result: IndexResult }> {\n return indexAllSqlite(this.db, registry);\n }\n\n // -------------------------------------------------------------------------\n // Cross-project concept tunnels\n // -------------------------------------------------------------------------\n\n async findTunnels(registry: RegistryBackend, options?: FindTunnelsOptions): Promise<FindTunnelsResult> {\n return findTunnelsSqlite(this.db, registry, options);\n }\n\n // -------------------------------------------------------------------------\n // Memory sources report\n // -------------------------------------------------------------------------\n\n async getMemorySourcesReport(): Promise<MemorySourcesReport> {\n return getMemorySourcesReportSqlite(this.db);\n }\n\n // -------------------------------------------------------------------------\n // Temporal knowledge graph / observations / skill telemetry — not\n // supported on SQLite (kg_triples, pai_observations, pai_session_summaries,\n // pai_skill_telemetry only exist in the Postgres schema).\n // -------------------------------------------------------------------------\n\n private postgresOnly(feature: string): never {\n throw new Error(`${feature} is not supported on the sqlite backend — set \"storageBackend\": \"postgres\" in the daemon config`);\n }\n\n async addKgTriple(): Promise<KgTriple> { this.postgresOnly(\"kg_triples\"); }\n async queryKgTriples(): Promise<KgTriple[]> { this.postgresOnly(\"kg_triples\"); }\n async invalidateKgTriple(): Promise<void> { this.postgresOnly(\"kg_triples\"); }\n async getKgContradictions(): Promise<KgContradiction[]> { this.postgresOnly(\"kg_triples\"); }\n async getKgStats(): Promise<KgStats> { this.postgresOnly(\"kg_triples\"); }\n\n async storeObservation(): Promise<number | null> { this.postgresOnly(\"observations\"); }\n async storeObservationWithProject(): Promise<number | null> { this.postgresOnly(\"observations\"); }\n async queryObservations(): Promise<ObservationRow[]> { this.postgresOnly(\"observations\"); }\n async queryRecentObservations(): Promise<ObservationRow[]> { this.postgresOnly(\"observations\"); }\n async querySessionObservations(): Promise<ObservationRow[]> { this.postgresOnly(\"observations\"); }\n async getObservationStats(): Promise<ObservationStats> { this.postgresOnly(\"observations\"); }\n async getObservationTypesForPaths(): Promise<Map<string, Record<string, number>>> { return new Map(); }\n async storeSessionSummary(): Promise<void> { this.postgresOnly(\"session summaries\"); }\n async queryRecentSummaries(): Promise<SessionSummaryRow[]> { this.postgresOnly(\"session summaries\"); }\n async recordSkillInvocation(): Promise<void> { this.postgresOnly(\"skill telemetry\"); }\n async querySkillTelemetry(): Promise<SkillTelemetryRow[]> { this.postgresOnly(\"skill telemetry\"); }\n\n // -------------------------------------------------------------------------\n // File-path rename (session cleanup: moving Notes/*.md into YYYY/MM/)\n // -------------------------------------------------------------------------\n\n async countFilesWithPaths(paths: string[]): Promise<number> {\n if (paths.length === 0) return 0;\n const placeholders = paths.map(() => \"?\").join(\", \");\n const row = this.db\n .prepare(`SELECT COUNT(*) AS n FROM memory_files WHERE path IN (${placeholders})`)\n .get(...paths) as { n: number };\n return row.n;\n }\n\n async renameFilePaths(moves: Array<{ oldPath: string; newPath: string }>): Promise<number> {\n if (moves.length === 0) return 0;\n const updateFiles = this.db.prepare(\"UPDATE memory_files SET path = ? WHERE path = ?\");\n const updateChunks = this.db.prepare(\"UPDATE memory_chunks SET path = ? WHERE path = ?\");\n let filesUpdated = 0;\n this.db.transaction(() => {\n for (const { oldPath, newPath } of moves) {\n const r = updateFiles.run(newPath, oldPath);\n filesUpdated += r.changes;\n updateChunks.run(newPath, oldPath);\n }\n })();\n return filesUpdated;\n }\n}\n"],"mappings":"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"}
@@ -1,6 +0,0 @@
1
- import "./factory-BozZa81q.mjs";
2
- import "./wakeup-DO1O4udw.mjs";
3
- import "./embeddings-DfrYNIng.mjs";
4
- import { A as formatProject, C as toolProjectInfo, D as toolMemoryGet, E as combineHybridResults, O as toolMemorySearch, S as toolProjectHealth, T as toolProjectTodo, _ as toolZettelThemes, a as toolMemoryWakeup, b as toolSessionRoute, c as toolKgInvalidate, d as toolZettelConverse, f as toolZettelExplore, g as toolZettelSurprise, h as toolZettelSuggest, i as toolMemoryTaxonomy, j as lookupProjectId, k as detectProjectFromPath, l as toolKgQuery, m as toolZettelHealth, n as toolMemoryFeedback, o as toolKgAdd, p as toolZettelGodNotes, r as toolMemoryTunnels, s as toolKgContradictions, t as toolMemoryKgSearch, u as toolZettelCommunities, v as toolRegistrySearch, w as toolProjectList, x as toolProjectDetect, y as toolSessionList } from "./tools-DFdPbX-X.mjs";
5
-
6
- export { toolKgAdd, toolKgContradictions, toolKgInvalidate, toolKgQuery, toolMemoryTunnels, toolZettelCommunities, toolZettelConverse, toolZettelExplore, toolZettelGodNotes, toolZettelHealth, toolZettelSuggest, toolZettelSurprise, toolZettelThemes };
@@ -1 +0,0 @@
1
- {"version":3,"file":"tools-DFdPbX-X.mjs","names":[],"sources":["../src/mcp/tools/types.ts","../src/mcp/tools/memory.ts","../src/mcp/tools/projects.ts","../src/mcp/tools/sessions.ts","../src/mcp/tools/registry.ts","../src/mcp/tools/zettel.ts","../src/mcp/tools/kg.ts","../src/mcp/tools/wakeup.ts","../src/memory/taxonomy.ts","../src/mcp/tools/taxonomy.ts","../src/memory/tunnels.ts","../src/mcp/tools/tunnels.ts","../src/mcp/tools/feedback.ts","../src/memory/kg-search.ts","../src/mcp/tools/kg-search.ts"],"sourcesContent":["/**\n * Shared types and project-row helpers used across all MCP tool handler modules.\n */\n\nimport { resolve } from \"node:path\";\nimport type { RegistryBackend, Project } from \"../../storage/registry-interface.js\";\n\nexport interface ToolContent {\n type: \"text\";\n text: string;\n}\n\nexport interface ToolResult {\n content: ToolContent[];\n isError?: boolean;\n}\n\n// ---------------------------------------------------------------------------\n// Shared row type — mirrors the projects schema (RegistryBackend's Project)\n// ---------------------------------------------------------------------------\n\nexport type ProjectRow = Project;\n\n// ---------------------------------------------------------------------------\n// Helper: lookup project_id by slug (also checks aliases)\n// ---------------------------------------------------------------------------\n\nexport async function lookupProjectId(\n registry: RegistryBackend,\n slug: string\n): Promise<number | null> {\n const bySlug = await registry.getProjectBySlug(slug);\n if (bySlug) return bySlug.id;\n\n const projectId = await registry.resolveAlias(slug);\n return projectId ?? null;\n}\n\n// ---------------------------------------------------------------------------\n// Helper: detect project from a filesystem path\n// ---------------------------------------------------------------------------\n\nexport async function detectProjectFromPath(\n registry: RegistryBackend,\n fsPath: string\n): Promise<ProjectRow | null> {\n const resolved = resolve(fsPath);\n\n const exact = await registry.getProjectByRootPath(resolved);\n if (exact) return exact;\n\n const all = await registry.listProjectsByPathLengthDesc();\n\n for (const project of all) {\n if (\n resolved.startsWith(project.root_path + \"/\") ||\n resolved === project.root_path\n ) {\n return project;\n }\n }\n\n return null;\n}\n\n// ---------------------------------------------------------------------------\n// Helper: format project row for tool output\n// ---------------------------------------------------------------------------\n\nexport async function formatProject(registry: RegistryBackend, project: ProjectRow): Promise<string> {\n const sessionCount = await registry.countSessionsForProject(project.id);\n const lastSessionDate = await registry.getMostRecentSessionDate(project.id);\n const tags = await registry.listTagsForProject(project.id);\n const aliases = await registry.listAliasesForProject(project.id);\n\n const lines: string[] = [\n `slug: ${project.slug}`,\n `display_name: ${project.display_name}`,\n `root_path: ${project.root_path}`,\n `type: ${project.type}`,\n `status: ${project.status}`,\n `sessions: ${sessionCount}`,\n ];\n\n if (lastSessionDate) lines.push(`last_session: ${lastSessionDate}`);\n if (tags.length) lines.push(`tags: ${tags.join(\", \")}`);\n if (aliases.length) lines.push(`aliases: ${aliases.join(\", \")}`);\n if (project.obsidian_link) lines.push(`obsidian_link: ${project.obsidian_link}`);\n if (project.archived_at) {\n lines.push(\n `archived_at: ${new Date(project.archived_at).toISOString().slice(0, 10)}`\n );\n }\n\n return lines.join(\"\\n\");\n}\n","/**\n * MCP tool handlers: memory_search, memory_get\n */\n\nimport { readFileSync, existsSync, statSync } from \"node:fs\";\nimport { join, resolve, isAbsolute } from \"node:path\";\nimport { populateSlugs } from \"../../memory/search.js\";\nimport type { StorageBackend } from \"../../storage/interface.js\";\nimport type { RegistryBackend } from \"../../storage/registry-interface.js\";\nimport type { SearchConfig } from \"../../daemon/config.js\";\nimport type { SearchResult } from \"../../memory/search.js\";\nimport {\n lookupProjectId,\n type ToolResult,\n} from \"./types.js\";\n\n// ---------------------------------------------------------------------------\n// Tool: memory_search\n// ---------------------------------------------------------------------------\n\nexport interface MemorySearchParams {\n query: string;\n project?: string;\n all_projects?: boolean;\n sources?: Array<\"memory\" | \"notes\">;\n limit?: number;\n mode?: \"keyword\" | \"semantic\" | \"hybrid\";\n /** Rerank results using cross-encoder model for better relevance ordering. */\n rerank?: boolean;\n /** Apply recency boost — score decays by half every N days. 0 = off (default). */\n recencyBoost?: number;\n /** Maximum characters per result snippet. Default 200.\n * Limit context consumption — MCP results go into Claude's context window. */\n snippetLength?: number;\n /** Output format: \"full\" (default, includes snippets) or \"compact\" (IDs + metadata only, ~10x fewer tokens). */\n format?: \"full\" | \"compact\";\n}\n\nexport async function toolMemorySearch(\n registry: RegistryBackend,\n federation: StorageBackend,\n params: MemorySearchParams,\n searchDefaults?: SearchConfig,\n): Promise<ToolResult> {\n try {\n const projectIds: number[] | undefined = params.project\n ? await (async () => {\n const id = await lookupProjectId(registry, params.project!);\n return id != null ? [id] : [];\n })()\n : undefined;\n\n if (params.project && (!projectIds || projectIds.length === 0)) {\n return {\n content: [\n { type: \"text\", text: `Project not found: ${params.project}` },\n ],\n isError: true,\n };\n }\n\n // NOTE: No indexAll() here — indexing is handled by the daemon scheduler.\n // The daemon ensures the index stays fresh; the search hot path is read-only.\n\n const mode = params.mode ?? (searchDefaults?.mode ?? \"keyword\");\n // Limit context consumption — MCP results go into Claude's context window.\n // Default to 5 results and 200-char snippets to keep a single search call\n // within ~1-2K tokens rather than 5K+.\n const snippetLength = params.snippetLength ?? (searchDefaults?.snippetLength ?? 200);\n const searchOpts = {\n projectIds,\n sources: params.sources,\n maxResults: params.limit ?? (searchDefaults?.defaultLimit ?? 5),\n };\n\n let results: SearchResult[];\n\n if (mode === \"keyword\") {\n results = await federation.searchKeyword(params.query, searchOpts);\n } else if (mode === \"semantic\" || mode === \"hybrid\") {\n const { generateEmbedding } = await import(\"../../memory/embeddings.js\");\n const queryEmbedding = await generateEmbedding(params.query, true);\n\n if (mode === \"semantic\") {\n results = await federation.searchSemantic(queryEmbedding, searchOpts);\n } else {\n // Hybrid: combine keyword + semantic\n const [kwResults, semResults] = await Promise.all([\n federation.searchKeyword(params.query, { ...searchOpts, maxResults: 50 }),\n federation.searchSemantic(queryEmbedding, { ...searchOpts, maxResults: 50 }),\n ]); // 50 candidates is sufficient for min-max normalization\n // Reuse the existing hybrid scoring logic\n results = combineHybridResults(kwResults, semResults, searchOpts.maxResults ?? 10);\n }\n } else {\n results = await federation.searchKeyword(params.query, searchOpts);\n }\n\n // Re-rank by the corpus's own links before returning.\n //\n // Similarity answers \"what reads like the query\"; it cannot answer \"which\n // of these is the one the others refer back to\", which is usually the note\n // to read first. The wikilinks are facts a person wrote, they cost nothing\n // to maintain, and they are correct for chunks that have no embedding yet —\n // which matters while a large embedding backlog drains.\n //\n // Only EXTRACTED links count: INFERRED ones are themselves derived by\n // similarity, so folding them in would feed the ranking its own output.\n try {\n const { applyLinkBoost } = await import(\"../../memory/link-boost.js\");\n const paths = [...new Set(results.map((r) => r.path))];\n // Bounded: one lookup per distinct result path, not per chunk, and the\n // result set is already capped by maxResults.\n if (paths.length > 1) {\n const edges: Array<{ sourcePath: string; targetPath: string }> = [];\n for (const p of paths) {\n for (const l of await federation.getLinksToTarget(p)) {\n if (l.confidence && l.confidence !== \"EXTRACTED\") continue;\n edges.push({ sourcePath: l.sourcePath, targetPath: p });\n }\n }\n if (edges.length > 0) results = applyLinkBoost(results, edges);\n }\n } catch {\n // A ranking aid, never a reason to fail a search.\n }\n\n // QW2: Update last_accessed_at for returned chunks (best-effort, non-blocking)\n try {\n const chunkIds = results\n .map((r) => r.chunkId)\n .filter((id): id is string => id != null);\n if (chunkIds.length > 0) {\n await federation.touchChunksLastAccessed(chunkIds);\n }\n } catch {\n // non-critical — never block search results\n }\n\n // Cross-encoder reranking (on by default)\n const shouldRerank = params.rerank ?? (searchDefaults?.rerank ?? true);\n if (shouldRerank && results.length > 0) {\n const { rerankResults } = await import(\"../../memory/reranker.js\");\n results = await rerankResults(params.query, results, {\n topK: searchOpts.maxResults ?? 5,\n });\n }\n\n // Recency boost (off by default, applied after reranking)\n const recencyDays = params.recencyBoost ?? (searchDefaults?.recencyBoostDays ?? 0);\n if (recencyDays > 0 && results.length > 0) {\n const { applyRecencyBoost } = await import(\"../../memory/search.js\");\n results = applyRecencyBoost(results, recencyDays);\n }\n\n const withSlugs = await populateSlugs(results, registry);\n\n if (withSlugs.length === 0) {\n return {\n content: [\n {\n type: \"text\",\n text: `No results found for query: \"${params.query}\" (mode: ${mode})`,\n },\n ],\n };\n }\n\n const rerankLabel = shouldRerank ? \" +rerank\" : \"\";\n const useCompact = params.format === \"compact\";\n const formatted = withSlugs\n .map((r, i) => {\n const slug = r.projectSlug ?? `project:${r.projectId}`;\n const idPart = r.chunkId ? ` id=${r.chunkId}` : \"\";\n if (useCompact) {\n // Compact format: chunk ID, path, score — pass id to memory_feedback to rate this result\n return `[${i + 1}]${idPart} ${slug} — ${r.path} L${r.startLine}-${r.endLine} score=${r.score.toFixed(3)}`;\n }\n const header = `[${i + 1}]${idPart} ${slug} — ${r.path} (lines ${r.startLine}-${r.endLine}) score=${r.score.toFixed(4)} tier=${r.tier} source=${r.source}`;\n // Truncate snippet to snippetLength — limit context consumption.\n // MCP results go into Claude's context window; keep each result tight.\n const raw = r.snippet.trim();\n const snippet = raw.length > snippetLength\n ? raw.slice(0, snippetLength) + \"...\"\n : raw;\n return `${header}\\n${snippet}`;\n })\n .join(useCompact ? \"\\n\" : \"\\n\\n---\\n\\n\");\n\n // Query feedback loop: save query + result metadata for future indexing\n try {\n const { saveQueryResult } = await import(\"../../zettelkasten/query-feedback.js\");\n saveQueryResult({\n query: params.query,\n timestamp: Date.now(),\n source: \"memory_search\",\n sourceSlugs: withSlugs.slice(0, 5).map((r) => r.path),\n answerPreview: withSlugs.slice(0, 3).map((r) => r.snippet.trim().slice(0, 150)).join(\" | \"),\n resultCount: withSlugs.length,\n });\n } catch {\n // Non-critical — don't fail the search if feedback logging errors\n }\n\n return {\n content: [\n {\n type: \"text\",\n text: `Found ${withSlugs.length} result(s) for \"${params.query}\" (mode: ${mode}${rerankLabel}):\\n\\n${formatted}`,\n },\n ],\n };\n } catch (e) {\n return {\n content: [{ type: \"text\", text: `Search error: ${String(e)}` }],\n isError: true,\n };\n }\n}\n\n// ---------------------------------------------------------------------------\n// Tool: memory_get\n// ---------------------------------------------------------------------------\n\nexport interface MemoryGetParams {\n project: string;\n path: string;\n from?: number;\n lines?: number;\n}\n\nexport async function toolMemoryGet(\n registry: RegistryBackend,\n params: MemoryGetParams\n): Promise<ToolResult> {\n try {\n const projectId = await lookupProjectId(registry, params.project);\n if (projectId == null) {\n return {\n content: [\n { type: \"text\", text: `Project not found: ${params.project}` },\n ],\n isError: true,\n };\n }\n\n const project = await registry.getProjectById(projectId);\n\n if (!project) {\n return {\n content: [\n { type: \"text\", text: `Project not found: ${params.project}` },\n ],\n isError: true,\n };\n }\n\n const requestedPath = params.path;\n if (requestedPath.includes(\"..\") || isAbsolute(requestedPath)) {\n return {\n content: [\n {\n type: \"text\",\n text: `Invalid path: ${params.path} (must be a relative path within the project root, no ../ allowed)`,\n },\n ],\n isError: true,\n };\n }\n\n const fullPath = join(project.root_path, requestedPath);\n const resolvedFull = resolve(fullPath);\n const resolvedRoot = resolve(project.root_path);\n\n if (\n !resolvedFull.startsWith(resolvedRoot + \"/\") &&\n resolvedFull !== resolvedRoot\n ) {\n return {\n content: [\n { type: \"text\", text: `Path traversal blocked: ${params.path}` },\n ],\n isError: true,\n };\n }\n\n if (!existsSync(fullPath)) {\n return {\n content: [\n {\n type: \"text\",\n text: `File not found: ${requestedPath} (project: ${params.project})`,\n },\n ],\n isError: true,\n };\n }\n\n const stat = statSync(fullPath);\n if (stat.size > 5 * 1024 * 1024) {\n return {\n content: [\n {\n type: \"text\",\n text: `Error: file too large (${(stat.size / 1024 / 1024).toFixed(1)} MB). Maximum 5 MB.`,\n },\n ],\n };\n }\n\n const content = readFileSync(fullPath, \"utf8\");\n const allLines = content.split(\"\\n\");\n\n const fromLine = (params.from ?? 1) - 1;\n const toLine =\n params.lines != null\n ? Math.min(fromLine + params.lines, allLines.length)\n : allLines.length;\n\n const selectedLines = allLines.slice(fromLine, toLine);\n const text = selectedLines.join(\"\\n\");\n\n const header =\n params.from != null\n ? `${params.project}/${requestedPath} (lines ${fromLine + 1}-${toLine}):`\n : `${params.project}/${requestedPath}:`;\n\n return {\n content: [{ type: \"text\", text: `${header}\\n\\n${text}` }],\n };\n } catch (e) {\n return {\n content: [{ type: \"text\", text: `Read error: ${String(e)}` }],\n isError: true,\n };\n }\n}\n\n// ---------------------------------------------------------------------------\n// Hybrid search helper (backend-agnostic)\n// ---------------------------------------------------------------------------\n\n/**\n * Combine keyword + semantic results using min-max normalized scoring.\n * Mirrors the logic in searchMemoryHybrid() from memory/search.ts,\n * but works on pre-computed result arrays so it works for any backend.\n */\nexport function combineHybridResults(\n keywordResults: SearchResult[],\n semanticResults: SearchResult[],\n maxResults: number,\n keywordWeight = 0.5,\n semanticWeight = 0.5\n): SearchResult[] {\n if (keywordResults.length === 0 && semanticResults.length === 0) return [];\n\n const keyFor = (r: SearchResult) =>\n `${r.projectId}:${r.path}:${r.startLine}:${r.endLine}`;\n\n function minMaxNormalize(items: SearchResult[]): Map<string, number> {\n if (items.length === 0) return new Map();\n const min = Math.min(...items.map((r) => r.score));\n const max = Math.max(...items.map((r) => r.score));\n const range = max - min;\n const m = new Map<string, number>();\n for (const r of items) {\n m.set(keyFor(r), range === 0 ? 1 : (r.score - min) / range);\n }\n return m;\n }\n\n const kwNorm = minMaxNormalize(keywordResults);\n const semNorm = minMaxNormalize(semanticResults);\n\n const allKeys = new Set<string>([\n ...keywordResults.map(keyFor),\n ...semanticResults.map(keyFor),\n ]);\n\n const metaMap = new Map<string, SearchResult>();\n for (const r of [...keywordResults, ...semanticResults]) {\n metaMap.set(keyFor(r), r);\n }\n\n const combined: Array<SearchResult & { combinedScore: number }> = [];\n for (const key of allKeys) {\n const meta = metaMap.get(key)!;\n const kwScore = kwNorm.get(key) ?? 0;\n const semScore = semNorm.get(key) ?? 0;\n const combinedScore = keywordWeight * kwScore + semanticWeight * semScore;\n combined.push({ ...meta, score: combinedScore, combinedScore });\n }\n\n return combined\n .sort((a, b) => b.score - a.score)\n .slice(0, maxResults)\n .map(({ combinedScore: _unused, ...r }) => r);\n}\n","/**\n * MCP tool handlers: project_info, project_list, project_detect,\n * project_health, project_todo\n */\n\nimport { readFileSync, existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { detectProject, formatDetectionJson } from \"../../cli/commands/detect.js\";\nimport type { RegistryBackend } from \"../../storage/registry-interface.js\";\nimport {\n lookupProjectId,\n detectProjectFromPath,\n formatProject,\n type ProjectRow,\n type ToolResult,\n} from \"./types.js\";\n\n// ---------------------------------------------------------------------------\n// Tool: project_info\n// ---------------------------------------------------------------------------\n\nexport interface ProjectInfoParams {\n slug?: string;\n}\n\nexport async function toolProjectInfo(\n registry: RegistryBackend,\n params: ProjectInfoParams\n): Promise<ToolResult> {\n try {\n let project: ProjectRow | null = null;\n\n if (params.slug) {\n const projectId = await lookupProjectId(registry, params.slug);\n if (projectId != null) {\n project = await registry.getProjectById(projectId);\n }\n } else {\n const cwd = process.cwd();\n project = await detectProjectFromPath(registry, cwd);\n }\n\n if (!project) {\n const message = params.slug\n ? `Project not found: ${params.slug}`\n : `No PAI project found matching the current directory: ${process.cwd()}`;\n return {\n content: [{ type: \"text\", text: message }],\n isError: !params.slug,\n };\n }\n\n return {\n content: [{ type: \"text\", text: await formatProject(registry, project) }],\n };\n } catch (e) {\n return {\n content: [{ type: \"text\", text: `project_info error: ${String(e)}` }],\n isError: true,\n };\n }\n}\n\n// ---------------------------------------------------------------------------\n// Tool: project_list\n// ---------------------------------------------------------------------------\n\nexport interface ProjectListParams {\n status?: \"active\" | \"archived\" | \"migrating\";\n tag?: string;\n limit?: number;\n}\n\nexport async function toolProjectList(\n registry: RegistryBackend,\n params: ProjectListParams\n): Promise<ToolResult> {\n try {\n const limit = params.limit ?? 50;\n\n const projects = await registry.listProjects({\n status: params.status,\n tagName: params.tag,\n orderBy: \"updated_desc\",\n limit,\n });\n\n if (projects.length === 0) {\n return {\n content: [\n {\n type: \"text\",\n text: \"No projects found matching the given filters.\",\n },\n ],\n };\n }\n\n const lines = projects.map(\n (p) =>\n `${p.slug} [${p.status}] ${p.root_path} (updated: ${new Date(p.updated_at).toISOString().slice(0, 10)})`\n );\n\n return {\n content: [\n {\n type: \"text\",\n text: `${projects.length} project(s):\\n\\n${lines.join(\"\\n\")}`,\n },\n ],\n };\n } catch (e) {\n return {\n content: [{ type: \"text\", text: `project_list error: ${String(e)}` }],\n isError: true,\n };\n }\n}\n\n// ---------------------------------------------------------------------------\n// Tool: project_detect\n// ---------------------------------------------------------------------------\n\nexport interface ProjectDetectParams {\n cwd?: string;\n}\n\nexport async function toolProjectDetect(\n params: ProjectDetectParams\n): Promise<ToolResult> {\n try {\n const detection = await detectProject(params.cwd);\n\n if (!detection) {\n const target = params.cwd ?? process.cwd();\n return {\n content: [\n {\n type: \"text\",\n text: `No registered project found for path: ${target}\\n\\nRun 'pai project add .' to register this directory.`,\n },\n ],\n };\n }\n\n return {\n content: [{ type: \"text\", text: formatDetectionJson(detection) }],\n };\n } catch (e) {\n return {\n content: [\n { type: \"text\", text: `project_detect error: ${String(e)}` },\n ],\n isError: true,\n };\n }\n}\n\n// ---------------------------------------------------------------------------\n// Tool: project_health\n// ---------------------------------------------------------------------------\n\nexport interface ProjectHealthParams {\n category?: \"active\" | \"stale\" | \"dead\" | \"all\";\n}\n\nexport async function toolProjectHealth(\n registry: RegistryBackend,\n params: ProjectHealthParams\n): Promise<ToolResult> {\n try {\n const { existsSync: fsExists, readdirSync, statSync } = await import(\n \"node:fs\"\n );\n const {\n join: pathJoin,\n basename: pathBasename,\n } = await import(\"node:path\");\n const { homedir } = await import(\"node:os\");\n const { encodeDir: enc } = await import(\"../../cli/utils.js\");\n\n const rows = (await registry.listProjectsWithSessionStats())\n .slice()\n .sort((a, b) => a.slug.localeCompare(b.slug));\n\n const home = homedir();\n const claudeProjects = pathJoin(home, \".claude\", \"projects\");\n\n function suggestMoved(rootPath: string): string | undefined {\n const name = pathBasename(rootPath);\n const candidates = [\n pathJoin(home, \"dev\", name),\n pathJoin(home, \"dev\", \"ai\", name),\n pathJoin(home, \"Desktop\", name),\n pathJoin(home, \"Projects\", name),\n ];\n return candidates.find((c) => fsExists(c));\n }\n\n function hasClaudeNotes(encodedDir: string): boolean {\n if (!fsExists(claudeProjects)) return false;\n try {\n for (const entry of readdirSync(claudeProjects)) {\n if (entry !== encodedDir && !entry.startsWith(encodedDir)) continue;\n const full = pathJoin(claudeProjects, entry);\n try {\n if (!statSync(full).isDirectory()) continue;\n } catch {\n continue;\n }\n if (fsExists(pathJoin(full, \"Notes\"))) return true;\n }\n } catch {\n /* ignore */\n }\n return false;\n }\n\n interface HealthResult {\n slug: string;\n display_name: string;\n root_path: string;\n status: string;\n type: string;\n session_count: number;\n health: string;\n suggested_path: string | null;\n has_claude_notes: boolean;\n todo: {\n found: boolean;\n path: string | null;\n has_continue: boolean;\n };\n }\n\n function findTodoForProject(rootPath: string): {\n found: boolean;\n path: string | null;\n has_continue: boolean;\n } {\n const locs = [\n \"Notes/TODO.md\",\n \".claude/Notes/TODO.md\",\n \"tasks/todo.md\",\n \"TODO.md\",\n ];\n for (const rel of locs) {\n const full = pathJoin(rootPath, rel);\n if (fsExists(full)) {\n try {\n const raw = readFileSync(full, \"utf8\");\n const hasContinue = /^## Continue$/m.test(raw);\n return { found: true, path: rel, has_continue: hasContinue };\n } catch {\n return { found: true, path: rel, has_continue: false };\n }\n }\n }\n return { found: false, path: null, has_continue: false };\n }\n\n const results: HealthResult[] = rows.map((p) => {\n const pathExists = fsExists(p.root_path);\n let health: string;\n let suggestedPath: string | null = null;\n\n if (pathExists) {\n health = \"active\";\n } else {\n suggestedPath = suggestMoved(p.root_path) ?? null;\n health = suggestedPath ? \"stale\" : \"dead\";\n }\n\n const todo = pathExists\n ? findTodoForProject(p.root_path)\n : { found: false, path: null, has_continue: false };\n\n return {\n slug: p.slug,\n display_name: p.display_name,\n root_path: p.root_path,\n status: p.status,\n type: p.type,\n session_count: p.session_count,\n health,\n suggested_path: suggestedPath,\n has_claude_notes: hasClaudeNotes(p.encoded_dir),\n todo,\n };\n });\n\n const filtered =\n !params.category || params.category === \"all\"\n ? results\n : results.filter((r) => r.health === params.category);\n\n const summary = {\n total: rows.length,\n active: results.filter((r) => r.health === \"active\").length,\n stale: results.filter((r) => r.health === \"stale\").length,\n dead: results.filter((r) => r.health === \"dead\").length,\n };\n\n return {\n content: [\n {\n type: \"text\",\n text: JSON.stringify({ summary, projects: filtered }, null, 2),\n },\n ],\n };\n } catch (e) {\n return {\n content: [\n { type: \"text\", text: `project_health error: ${String(e)}` },\n ],\n isError: true,\n };\n }\n}\n\n// ---------------------------------------------------------------------------\n// Tool: project_todo\n// ---------------------------------------------------------------------------\n\nexport interface ProjectTodoParams {\n project?: string;\n}\n\n/**\n * TODO candidate locations searched in priority order.\n * Returns the first one that exists, along with its label.\n */\nconst TODO_LOCATIONS = [\n { rel: \"Notes/TODO.md\", label: \"Notes/TODO.md\" },\n { rel: \".claude/Notes/TODO.md\", label: \".claude/Notes/TODO.md\" },\n { rel: \"tasks/todo.md\", label: \"tasks/todo.md\" },\n { rel: \"TODO.md\", label: \"TODO.md\" },\n];\n\n/**\n * Given TODO file content, extract and surface the ## Continue section first,\n * then return the remaining content. Returns an object with:\n * continueSection: string | null\n * fullContent: string\n * hasContinue: boolean\n */\nfunction parseTodoContent(raw: string): {\n continueSection: string | null;\n fullContent: string;\n hasContinue: boolean;\n} {\n const lines = raw.split(\"\\n\");\n\n // Find the ## Continue heading\n const continueIdx = lines.findIndex(\n (l) => l.trim() === \"## Continue\"\n );\n\n if (continueIdx === -1) {\n return { continueSection: null, fullContent: raw, hasContinue: false };\n }\n\n // The section ends at the first `---` separator or next `##` heading after\n // the Continue heading (whichever comes first).\n let endIdx = lines.length;\n for (let i = continueIdx + 1; i < lines.length; i++) {\n const trimmed = lines[i].trim();\n if (trimmed === \"---\" || (trimmed.startsWith(\"##\") && trimmed !== \"## Continue\")) {\n endIdx = i;\n break;\n }\n }\n\n const continueLines = lines.slice(continueIdx, endIdx);\n const continueSection = continueLines.join(\"\\n\").trim();\n\n return { continueSection, fullContent: raw, hasContinue: true };\n}\n\nexport async function toolProjectTodo(\n registry: RegistryBackend,\n params: ProjectTodoParams\n): Promise<ToolResult> {\n try {\n let rootPath: string;\n let projectSlug: string;\n\n if (params.project) {\n const projectId = await lookupProjectId(registry, params.project);\n if (projectId == null) {\n return {\n content: [\n { type: \"text\", text: `Project not found: ${params.project}` },\n ],\n isError: true,\n };\n }\n\n const row = await registry.getProjectById(projectId);\n\n if (!row) {\n return {\n content: [\n { type: \"text\", text: `Project not found: ${params.project}` },\n ],\n isError: true,\n };\n }\n\n rootPath = row.root_path;\n projectSlug = row.slug;\n } else {\n // Auto-detect from cwd\n const project = await detectProjectFromPath(registry, process.cwd());\n if (!project) {\n return {\n content: [\n {\n type: \"text\",\n 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.`,\n },\n ],\n };\n }\n rootPath = project.root_path;\n projectSlug = project.slug;\n }\n\n // Search for TODO in priority order\n for (const loc of TODO_LOCATIONS) {\n const fullPath = join(rootPath, loc.rel);\n if (existsSync(fullPath)) {\n const raw = readFileSync(fullPath, \"utf8\");\n const { continueSection, fullContent, hasContinue } = parseTodoContent(raw);\n\n let output: string;\n if (hasContinue && continueSection) {\n // Surface the ## Continue section first, then the full content\n output = [\n `TODO found: ${projectSlug}/${loc.label}`,\n \"\",\n \"=== CONTINUE SECTION (surfaced first) ===\",\n continueSection,\n \"\",\n \"=== FULL TODO CONTENT ===\",\n fullContent,\n ].join(\"\\n\");\n } else {\n output = [\n `TODO found: ${projectSlug}/${loc.label}`,\n \"\",\n fullContent,\n ].join(\"\\n\");\n }\n\n return {\n content: [{ type: \"text\", text: output }],\n };\n }\n }\n\n // No TODO found in any location\n const searched = TODO_LOCATIONS.map((l) => ` ${rootPath}/${l.rel}`).join(\"\\n\");\n return {\n content: [\n {\n type: \"text\",\n text: [\n `No TODO.md found for project: ${projectSlug}`,\n \"\",\n \"Searched locations (in order):\",\n searched,\n \"\",\n \"Create a TODO with: echo '## Tasks\\\\n- [ ] First task' > Notes/TODO.md\",\n ].join(\"\\n\"),\n },\n ],\n };\n } catch (e) {\n return {\n content: [{ type: \"text\", text: `project_todo error: ${String(e)}` }],\n isError: true,\n };\n }\n}\n","/**\n * MCP tool handlers: session_list, session_route\n */\n\nimport type { StorageBackend } from \"../../storage/interface.js\";\nimport type { RegistryBackend } from \"../../storage/registry-interface.js\";\nimport {\n lookupProjectId,\n type ToolResult,\n} from \"./types.js\";\n\n// ---------------------------------------------------------------------------\n// Tool: session_list\n// ---------------------------------------------------------------------------\n\nexport interface SessionListParams {\n project: string;\n limit?: number;\n status?: \"open\" | \"completed\" | \"compacted\";\n}\n\nexport async function toolSessionList(\n registry: RegistryBackend,\n params: SessionListParams\n): Promise<ToolResult> {\n try {\n const projectId = await lookupProjectId(registry, params.project);\n if (projectId == null) {\n return {\n content: [\n { type: \"text\", text: `Project not found: ${params.project}` },\n ],\n isError: true,\n };\n }\n\n const limit = params.limit ?? 10;\n const sessions = await registry.listSessions({\n projectId,\n status: params.status,\n limit,\n });\n\n if (sessions.length === 0) {\n return {\n content: [\n {\n type: \"text\",\n text: `No sessions found for project: ${params.project}`,\n },\n ],\n };\n }\n\n const lines = sessions.map(\n (s) =>\n `#${String(s.number).padStart(4, \"0\")} ${s.date} [${s.status}] ${s.title}\\n file: Notes/${s.filename}`\n );\n\n return {\n content: [\n {\n type: \"text\",\n text: `${sessions.length} session(s) for ${params.project}:\\n\\n${lines.join(\"\\n\\n\")}`,\n },\n ],\n };\n } catch (e) {\n return {\n content: [{ type: \"text\", text: `session_list error: ${String(e)}` }],\n isError: true,\n };\n }\n}\n\n// ---------------------------------------------------------------------------\n// Tool: session_route\n// ---------------------------------------------------------------------------\n\nexport interface SessionRouteParams {\n /** Working directory to route from (defaults to process.cwd()) */\n cwd?: string;\n /** Optional conversation context for topic-based fallback routing */\n context?: string;\n}\n\n/**\n * Automatically suggest which project a session belongs to.\n *\n * Strategy (in priority order):\n * 1. path — exact or parent-directory match in the project registry\n * 2. marker — walk up from cwd looking for Notes/PAI.md\n * 3. topic — BM25 keyword search against memory (requires context)\n *\n * Call this at session start (e.g., from CLAUDE.md or a session-start hook)\n * to automatically route the session to the correct project.\n */\nexport async function toolSessionRoute(\n registry: RegistryBackend,\n federation: StorageBackend,\n params: SessionRouteParams\n): Promise<ToolResult> {\n try {\n const { autoRoute, formatAutoRouteJson } = await import(\"../../session/auto-route.js\");\n\n const result = await autoRoute(\n registry,\n federation,\n params.cwd,\n params.context\n );\n\n if (!result) {\n const target = params.cwd ?? process.cwd();\n return {\n content: [\n {\n type: \"text\",\n text: [\n `No project match found for: ${target}`,\n \"\",\n \"Tried: path match, PAI.md marker walk\" +\n (params.context ? \", topic detection\" : \"\"),\n \"\",\n \"Run 'pai project add .' to register this directory,\",\n \"or provide conversation context for topic-based routing.\",\n ].join(\"\\n\"),\n },\n ],\n };\n }\n\n return {\n content: [{ type: \"text\", text: formatAutoRouteJson(result) }],\n };\n } catch (e) {\n return {\n content: [{ type: \"text\", text: `session_route error: ${String(e)}` }],\n isError: true,\n };\n }\n}\n","/**\n * MCP tool handler: registry_search\n */\n\nimport type { RegistryBackend } from \"../../storage/registry-interface.js\";\nimport type { ToolResult } from \"./types.js\";\n\n// ---------------------------------------------------------------------------\n// Tool: registry_search\n// ---------------------------------------------------------------------------\n\nexport interface RegistrySearchParams {\n query: string;\n}\n\nexport async function toolRegistrySearch(\n registry: RegistryBackend,\n params: RegistrySearchParams\n): Promise<ToolResult> {\n try {\n const projects = await registry.searchProjects(params.query, 20);\n\n if (projects.length === 0) {\n return {\n content: [\n {\n type: \"text\",\n text: `No projects found matching: \"${params.query}\"`,\n },\n ],\n };\n }\n\n const lines = projects.map((p) => `${p.slug} [${p.status}] ${p.root_path}`);\n\n return {\n content: [\n {\n type: \"text\",\n text: `${projects.length} match(es) for \"${params.query}\":\\n\\n${lines.join(\"\\n\")}`,\n },\n ],\n };\n } catch (e) {\n return {\n content: [\n { type: \"text\", text: `registry_search error: ${String(e)}` },\n ],\n isError: true,\n };\n }\n}\n","/**\n * MCP tool handlers: zettel_explore, zettel_health, zettel_surprise,\n * zettel_suggest, zettel_converse, zettel_themes,\n * zettel_god_notes, zettel_communities\n */\n\nimport type { StorageBackend } from \"../../storage/interface.js\";\nimport type { ToolResult } from \"./types.js\";\n\n// ---------------------------------------------------------------------------\n// Tool: zettel_explore\n// ---------------------------------------------------------------------------\n\nexport interface ZettelExploreParams {\n start_note: string;\n depth?: number;\n direction?: string;\n mode?: string;\n}\n\nexport async function toolZettelExplore(\n backend: StorageBackend,\n params: ZettelExploreParams\n): Promise<ToolResult> {\n try {\n const { zettelExplore } = await import(\"../../zettelkasten/index.js\");\n const result = await zettelExplore(backend, {\n startNote: params.start_note,\n depth: params.depth,\n direction: params.direction as \"forward\" | \"backward\" | \"both\" | undefined,\n mode: params.mode as \"sequential\" | \"associative\" | \"all\" | undefined,\n });\n return {\n content: [{ type: \"text\", text: JSON.stringify(result, null, 2) }],\n };\n } catch (e) {\n return {\n content: [{ type: \"text\", text: `zettel_explore error: ${String(e)}` }],\n isError: true,\n };\n }\n}\n\n// ---------------------------------------------------------------------------\n// Tool: zettel_health\n// ---------------------------------------------------------------------------\n\nexport interface ZettelHealthParams {\n scope?: string;\n project_path?: string;\n recent_days?: number;\n include?: string[];\n}\n\nexport async function toolZettelHealth(\n backend: StorageBackend,\n params: ZettelHealthParams\n): Promise<ToolResult> {\n try {\n const { zettelHealth } = await import(\"../../zettelkasten/index.js\");\n const result = await zettelHealth(backend, {\n scope: params.scope as \"full\" | \"recent\" | \"project\" | undefined,\n projectPath: params.project_path,\n recentDays: params.recent_days,\n include: params.include as Array<\"dead_links\" | \"orphans\" | \"disconnected\" | \"low_connectivity\"> | undefined,\n });\n return {\n content: [{ type: \"text\", text: JSON.stringify(result, null, 2) }],\n };\n } catch (e) {\n return {\n content: [{ type: \"text\", text: `zettel_health error: ${String(e)}` }],\n isError: true,\n };\n }\n}\n\n// ---------------------------------------------------------------------------\n// Tool: zettel_surprise\n// ---------------------------------------------------------------------------\n\nexport interface ZettelSurpriseParams {\n reference_path: string;\n vault_project_id: number;\n limit?: number;\n min_similarity?: number;\n min_graph_distance?: number;\n}\n\nexport async function toolZettelSurprise(\n backend: StorageBackend,\n params: ZettelSurpriseParams\n): Promise<ToolResult> {\n try {\n const { zettelSurprise } = await import(\"../../zettelkasten/index.js\");\n const results = await zettelSurprise(backend, {\n referencePath: params.reference_path,\n vaultProjectId: params.vault_project_id,\n limit: params.limit,\n minSimilarity: params.min_similarity,\n minGraphDistance: params.min_graph_distance,\n });\n return {\n content: [{ type: \"text\", text: JSON.stringify(results, null, 2) }],\n };\n } catch (e) {\n return {\n content: [{ type: \"text\", text: `zettel_surprise error: ${String(e)}` }],\n isError: true,\n };\n }\n}\n\n// ---------------------------------------------------------------------------\n// Tool: zettel_suggest\n// ---------------------------------------------------------------------------\n\nexport interface ZettelSuggestParams {\n note_path: string;\n vault_project_id: number;\n limit?: number;\n exclude_linked?: boolean;\n}\n\nexport async function toolZettelSuggest(\n backend: StorageBackend,\n params: ZettelSuggestParams\n): Promise<ToolResult> {\n try {\n const { zettelSuggest } = await import(\"../../zettelkasten/index.js\");\n const results = await zettelSuggest(backend, {\n notePath: params.note_path,\n vaultProjectId: params.vault_project_id,\n limit: params.limit,\n excludeLinked: params.exclude_linked,\n });\n return {\n content: [{ type: \"text\", text: JSON.stringify(results, null, 2) }],\n };\n } catch (e) {\n return {\n content: [{ type: \"text\", text: `zettel_suggest error: ${String(e)}` }],\n isError: true,\n };\n }\n}\n\n// ---------------------------------------------------------------------------\n// Tool: zettel_converse\n// ---------------------------------------------------------------------------\n\nexport interface ZettelConverseParams {\n question: string;\n vault_project_id: number;\n depth?: number;\n limit?: number;\n}\n\nexport async function toolZettelConverse(\n backend: StorageBackend,\n params: ZettelConverseParams\n): Promise<ToolResult> {\n try {\n const { zettelConverse } = await import(\"../../zettelkasten/index.js\");\n const result = await zettelConverse(backend, {\n question: params.question,\n vaultProjectId: params.vault_project_id,\n depth: params.depth,\n limit: params.limit,\n });\n\n // Query feedback loop: save query + result metadata for future indexing\n try {\n const { saveQueryResult } = await import(\"../../zettelkasten/query-feedback.js\");\n saveQueryResult({\n query: params.question,\n timestamp: Date.now(),\n source: \"zettel_converse\",\n sourceSlugs: result.relevantNotes.slice(0, 5).map((n) => n.path),\n answerPreview: result.relevantNotes.slice(0, 3).map((n) => {\n const title = n.title ?? \"(untitled)\";\n return `${title}: ${n.snippet.trim().slice(0, 100)}`;\n }).join(\" | \"),\n resultCount: result.relevantNotes.length,\n });\n } catch {\n // Non-critical\n }\n\n return {\n content: [{ type: \"text\", text: JSON.stringify(result, null, 2) }],\n };\n } catch (e) {\n return {\n content: [{ type: \"text\", text: `zettel_converse error: ${String(e)}` }],\n isError: true,\n };\n }\n}\n\n// ---------------------------------------------------------------------------\n// Tool: zettel_themes\n// ---------------------------------------------------------------------------\n\nexport interface ZettelThemesParams {\n vault_project_id: number;\n lookback_days?: number;\n min_cluster_size?: number;\n max_themes?: number;\n similarity_threshold?: number;\n}\n\nexport async function toolZettelThemes(\n backend: StorageBackend,\n params: ZettelThemesParams\n): Promise<ToolResult> {\n try {\n const { zettelThemes } = await import(\"../../zettelkasten/index.js\");\n const result = await zettelThemes(backend, {\n vaultProjectId: params.vault_project_id,\n lookbackDays: params.lookback_days,\n minClusterSize: params.min_cluster_size,\n maxThemes: params.max_themes,\n similarityThreshold: params.similarity_threshold,\n });\n return {\n content: [{ type: \"text\", text: JSON.stringify(result, null, 2) }],\n };\n } catch (e) {\n return {\n content: [{ type: \"text\", text: `zettel_themes error: ${String(e)}` }],\n isError: true,\n };\n }\n}\n\n// ---------------------------------------------------------------------------\n// Tool: zettel_god_notes\n// ---------------------------------------------------------------------------\n\nexport interface ZettelGodNotesParams {\n limit?: number;\n min_inbound?: number;\n}\n\nexport async function toolZettelGodNotes(\n backend: StorageBackend,\n params: ZettelGodNotesParams\n): Promise<ToolResult> {\n try {\n const { zettelGodNotes } = await import(\"../../zettelkasten/index.js\");\n const result = await zettelGodNotes(backend, {\n limit: params.limit,\n minInbound: params.min_inbound,\n });\n return {\n content: [{ type: \"text\", text: JSON.stringify(result, null, 2) }],\n };\n } catch (e) {\n return {\n content: [{ type: \"text\", text: `zettel_god_notes error: ${String(e)}` }],\n isError: true,\n };\n }\n}\n\n// ---------------------------------------------------------------------------\n// Tool: zettel_communities\n// ---------------------------------------------------------------------------\n\nexport interface ZettelCommunitiesParams {\n min_size?: number;\n max_communities?: number;\n resolution?: number;\n}\n\nexport async function toolZettelCommunities(\n backend: StorageBackend,\n params: ZettelCommunitiesParams\n): Promise<ToolResult> {\n try {\n const { zettelCommunities } = await import(\"../../zettelkasten/index.js\");\n const result = await zettelCommunities(backend, {\n minSize: params.min_size,\n maxCommunities: params.max_communities,\n resolution: params.resolution,\n });\n return {\n content: [{ type: \"text\", text: JSON.stringify(result, null, 2) }],\n };\n } catch (e) {\n return {\n content: [{ type: \"text\", text: `zettel_communities error: ${String(e)}` }],\n isError: true,\n };\n }\n}\n","/**\n * MCP tool handlers for the temporal knowledge graph.\n *\n * Tools:\n * kg_add — insert a new (subject, predicate, object) triple\n * kg_query — query triples with optional temporal filter\n * kg_invalidate — mark a triple as no longer valid (sets valid_to)\n * kg_contradictions — find (subject, predicate) pairs with multiple valid objects\n */\n\nimport type { StorageBackend } from \"../../storage/interface.js\";\nimport type { ToolResult } from \"./types.js\";\n\n// ---------------------------------------------------------------------------\n// Tool: kg_add\n// ---------------------------------------------------------------------------\n\nexport interface KgAddToolParams {\n subject: string;\n predicate: string;\n object: string;\n project_id?: number;\n source_session?: string;\n confidence?: \"EXTRACTED\" | \"INFERRED\" | \"AMBIGUOUS\";\n}\n\nexport async function toolKgAdd(\n backend: StorageBackend,\n params: KgAddToolParams\n): Promise<ToolResult> {\n try {\n if (!params.subject || !params.predicate || !params.object) {\n return {\n content: [{ type: \"text\", text: \"kg_add error: subject, predicate, and object are required\" }],\n isError: true,\n };\n }\n const triple = await backend.addKgTriple(params);\n return {\n content: [{ type: \"text\", text: JSON.stringify(triple, null, 2) }],\n };\n } catch (e) {\n return {\n content: [{ type: \"text\", text: `kg_add error: ${String(e)}` }],\n isError: true,\n };\n }\n}\n\n// ---------------------------------------------------------------------------\n// Tool: kg_query\n// ---------------------------------------------------------------------------\n\nexport interface KgQueryToolParams {\n subject?: string;\n predicate?: string;\n object?: string;\n project_id?: number;\n as_of?: string; // ISO 8601 string; converted to Date\n include_invalidated?: boolean;\n}\n\nexport async function toolKgQuery(\n backend: StorageBackend,\n params: KgQueryToolParams\n): Promise<ToolResult> {\n try {\n const asOf = params.as_of ? new Date(params.as_of) : undefined;\n if (asOf && isNaN(asOf.getTime())) {\n return {\n content: [{ type: \"text\", text: `kg_query error: invalid as_of date: ${params.as_of}` }],\n isError: true,\n };\n }\n const triples = await backend.queryKgTriples({\n subject: params.subject,\n predicate: params.predicate,\n object: params.object,\n project_id: params.project_id,\n as_of: asOf,\n include_invalidated: params.include_invalidated,\n });\n return {\n content: [{ type: \"text\", text: JSON.stringify(triples, null, 2) }],\n };\n } catch (e) {\n return {\n content: [{ type: \"text\", text: `kg_query error: ${String(e)}` }],\n isError: true,\n };\n }\n}\n\n// ---------------------------------------------------------------------------\n// Tool: kg_invalidate\n// ---------------------------------------------------------------------------\n\nexport interface KgInvalidateToolParams {\n triple_id: number;\n}\n\nexport async function toolKgInvalidate(\n backend: StorageBackend,\n params: KgInvalidateToolParams\n): Promise<ToolResult> {\n try {\n if (params.triple_id === undefined || params.triple_id === null) {\n return {\n content: [{ type: \"text\", text: \"kg_invalidate error: triple_id is required\" }],\n isError: true,\n };\n }\n await backend.invalidateKgTriple(params.triple_id);\n return {\n content: [{ type: \"text\", text: JSON.stringify({ invalidated: true, triple_id: params.triple_id }) }],\n };\n } catch (e) {\n return {\n content: [{ type: \"text\", text: `kg_invalidate error: ${String(e)}` }],\n isError: true,\n };\n }\n}\n\n// ---------------------------------------------------------------------------\n// Tool: kg_contradictions\n// ---------------------------------------------------------------------------\n\nexport interface KgContradictionsToolParams {\n subject: string;\n}\n\nexport async function toolKgContradictions(\n backend: StorageBackend,\n params: KgContradictionsToolParams\n): Promise<ToolResult> {\n try {\n if (!params.subject) {\n return {\n content: [{ type: \"text\", text: \"kg_contradictions error: subject is required\" }],\n isError: true,\n };\n }\n const contradictions = await backend.getKgContradictions(params.subject);\n return {\n content: [{ type: \"text\", text: JSON.stringify(contradictions, null, 2) }],\n };\n } catch (e) {\n return {\n content: [{ type: \"text\", text: `kg_contradictions error: ${String(e)}` }],\n isError: true,\n };\n }\n}\n","/**\n * MCP tool handler: memory_wakeup\n *\n * Returns the L0+L1 wake-up context block for a project:\n * L0 — user identity from ~/.pai/identity.txt\n * L1 — recent session note highlights (Work Done / Key Decisions / Next Steps)\n */\n\nimport type { RegistryBackend } from \"../../storage/registry-interface.js\";\nimport { buildWakeupContext } from \"../../memory/wakeup.js\";\nimport { detectProjectFromPath } from \"./types.js\";\nimport type { ToolResult } from \"./types.js\";\n\n// ---------------------------------------------------------------------------\n// Default token budget (constant, overridable per call)\n// ---------------------------------------------------------------------------\n\nconst DEFAULT_TOKEN_BUDGET = 800;\n\n// ---------------------------------------------------------------------------\n// Params\n// ---------------------------------------------------------------------------\n\nexport interface MemoryWakeupParams {\n /** Project slug or absolute path. Omit to auto-detect from process.cwd(). */\n project?: string;\n /** L1 token budget. Default 800. */\n token_budget?: number;\n}\n\n// ---------------------------------------------------------------------------\n// Tool handler\n// ---------------------------------------------------------------------------\n\nexport async function toolMemoryWakeup(\n registry: RegistryBackend,\n params: MemoryWakeupParams\n): Promise<ToolResult> {\n try {\n const tokenBudget = params.token_budget ?? DEFAULT_TOKEN_BUDGET;\n\n // Resolve the project root path\n let rootPath: string | undefined;\n\n if (params.project) {\n // Try slug lookup first\n const bySlug = await registry.getProjectBySlug(params.project);\n\n if (bySlug) {\n rootPath = bySlug.root_path;\n } else {\n // Maybe it's an absolute path — try path-based detect\n const detected = await detectProjectFromPath(registry, params.project);\n if (detected) rootPath = detected.root_path;\n }\n } else {\n // Auto-detect from cwd\n const detected = await detectProjectFromPath(registry, process.cwd());\n if (detected) rootPath = detected.root_path;\n }\n\n const wakeupBlock = buildWakeupContext(rootPath, tokenBudget);\n\n if (!wakeupBlock) {\n return {\n content: [\n {\n type: \"text\",\n text: \"No wake-up context available. Create ~/.pai/identity.txt for L0 identity, or ensure session notes exist for L1 story.\",\n },\n ],\n };\n }\n\n return {\n content: [\n {\n type: \"text\",\n text: `WAKEUP CONTEXT\\n\\n${wakeupBlock}`,\n },\n ],\n };\n } catch (e) {\n return {\n content: [{ type: \"text\", text: `Wakeup context error: ${String(e)}` }],\n isError: true,\n };\n }\n}\n","/**\n * getTaxonomy — returns the shape of stored memory without requiring a query.\n *\n * Answers \"what do I know about?\" not \"what do I know about X?\"\n *\n * Uses the registry DB (projects, sessions) and the storage backend\n * (memory_files, memory_chunks) to build a structural overview.\n */\n\nimport type { StorageBackend } from \"../storage/interface.js\";\nimport type { RegistryBackend } from \"../storage/registry-interface.js\";\n\n// ---------------------------------------------------------------------------\n// Return types\n// ---------------------------------------------------------------------------\n\nexport interface TaxonomyProject {\n slug: string;\n display_name: string;\n session_count: number;\n note_count: number;\n last_activity: string | null; // ISO date string, e.g. \"2026-04-07\"\n top_tags: string[]; // project tags from the registry\n}\n\nexport interface TaxonomyTotals {\n projects: number;\n sessions: number;\n notes: number;\n chunks: number;\n}\n\nexport interface TaxonomyRecentActivity {\n project_slug: string;\n action: string;\n timestamp: string; // ISO date string\n}\n\nexport interface TaxonomyResult {\n projects: TaxonomyProject[];\n totals: TaxonomyTotals;\n recent_activity: TaxonomyRecentActivity[];\n}\n\n// ---------------------------------------------------------------------------\n// Options\n// ---------------------------------------------------------------------------\n\nexport interface TaxonomyOptions {\n /** Include archived projects. Default: false. */\n include_archived?: boolean;\n /** Maximum projects to return. Default: 50. */\n limit?: number;\n}\n\n// ---------------------------------------------------------------------------\n// Main function\n// ---------------------------------------------------------------------------\n\n/**\n * Build a taxonomy of stored memory — what projects exist, how much is stored,\n * and what has been active recently.\n *\n * Registry queries (projects, sessions) go through RegistryBackend (async).\n * Storage backend queries (files, chunks) are async.\n */\nexport async function getTaxonomy(\n registry: RegistryBackend,\n storage: StorageBackend,\n options: TaxonomyOptions = {}\n): Promise<TaxonomyResult> {\n const includeArchived = options.include_archived ?? false;\n const limit = options.limit ?? 50;\n\n // -------------------------------------------------------------------------\n // 1. Load all (active) projects from the registry\n // -------------------------------------------------------------------------\n\n const projectRows = includeArchived\n ? await registry.listProjects({ orderBy: \"updated_desc\", limit })\n : await registry.listProjects({ status: \"active\", orderBy: \"updated_desc\", limit });\n\n if (projectRows.length === 0) {\n return {\n projects: [],\n totals: { projects: 0, sessions: 0, notes: 0, chunks: 0 },\n recent_activity: [],\n };\n }\n\n const projectIds = projectRows.map((p) => p.id);\n const projectIdSet = new Set(projectIds);\n\n // -------------------------------------------------------------------------\n // 2. Session counts per project (registry, async)\n // -------------------------------------------------------------------------\n\n const sessionCountsByProject = new Map<number, number>();\n const lastSessionDateByProject = new Map<number, string | null>();\n\n for (const projectId of projectIds) {\n sessionCountsByProject.set(projectId, await registry.countSessionsForProject(projectId));\n lastSessionDateByProject.set(projectId, await registry.getMostRecentSessionDate(projectId));\n }\n\n // -------------------------------------------------------------------------\n // 3. Tags per project (registry, async)\n // -------------------------------------------------------------------------\n\n const tagsByProject = new Map<number, string[]>();\n\n for (const projectId of projectIds) {\n tagsByProject.set(projectId, await registry.listTagsForProject(projectId));\n }\n\n // -------------------------------------------------------------------------\n // 4. Note and chunk counts per project (storage backend, async)\n // memory_files (one row per indexed file) and memory_chunks (many per\n // file), via StorageBackend.getProjectStats() — same on both backends.\n // -------------------------------------------------------------------------\n\n const noteCountsByProject = new Map<number, number>();\n const chunkCountsByProject = new Map<number, number>();\n\n for (const projectId of projectIds) {\n const projectStats = await storage.getProjectStats(projectId);\n noteCountsByProject.set(projectId, projectStats.files);\n chunkCountsByProject.set(projectId, projectStats.chunks);\n }\n\n // -------------------------------------------------------------------------\n // 5. Global totals\n // -------------------------------------------------------------------------\n\n const stats = await storage.getStats();\n\n const totalProjects = includeArchived\n ? await registry.countProjects()\n : await registry.countProjects({ status: \"active\" });\n\n // No RegistryBackend method for a global session count (flagged in the\n // report) — approximate as the sum over the projects already fetched\n // above (bounded by `limit`), the closest available without raw SQL.\n const totalSessions = [...sessionCountsByProject.values()].reduce((a, b) => a + b, 0);\n\n // -------------------------------------------------------------------------\n // 6. Recent activity — last 10 sessions across all (visible) projects\n // No RegistryBackend method filters sessions by their project's status\n // directly (flagged in the report) — over-fetch and filter client-side\n // against the project set already resolved above.\n // -------------------------------------------------------------------------\n\n const recentSessions = (await registry.listSessions({ limit: Math.max(50, limit) }))\n .filter((s) => projectIdSet.has(s.project_id))\n .slice(0, 10);\n\n const recentActivity: TaxonomyRecentActivity[] = recentSessions.map((row) => ({\n project_slug: row.project_slug,\n action: `session: ${row.title || \"(untitled)\"}`,\n timestamp: row.date,\n }));\n\n // -------------------------------------------------------------------------\n // 7. Assemble result\n // -------------------------------------------------------------------------\n\n const projects: TaxonomyProject[] = projectRows.map((row) => ({\n slug: row.slug,\n display_name: row.display_name,\n session_count: sessionCountsByProject.get(row.id) ?? 0,\n note_count: noteCountsByProject.get(row.id) ?? 0,\n last_activity: lastSessionDateByProject.get(row.id) ?? null,\n top_tags: tagsByProject.get(row.id) ?? [],\n }));\n\n return {\n projects,\n totals: {\n projects: totalProjects,\n sessions: totalSessions,\n notes: stats.files,\n chunks: stats.chunks,\n },\n recent_activity: recentActivity,\n };\n}\n","/**\n * MCP tool handler: memory_taxonomy\n *\n * Returns the SHAPE of stored memory without requiring a query.\n * Answers \"what do I know about?\" not \"what do I know about X?\"\n */\n\nimport type { StorageBackend } from \"../../storage/interface.js\";\nimport type { RegistryBackend } from \"../../storage/registry-interface.js\";\nimport { getTaxonomy } from \"../../memory/taxonomy.js\";\nimport type { ToolResult } from \"./types.js\";\n\n// ---------------------------------------------------------------------------\n// Tool: memory_taxonomy\n// ---------------------------------------------------------------------------\n\nexport interface MemoryTaxonomyParams {\n /** Include archived projects in the result. Default: false. */\n include_archived?: boolean;\n /** Maximum number of projects to return. Default: 50. */\n limit?: number;\n}\n\nexport async function toolMemoryTaxonomy(\n registry: RegistryBackend,\n storage: StorageBackend,\n params: MemoryTaxonomyParams = {}\n): Promise<ToolResult> {\n try {\n const result = await getTaxonomy(registry, storage, {\n include_archived: params.include_archived,\n limit: params.limit,\n });\n\n // -----------------------------------------------------------------------\n // Format output as human-readable text\n // -----------------------------------------------------------------------\n\n const lines: string[] = [];\n\n // Totals header\n lines.push(\n `PAI Memory Taxonomy — ${result.totals.projects} project(s), ` +\n `${result.totals.sessions} session(s), ` +\n `${result.totals.notes} indexed file(s), ` +\n `${result.totals.chunks} chunk(s)`\n );\n lines.push(\"\");\n\n // Per-project breakdown\n if (result.projects.length === 0) {\n lines.push(\"No active projects found.\");\n } else {\n lines.push(\"Projects:\");\n for (const p of result.projects) {\n const tagStr = p.top_tags.length > 0 ? ` [${p.top_tags.join(\", \")}]` : \"\";\n const activityStr = p.last_activity ? ` last: ${p.last_activity}` : \"\";\n lines.push(\n ` ${p.slug} — ${p.display_name}` +\n ` sessions=${p.session_count}` +\n (p.note_count > 0 ? ` files=${p.note_count}` : \"\") +\n activityStr +\n tagStr\n );\n }\n }\n\n // Recent activity\n if (result.recent_activity.length > 0) {\n lines.push(\"\");\n lines.push(\"Recent activity (last 10 sessions across all projects):\");\n for (const a of result.recent_activity) {\n lines.push(` ${a.timestamp} ${a.project_slug} ${a.action}`);\n }\n }\n\n return {\n content: [{ type: \"text\", text: lines.join(\"\\n\") }],\n };\n } catch (e) {\n return {\n content: [{ type: \"text\", text: `memory_taxonomy error: ${String(e)}` }],\n isError: true,\n };\n }\n}\n","/**\n * tunnels.ts — cross-project concept detection (\"palace graph / tunnel detection\")\n *\n * A \"tunnel\" is a concept (word or short phrase) that appears in chunks from\n * at least two distinct projects. These serendipitous cross-project connections\n * are surfaced so the user can discover unexpected relationships between their\n * work streams.\n *\n * The actual SQL (SQLite FTS5 vocab / Postgres ts_stat) lives under\n * src/storage/{sqlite,postgres}/tunnels.ts, reached through\n * StorageBackend.findTunnels() — this module just re-exports the public\n * types and forwards the call, so existing callers (MCP tools) don't have\n * to change their import path.\n */\n\nimport type { StorageBackend } from \"../storage/interface.js\";\nimport type { RegistryBackend } from \"../storage/registry-interface.js\";\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\nexport interface Tunnel {\n /** The shared term or phrase. */\n concept: string;\n /** Project slugs where this concept appears. */\n projects: string[];\n /** Total chunk occurrences across all projects. */\n occurrences: number;\n /** First time the concept appeared (Unix ms). */\n first_seen: number;\n /** Most recent time the concept appeared (Unix ms). */\n last_seen: number;\n}\n\nexport interface FindTunnelsOptions {\n /** Minimum distinct projects a concept must appear in. Default 2. */\n min_projects?: number;\n /** Minimum total chunk occurrences across all projects. Default 3. */\n min_occurrences?: number;\n /** Maximum number of tunnels to return. Default 20. */\n limit?: number;\n}\n\nexport interface FindTunnelsResult {\n tunnels: Tunnel[];\n projects_analyzed: number;\n total_concepts_evaluated: number;\n}\n\n// ---------------------------------------------------------------------------\n// Public entry point\n// ---------------------------------------------------------------------------\n\n/**\n * Find cross-project concept tunnels.\n *\n * Works with both SQLite and Postgres storage backends.\n * Requires the RegistryBackend for project slug resolution.\n *\n * @param backend Active PAI storage backend.\n * @param registry Registry backend for project slug resolution.\n * @param options Filter and limit options.\n */\nexport async function findTunnels(\n backend: StorageBackend,\n registry: RegistryBackend,\n options?: FindTunnelsOptions\n): Promise<FindTunnelsResult> {\n return backend.findTunnels(registry, options);\n}\n","/**\n * MCP tool handler: memory_tunnels\n *\n * Finds \"tunnels\" — concepts that appear across multiple projects —\n * surfacing cross-project serendipitous connections in the PAI memory graph.\n */\n\nimport type { StorageBackend } from \"../../storage/interface.js\";\nimport type { RegistryBackend } from \"../../storage/registry-interface.js\";\nimport type { ToolResult } from \"./types.js\";\nimport { findTunnels } from \"../../memory/tunnels.js\";\n\n// ---------------------------------------------------------------------------\n// Tool: memory_tunnels\n// ---------------------------------------------------------------------------\n\nexport interface MemoryTunnelsParams {\n /** Minimum distinct projects a concept must appear in. Default 2. */\n min_projects?: number;\n /** Minimum total chunk occurrences across all projects. Default 3. */\n min_occurrences?: number;\n /** Maximum number of tunnels to return. Default 20. */\n limit?: number;\n}\n\nexport async function toolMemoryTunnels(\n registry: RegistryBackend,\n backend: StorageBackend,\n params: MemoryTunnelsParams\n): Promise<ToolResult> {\n try {\n const result = await findTunnels(backend, registry, {\n min_projects: params.min_projects,\n min_occurrences: params.min_occurrences,\n limit: params.limit,\n });\n\n return {\n content: [{ type: \"text\", text: JSON.stringify(result, null, 2) }],\n };\n } catch (e) {\n return {\n content: [{ type: \"text\", text: `memory_tunnels error: ${String(e)}` }],\n isError: true,\n };\n }\n}\n","/**\n * MCP tool handler: memory_feedback\n *\n * Implements the MR2 feedback weight loop:\n * - Accepts chunk IDs and a rating (1-5)\n * - Applies EMA update to memory_chunks.relevance_score\n * - Finds entity names mentioned in those chunks and updates kg_entities.feedback_weight\n *\n * EMA formula: new = old + alpha * (normalized_rating - old)\n * where normalized_rating = (rating - 1) / 4 → maps [1,5] to [0,1]\n * and alpha = 0.1\n *\n * Integration into search scoring:\n * final_score *= (0.5 + relevance_score)\n * This multiplier ranges from 0.5 (relevance_score=0) to 1.5 (relevance_score=1),\n * giving a ±50% boost/penalty based on accumulated feedback.\n */\n\nimport type { ToolResult } from \"./types.js\";\nimport type { StorageBackend } from \"../../storage/interface.js\";\n\n// EMA learning rate\nconst FEEDBACK_ALPHA = 0.1;\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface MemoryFeedbackParams {\n /** Array of chunk IDs to apply feedback to */\n chunk_ids: string[];\n /** Rating from 1 (not relevant) to 5 (highly relevant) */\n rating: number;\n /** Optional tenant ID for entity feedback scoping. Default: \"default\" */\n tenant_id?: string;\n}\n\n// ---------------------------------------------------------------------------\n// Tool handler\n// ---------------------------------------------------------------------------\n\n/**\n * Apply relevance feedback to memory chunks and associated KG entities.\n * Both memory_chunks.relevance_score and kg_entities.feedback_weight go\n * through the passed StorageBackend, so this works on either backend.\n *\n * @param backend StorageBackend for both the chunk and kg_entities portions\n * @param params Feedback parameters\n */\nexport async function toolMemoryFeedback(\n backend: StorageBackend,\n params: MemoryFeedbackParams\n): Promise<ToolResult> {\n try {\n if (!Array.isArray(params.chunk_ids) || params.chunk_ids.length === 0) {\n return {\n content: [{ type: \"text\", text: \"memory_feedback error: chunk_ids must be a non-empty array\" }],\n isError: true,\n };\n }\n\n const rating = params.rating;\n if (typeof rating !== \"number\" || rating < 1 || rating > 5) {\n return {\n content: [{ type: \"text\", text: \"memory_feedback error: rating must be a number between 1 and 5\" }],\n isError: true,\n };\n }\n\n // Normalize rating [1,5] → [0,1]\n const normalizedRating = (rating - 1) / 4;\n\n // Fetch current relevance scores for the given chunk IDs\n const chunks = await backend.getChunksForFeedback(params.chunk_ids);\n\n if (chunks.length === 0) {\n return {\n content: [{ type: \"text\", text: \"memory_feedback: no matching chunks found\" }],\n isError: true,\n };\n }\n\n let updatedChunks = 0;\n const combinedText: string[] = [];\n\n // Apply EMA update to each chunk's relevance_score\n for (const chunk of chunks) {\n const oldScore = chunk.relevanceScore ?? 0.5;\n const newScore = oldScore + FEEDBACK_ALPHA * (normalizedRating - oldScore);\n await backend.updateChunkRelevanceScore(chunk.id, newScore);\n updatedChunks++;\n combinedText.push(chunk.text);\n }\n\n // Find entity mentions in updated chunks and apply EMA to kg_entities.feedback_weight\n const tenantId = params.tenant_id ?? \"default\";\n const entities = await backend.listKgEntities(tenantId, undefined, 500);\n const text = combinedText.join(\"\\n\").toLowerCase();\n\n let updatedEntities = 0;\n for (const entity of entities) {\n if (text.includes(entity.name.toLowerCase())) {\n await backend.updateEntityFeedbackWeight(entity.entity_id, normalizedRating, FEEDBACK_ALPHA);\n updatedEntities++;\n }\n }\n\n return {\n content: [\n {\n type: \"text\",\n text: JSON.stringify({\n updated_chunks: updatedChunks,\n updated_entities: updatedEntities,\n rating,\n normalized_rating: normalizedRating,\n }),\n },\n ],\n };\n } catch (e) {\n return {\n content: [{ type: \"text\", text: `memory_feedback error: ${String(e)}` }],\n isError: true,\n };\n }\n}\n","/**\n * kg-search.ts — Graph-completion retrieval (MR1).\n *\n * Implements graphCompletionSearch: a multi-phase retrieval strategy that\n * combines vector search with knowledge-graph neighborhood expansion.\n *\n * Algorithm:\n * Phase 1: Wide vector search (50 candidates) using queryVec\n * Phase 2: Extract entity mentions from retrieved chunks (substring match vs kg_entities)\n * Phase 3: BFS neighborhood expansion in kg_triples (1-2 hops from matched entities)\n * Phase 4: Re-rank all collected triples against query embedding using cosine similarity\n *\n * The result is a ranked list of KgTriple objects most relevant to the query,\n * augmented with graph-derived context that pure vector search would miss.\n */\n\nimport { cosineSimilarity, deserializeEmbedding } from \"./embeddings.js\";\nimport type { SearchResult, SearchOptions } from \"./search.js\";\nimport type { KgTriple } from \"./kg.js\";\nimport type { StorageBackend } from \"../storage/interface.js\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface GraphCompletionResult {\n /** Ranked KG triples relevant to the query */\n triples: Array<KgTriple & { relevanceScore: number }>;\n /** Chunks from the initial wide vector search (Phase 1) */\n seedChunks: SearchResult[];\n /** Entity names that were matched and used for BFS expansion */\n expandedEntities: string[];\n}\n\nexport interface GraphCompletionOptions {\n /** Number of seed chunks to fetch in Phase 1. Default: 50 */\n seedCount?: number;\n /** BFS hop depth for neighborhood expansion. Default: 1 */\n hops?: number;\n /** Maximum triples to return after re-ranking. Default: 20 */\n maxTriples?: number;\n /** Postgres project_id filter for triples. */\n projectId?: number;\n /** Tenant ID for entity lookup in the storage backend. Default: \"default\" */\n tenantId?: string;\n /** StorageBackend search options to pass to Phase 1 */\n searchOpts?: SearchOptions;\n}\n\n// ---------------------------------------------------------------------------\n// Phase 2: Entity mention extraction\n// ---------------------------------------------------------------------------\n\n/**\n * Find entity names (from the kg_entities table) that appear\n * as substrings in the given text chunks.\n *\n * Returns a deduplicated list of matching entity names.\n */\nasync function extractEntityMentions(\n federation: StorageBackend,\n chunks: SearchResult[],\n tenantId = \"default\"\n): Promise<string[]> {\n // Load all entity names for this tenant (sorted by length desc so longer\n // entity names are matched before substrings)\n const entities = await federation.listKgEntities(tenantId, undefined, 500);\n if (entities.length === 0) return [];\n\n const combinedText = chunks.map((c) => c.snippet).join(\"\\n\").toLowerCase();\n const matched = new Set<string>();\n\n for (const entity of entities) {\n if (combinedText.includes(entity.name.toLowerCase())) {\n matched.add(entity.name);\n }\n }\n\n return Array.from(matched);\n}\n\n// ---------------------------------------------------------------------------\n// Phase 3: BFS neighborhood expansion\n// ---------------------------------------------------------------------------\n\n/**\n * Perform BFS over kg_triples to find neighbors of the given entity names.\n *\n * For each entity name, fetches triples where the entity is the subject\n * or the object (bi-directional expansion). Supports 1 or 2 hop depths.\n *\n * Returns all unique triples found in the BFS expansion.\n */\nasync function bfsExpand(\n federation: StorageBackend,\n entityNames: string[],\n hops: number,\n projectId?: number\n): Promise<KgTriple[]> {\n const seen = new Set<number>(); // triple IDs already included\n const tripleMap = new Map<number, KgTriple>();\n const frontier = new Set(entityNames);\n\n for (let hop = 0; hop < hops; hop++) {\n const nextFrontier = new Set<string>();\n\n for (const name of frontier) {\n // Expand as subject\n const asSubject = await federation.queryKgTriples({\n subject: name,\n project_id: projectId,\n });\n\n // Expand as object\n const asObject = await federation.queryKgTriples({\n object: name,\n project_id: projectId,\n });\n\n for (const triple of [...asSubject, ...asObject]) {\n if (!seen.has(triple.id)) {\n seen.add(triple.id);\n tripleMap.set(triple.id, triple);\n // Add neighbors to next frontier for deeper hops\n nextFrontier.add(triple.subject);\n nextFrontier.add(triple.object);\n }\n }\n }\n\n // Next hop frontier: only new names not already processed\n frontier.clear();\n for (const name of nextFrontier) {\n if (!entityNames.includes(name)) {\n frontier.add(name);\n entityNames.push(name);\n }\n }\n }\n\n return Array.from(tripleMap.values());\n}\n\n// ---------------------------------------------------------------------------\n// Phase 4: Re-rank triples against query embedding\n// ---------------------------------------------------------------------------\n\n/**\n * Score each triple by cosine similarity to the query embedding.\n *\n * The triple text representation is: \"subject predicate object\"\n * We embed this on-the-fly using generateEmbedding(), which may use a cache.\n *\n * Returns triples sorted by relevance score descending.\n */\nasync function rerankTriples(\n triples: KgTriple[],\n queryVec: Float32Array,\n maxTriples: number\n): Promise<Array<KgTriple & { relevanceScore: number }>> {\n if (triples.length === 0) return [];\n\n const { generateEmbedding } = await import(\"./embeddings.js\");\n\n const scored = await Promise.all(\n triples.map(async (triple) => {\n const text = `${triple.subject} ${triple.predicate} ${triple.object}`;\n try {\n const vec = await generateEmbedding(text, false);\n const score = cosineSimilarity(queryVec, vec);\n return { ...triple, relevanceScore: score };\n } catch {\n return { ...triple, relevanceScore: 0 };\n }\n })\n );\n\n return scored\n .sort((a, b) => b.relevanceScore - a.relevanceScore)\n .slice(0, maxTriples);\n}\n\n// ---------------------------------------------------------------------------\n// Main: graphCompletionSearch\n// ---------------------------------------------------------------------------\n\n/**\n * Graph-completion retrieval combining vector search with KG neighborhood BFS.\n *\n * @param federation Active StorageBackend (for chunk/entity/kg_triples lookup)\n * @param queryVec Pre-computed embedding for the search query\n * @param opts Configuration options\n */\nexport async function graphCompletionSearch(\n federation: StorageBackend,\n queryVec: Float32Array,\n opts: GraphCompletionOptions = {}\n): Promise<GraphCompletionResult> {\n const {\n seedCount = 50,\n hops = 1,\n maxTriples = 20,\n projectId,\n tenantId = \"default\",\n searchOpts,\n } = opts;\n\n // Phase 1: Wide vector search for seed chunks\n const seedSearchOpts: SearchOptions = {\n ...searchOpts,\n maxResults: seedCount,\n projectIds: projectId != null ? [projectId] : searchOpts?.projectIds,\n };\n\n let seedChunks: SearchResult[] = [];\n try {\n seedChunks = await federation.searchSemantic(queryVec, seedSearchOpts);\n } catch (e) {\n process.stderr.write(`[kg-search] Phase 1 seed search error: ${e}\\n`);\n }\n\n if (seedChunks.length === 0) {\n return { triples: [], seedChunks: [], expandedEntities: [] };\n }\n\n // Phase 2: Extract entity mentions from seed chunks\n const expandedEntities = await extractEntityMentions(federation, seedChunks, tenantId);\n\n if (expandedEntities.length === 0) {\n // No entities matched — return empty graph results (seed chunks still useful)\n return { triples: [], seedChunks, expandedEntities: [] };\n }\n\n // Phase 3: BFS neighborhood expansion in kg_triples\n let expandedTriples: KgTriple[] = [];\n try {\n expandedTriples = await bfsExpand(federation, [...expandedEntities], hops, projectId);\n } catch (e) {\n process.stderr.write(`[kg-search] Phase 3 BFS expansion error: ${e}\\n`);\n }\n\n if (expandedTriples.length === 0) {\n return { triples: [], seedChunks, expandedEntities };\n }\n\n // Phase 4: Re-rank triples against query embedding\n let rankedTriples: Array<KgTriple & { relevanceScore: number }> = [];\n try {\n rankedTriples = await rerankTriples(expandedTriples, queryVec, maxTriples);\n } catch (e) {\n process.stderr.write(`[kg-search] Phase 4 rerank error: ${e}\\n`);\n // Fall back to unranked triples\n rankedTriples = expandedTriples.slice(0, maxTriples).map((t) => ({\n ...t,\n relevanceScore: 0,\n }));\n }\n\n return { triples: rankedTriples, seedChunks, expandedEntities };\n}\n","/**\n * MCP tool handler: memory_kg_search\n *\n * Exposes graphCompletionSearch as an MCP tool (MR1 — graph-completion retrieval).\n *\n * Algorithm: wide vector search → entity mention extraction → BFS neighborhood\n * expansion in kg_triples → re-rank triples by cosine similarity to query.\n *\n * Requires the storage backend for chunk/entity lookups and kg_triples BFS\n * expansion.\n *\n * Returns ranked KG triples with relevanceScore, plus seed chunk metadata.\n */\n\nimport type { ToolResult } from \"./types.js\";\nimport type { StorageBackend } from \"../../storage/interface.js\";\nimport { graphCompletionSearch } from \"../../memory/kg-search.js\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface MemoryKgSearchParams {\n /** Free-text query — used to generate embedding for Phase 1 vector search. */\n query: string;\n /** Restrict seed vector search to a specific project ID. Optional. */\n project_id?: number;\n /** Number of seed chunks from Phase 1 vector search. Default: 50 */\n wide_k?: number;\n /** BFS hop depth for KG neighborhood expansion. Default: 1 */\n neighborhood_depth?: number;\n /** Maximum triples to return after Phase 4 re-ranking. Default: 20 */\n top_k?: number;\n /** Tenant ID for entity lookup in the storage backend. Default: \"default\" */\n tenant_id?: string;\n}\n\n// ---------------------------------------------------------------------------\n// Tool handler\n// ---------------------------------------------------------------------------\n\n/**\n * Graph-completion search: vector search + KG neighborhood BFS.\n *\n * @param federation Active StorageBackend for chunk/entity/kg_triples operations\n * @param params Tool parameters\n */\nexport async function toolMemoryKgSearch(\n federation: StorageBackend,\n params: MemoryKgSearchParams\n): Promise<ToolResult> {\n try {\n if (!params.query || typeof params.query !== \"string\" || params.query.trim() === \"\") {\n return {\n content: [{ type: \"text\", text: \"memory_kg_search error: query is required\" }],\n isError: true,\n };\n }\n\n const { generateEmbedding } = await import(\"../../memory/embeddings.js\");\n const queryVec = await generateEmbedding(params.query, true);\n\n const result = await graphCompletionSearch(federation, queryVec, {\n seedCount: params.wide_k ?? 50,\n hops: params.neighborhood_depth ?? 1,\n maxTriples: params.top_k ?? 20,\n projectId: params.project_id,\n tenantId: params.tenant_id ?? \"default\",\n });\n\n if (result.triples.length === 0) {\n return {\n content: [\n {\n type: \"text\",\n text: `No KG triples found for query: \"${params.query}\"\\n` +\n `Seed chunks: ${result.seedChunks.length}, ` +\n `Expanded entities: ${result.expandedEntities.length}`,\n },\n ],\n };\n }\n\n const tripleLines = result.triples\n .map((t, i) =>\n `[${i + 1}] (score=${t.relevanceScore.toFixed(4)}) ${t.subject} — ${t.predicate} — ${t.object}`\n )\n .join(\"\\n\");\n\n const entityList = result.expandedEntities.slice(0, 10).join(\", \");\n const summary = [\n `Found ${result.triples.length} KG triple(s) for \"${params.query}\"`,\n `Seed chunks: ${result.seedChunks.length}`,\n `Expanded entities (${result.expandedEntities.length}): ${entityList}`,\n \"\",\n tripleLines,\n ].join(\"\\n\");\n\n return {\n content: [{ type: \"text\", text: summary }],\n };\n } catch (e) {\n return {\n content: [{ type: \"text\", text: `memory_kg_search error: ${String(e)}` }],\n isError: true,\n };\n }\n}\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"vault-indexer-mCFus_AM.mjs","names":[],"sources":["../src/memory/vault/walk.ts","../src/memory/vault/deduplicate.ts","../src/memory/vault/parse-links.ts","../src/memory/vault/name-index.ts","../src/memory/vault/resolve.ts","../src/memory/vault/indexer.ts"],"sourcesContent":["/**\n * Vault directory walker — follows symlinks with cycle detection.\n */\n\nimport { statSync, readdirSync, existsSync } from \"node:fs\";\nimport { join, relative } from \"node:path\";\nimport type { VaultFile } from \"./types.js\";\n\n/** Maximum number of .md files to collect from a vault. */\nconst VAULT_MAX_FILES = 10_000;\n\n/** Maximum recursion depth for vault directory walks. */\nconst VAULT_MAX_DEPTH = 10;\n\n/**\n * Directories to always skip, at any depth, during vault walks.\n * Includes standard build/VCS noise plus Obsidian-specific directories.\n */\nconst VAULT_SKIP_DIRS = new Set([\n // Version control\n \".git\",\n // Dependency directories (any language)\n \"node_modules\",\n \"vendor\",\n \"Pods\",\n // Build / compile output\n \"dist\",\n \"build\",\n \"out\",\n \"DerivedData\",\n \".next\",\n // Python virtual environments and caches\n \".venv\",\n \"venv\",\n \"__pycache__\",\n // General caches\n \".cache\",\n \".bun\",\n // Obsidian internals\n \".obsidian\",\n \".trash\",\n]);\n\n/**\n * Recursively collect all .md files under a vault root, following symlinks.\n *\n * Symlink-following behaviour:\n * - Symbolic links to files: followed if the target is a .md file\n * - Symbolic links to directories: followed with cycle detection via inode\n *\n * Cycle detection is based on the real inode of each visited directory.\n * Using the real stat (not lstat) ensures that symlinked dirs resolve to\n * their actual inode, preventing infinite loops.\n *\n * @param vaultRoot Absolute path to the vault root directory.\n * @param opts Optional overrides for maxFiles and maxDepth.\n */\nexport function walkVaultMdFiles(\n vaultRoot: string,\n opts?: { maxFiles?: number; maxDepth?: number },\n): VaultFile[] {\n const maxFiles = opts?.maxFiles ?? VAULT_MAX_FILES;\n const maxDepth = opts?.maxDepth ?? VAULT_MAX_DEPTH;\n\n const results: VaultFile[] = [];\n const visitedDirs = new Set<string>();\n\n function walk(dir: string, depth: number): void {\n if (results.length >= maxFiles) return;\n if (depth > maxDepth) return;\n\n // Get the real inode of this directory (follows symlinks on the dir itself)\n let dirStat: ReturnType<typeof statSync>;\n try {\n dirStat = statSync(dir);\n } catch {\n return; // Unreadable or broken symlink — skip\n }\n\n const dirKey = `${dirStat.dev}:${dirStat.ino}`;\n if (visitedDirs.has(dirKey)) return; // Cycle detected\n visitedDirs.add(dirKey);\n\n let entries: import(\"node:fs\").Dirent<string>[];\n try {\n entries = readdirSync(dir, { withFileTypes: true, encoding: \"utf8\" });\n } catch {\n return; // Unreadable directory — skip\n }\n\n for (const entry of entries) {\n if (results.length >= maxFiles) break;\n if (VAULT_SKIP_DIRS.has(entry.name)) continue;\n\n const full = join(dir, entry.name);\n\n if (entry.isSymbolicLink()) {\n // Follow the symlink — resolve to real target\n let targetStat: ReturnType<typeof statSync>;\n try {\n targetStat = statSync(full); // statSync follows symlinks\n } catch {\n continue; // Broken symlink — skip\n }\n\n if (targetStat.isDirectory()) {\n if (!VAULT_SKIP_DIRS.has(entry.name)) {\n walk(full, depth + 1);\n }\n } else if (targetStat.isFile() && entry.name.endsWith(\".md\")) {\n results.push({\n absPath: full,\n vaultRelPath: relative(vaultRoot, full),\n inode: targetStat.ino,\n device: targetStat.dev,\n });\n }\n } else if (entry.isDirectory()) {\n walk(full, depth + 1);\n } else if (entry.isFile() && entry.name.endsWith(\".md\")) {\n let fileStat: ReturnType<typeof statSync>;\n try {\n fileStat = statSync(full);\n } catch {\n continue;\n }\n results.push({\n absPath: full,\n vaultRelPath: relative(vaultRoot, full),\n inode: fileStat.ino,\n device: fileStat.dev,\n });\n }\n }\n }\n\n if (existsSync(vaultRoot)) {\n walk(vaultRoot, 0);\n }\n\n return results;\n}\n","/**\n * Inode-based deduplication for vault files.\n */\n\nimport type { VaultFile, InodeGroup } from \"./types.js\";\n\n/**\n * Group vault files by inode identity (device + inode).\n *\n * Within each group, the canonical file is chosen as the one with the\n * fewest path separators (shallowest), breaking ties by shortest string.\n * All other group members become aliases.\n */\nexport function deduplicateByInode(files: VaultFile[]): InodeGroup[] {\n const groups = new Map<string, VaultFile[]>();\n\n for (const file of files) {\n const key = `${file.device}:${file.inode}`;\n const existing = groups.get(key);\n if (existing) {\n existing.push(file);\n } else {\n groups.set(key, [file]);\n }\n }\n\n const result: InodeGroup[] = [];\n\n for (const group of groups.values()) {\n if (group.length === 0) continue;\n\n // Sort: fewest path separators first, then shortest string\n const sorted = [...group].sort((a, b) => {\n const aDepth = (a.vaultRelPath.match(/\\//g) ?? []).length;\n const bDepth = (b.vaultRelPath.match(/\\//g) ?? []).length;\n if (aDepth !== bDepth) return aDepth - bDepth;\n return a.vaultRelPath.length - b.vaultRelPath.length;\n });\n\n const [canonical, ...aliases] = sorted as [VaultFile, ...VaultFile[]];\n result.push({ canonical, aliases });\n }\n\n return result;\n}\n","/**\n * Markdown link parser for vault files.\n *\n * Handles wikilinks ([[target]]), markdown links ([text](path)),\n * embeds (![[target]]), and frontmatter wikilinks.\n */\n\nimport type { ParsedLink } from \"./types.js\";\n\n/**\n * Parse all links from markdown content.\n *\n * Handles:\n * - Standard wikilinks: [[Target Note]]\n * - Aliased wikilinks: [[Target Note|Display Text]]\n * - Heading anchors: [[Target Note#Heading]] (stripped for resolution)\n * - Embeds: ![[Target Note]]\n * - Frontmatter wikilinks (YAML between --- delimiters)\n * - Markdown links: [text](path/to/note.md)\n * - Markdown embeds: ![alt](image.png)\n *\n * External URLs (http://, https://, mailto:, etc.) are excluded — only\n * relative paths are treated as vault links.\n *\n * @param content Raw markdown file content.\n * @returns Array of parsed links in document order.\n */\nexport function parseLinks(content: string): ParsedLink[] {\n const links: ParsedLink[] = [];\n const lines = content.split(\"\\n\");\n\n // Determine frontmatter range (YAML between opening and closing ---)\n let frontmatterEnd = 0;\n if (content.startsWith(\"---\")) {\n const closingIdx = content.indexOf(\"\\n---\", 3);\n if (closingIdx !== -1) {\n frontmatterEnd = content.slice(0, closingIdx + 4).split(\"\\n\").length - 1;\n }\n }\n\n // Regex for [[wikilinks]] and ![[embeds]]\n const wikilinkRe = /(!?)\\[\\[([^\\]]+?)\\]\\]/g;\n\n // Regex for markdown links [text](target) and embeds ![alt](target)\n const mdLinkRe = /(!)?\\[([^\\]]*)\\]\\(([^)]+)\\)/g;\n\n for (let lineIdx = 0; lineIdx < lines.length; lineIdx++) {\n const line = lines[lineIdx]!;\n const lineNumber = lineIdx + 1; // 1-indexed\n const isFrontmatter = lineIdx < frontmatterEnd;\n\n // --- Wikilinks ---\n wikilinkRe.lastIndex = 0;\n let match: RegExpExecArray | null;\n while ((match = wikilinkRe.exec(line)) !== null) {\n const isEmbed = match[1] === \"!\";\n const inner = match[2]!;\n\n // Split on first | for alias\n const pipeIdx = inner.indexOf(\"|\");\n const beforePipe = pipeIdx === -1 ? inner : inner.slice(0, pipeIdx);\n const alias = pipeIdx === -1 ? null : inner.slice(pipeIdx + 1);\n\n // Strip heading anchor (everything after #)\n const hashIdx = beforePipe.indexOf(\"#\");\n const raw = hashIdx === -1 ? beforePipe.trim() : beforePipe.slice(0, hashIdx).trim();\n\n if (!raw) continue; // Skip links with empty targets (e.g. [[#Heading]])\n\n links.push({\n raw,\n alias: alias?.trim() ?? null,\n lineNumber,\n isEmbed: isEmbed && !isFrontmatter,\n isMdLink: false,\n });\n }\n\n // --- Markdown links --- (skip inside frontmatter)\n if (!isFrontmatter) {\n mdLinkRe.lastIndex = 0;\n while ((match = mdLinkRe.exec(line)) !== null) {\n const isEmbed = match[1] === \"!\";\n const displayText = match[2]!;\n let target = match[3]!.trim();\n\n // Skip external URLs\n if (/^[a-z][a-z0-9+.-]*:/i.test(target)) continue;\n\n // Skip pure anchor links (#heading)\n if (target.startsWith(\"#\")) continue;\n\n // Strip heading anchor from target\n const hashIdx = target.indexOf(\"#\");\n if (hashIdx !== -1) target = target.slice(0, hashIdx);\n\n // URL-decode (Obsidian encodes spaces as %20 in md links)\n try {\n target = decodeURIComponent(target);\n } catch {\n // Malformed encoding — use as-is\n }\n\n // Strip .md extension for resolution (resolveWikilink adds it back)\n const raw = target.replace(/\\.md$/i, \"\").trim();\n if (!raw) continue;\n\n links.push({\n raw,\n alias: displayText || null,\n lineNumber,\n isEmbed,\n isMdLink: true,\n });\n }\n }\n }\n\n return links;\n}\n\n/** @deprecated Use {@link parseLinks} instead. */\nexport const parseWikilinks = parseLinks;\n","/**\n * Name index builder for Obsidian wikilink resolution.\n */\n\nimport { basename } from \"node:path\";\nimport type { VaultFile } from \"./types.js\";\n\n/**\n * Build a name lookup index for Obsidian wikilink resolution.\n *\n * Maps lowercase filename (without .md extension) to all vault-relative paths\n * that share that name. Includes both canonical paths and alias paths so that\n * wikilinks resolve regardless of which path the file is accessed through.\n */\nexport function buildNameIndex(files: VaultFile[]): Map<string, string[]> {\n const index = new Map<string, string[]>();\n\n for (const file of files) {\n const name = basename(file.vaultRelPath, \".md\").toLowerCase();\n const existing = index.get(name);\n if (existing) {\n existing.push(file.vaultRelPath);\n } else {\n index.set(name, [file.vaultRelPath]);\n }\n }\n\n return index;\n}\n","/**\n * Wikilink resolver using Obsidian's shortest-match algorithm.\n */\n\nimport { normalize, basename, dirname } from \"node:path\";\n\n/**\n * Resolve a wikilink target to a vault-relative path using Obsidian's rules.\n *\n * Resolution algorithm:\n * 1. If raw contains \"/\", attempt exact path match (with and without .md).\n * 2. Normalize: lowercase the raw target, strip .md extension.\n * 3. Look up in the name index (all files with that basename).\n * 4. If exactly one match, return it.\n * 5. If multiple matches, pick the one closest to the source file\n * (longest common directory prefix, then shortest overall path).\n * 6. If no matches, return null (dead link).\n *\n * @param raw The raw link target (heading-stripped, pipe-stripped).\n * @param nameIndex Map from lowercase basename-without-ext to vault paths.\n * @param sourcePath Vault-relative path of the file containing the link.\n * @returns Vault-relative path of the resolved target, or null.\n */\nexport function resolveWikilink(\n raw: string,\n nameIndex: Map<string, string[]>,\n sourcePath: string,\n): string | null {\n if (!raw) return null;\n\n // Case 1: path contains \"/\" — try exact match with and without .md\n if (raw.includes(\"/\")) {\n const normalized = normalize(raw);\n const normalizedMd = normalized.endsWith(\".md\") ? normalized : normalized + \".md\";\n\n // Check if any indexed path matches (case-insensitive for macOS compatibility)\n for (const [, paths] of nameIndex) {\n for (const p of paths) {\n if (p === normalizedMd || p === normalized) return p;\n if (p.toLowerCase() === normalizedMd.toLowerCase()) return p;\n }\n }\n // Fall through to name lookup in case the path prefix was wrong\n }\n\n // Normalize the raw target for name lookup.\n // Use the basename only — Obsidian resolves by filename, not full path.\n const rawBase = basename(raw)\n .replace(/\\.md$/i, \"\")\n .toLowerCase()\n .trim();\n\n if (!rawBase) return null;\n\n const candidates = nameIndex.get(rawBase);\n\n if (!candidates || candidates.length === 0) {\n return null; // Dead link\n }\n\n if (candidates.length === 1) {\n return candidates[0]!;\n }\n\n // Multiple matches — pick the one closest to the source file\n const sourceDir = dirname(sourcePath);\n\n let bestPath: string | null = null;\n let bestPrefixLen = -1;\n let bestPathLen = Infinity;\n\n for (const candidate of candidates) {\n const candidateDir = dirname(candidate);\n const prefixLen = commonPrefixLength(sourceDir, candidateDir);\n const pathLen = candidate.length;\n\n if (\n prefixLen > bestPrefixLen ||\n (prefixLen === bestPrefixLen && pathLen < bestPathLen)\n ) {\n bestPrefixLen = prefixLen;\n bestPathLen = pathLen;\n bestPath = candidate;\n }\n }\n\n return bestPath;\n}\n\n/**\n * Compute the length of the common prefix between two directory paths,\n * measured in path segments (not raw characters).\n *\n * Example: \"a/b/c\" and \"a/b/d\" → 2 (common: \"a\", \"b\")\n */\nfunction commonPrefixLength(a: string, b: string): number {\n if (a === \".\" && b === \".\") return 0;\n const aParts = a === \".\" ? [] : a.split(\"/\");\n const bParts = b === \".\" ? [] : b.split(\"/\");\n let count = 0;\n const len = Math.min(aParts.length, bParts.length);\n for (let i = 0; i < len; i++) {\n if (aParts[i] === bParts[i]) {\n count++;\n } else {\n break;\n }\n }\n return count;\n}\n","/**\n * Main vault indexing orchestrator.\n *\n * Indexes an entire Obsidian vault (or any markdown knowledge base), following\n * symlinks, deduplicating files by inode, parsing wikilinks, and computing\n * per-file health metrics (orphan detection, dead links).\n *\n * Key differences from the project indexer (indexer/sync.ts):\n * - Follows symbolic links (project indexer skips them)\n * - Deduplicates files with the same inode (same content reachable via multiple paths)\n * - Parses [[wikilinks]] and builds a directed link graph\n * - Resolves wikilinks using Obsidian's shortest-match algorithm\n * - Computes health metrics per file: inbound/outbound link counts, dead links, orphans\n */\n\nimport { readFileSync } from \"node:fs\";\nimport { basename } from \"node:path\";\nimport type {\n StorageBackend,\n VaultFileRow,\n VaultAliasRow,\n VaultLinkRow,\n VaultHealthRow,\n VaultNameEntry,\n ChunkRow,\n} from \"../../storage/interface.js\";\nimport { chunkMarkdown } from \"../chunker.js\";\nimport { sha256File, chunkId, yieldToEventLoop } from \"../indexer/helpers.js\";\nimport { walkVaultMdFiles } from \"./walk.js\";\nimport { deduplicateByInode } from \"./deduplicate.js\";\nimport { parseLinks } from \"./parse-links.js\";\nimport { buildNameIndex } from \"./name-index.js\";\nimport { resolveWikilink } from \"./resolve.js\";\nimport type { VaultIndexResult } from \"./types.js\";\n\n/** Number of files to process before yielding to the event loop. */\nconst VAULT_YIELD_EVERY = 1;\n\n/**\n * Index an entire Obsidian vault (or markdown knowledge base) using the\n * async StorageBackend interface.\n *\n * Steps:\n * 1. Walk vault root, following symlinks.\n * 2. Deduplicate by inode — each unique file is indexed once.\n * 3. Build a name index for wikilink resolution.\n * 4. For each canonical file:\n * a. SHA-256 hash for change detection — skip unchanged files.\n * b. Read content, chunk with chunkMarkdown().\n * c. Insert chunks into backend (memory_chunks and memory_fts).\n * d. Upsert vault_files row.\n * 5. Record aliases in vault_aliases.\n * 6. Rebuild vault_name_index table.\n * 7. Rebuild vault_links:\n * a. Parse [[wikilinks]] from each canonical file.\n * b. Resolve each link with resolveWikilink().\n * c. Insert into vault_links.\n * 8. Compute and upsert health metrics (vault_health).\n * 9. Return statistics.\n *\n * @param backend StorageBackend to write to.\n * @param vaultProjectId Registry project ID for the vault \"project\".\n * @param vaultRoot Absolute path to the vault root directory.\n */\nexport async function indexVault(\n backend: StorageBackend,\n vaultProjectId: number,\n vaultRoot: string,\n): Promise<VaultIndexResult> {\n const startTime = Date.now();\n\n const result: VaultIndexResult = {\n filesIndexed: 0,\n chunksCreated: 0,\n filesSkipped: 0,\n aliasesRecorded: 0,\n linksExtracted: 0,\n deadLinksFound: 0,\n orphansFound: 0,\n elapsed: 0,\n };\n\n // Step 1: Walk vault, collecting all .md files (follows symlinks)\n const allFiles = walkVaultMdFiles(vaultRoot);\n\n // Step 2: Deduplicate by inode\n const inodeGroups = deduplicateByInode(allFiles);\n\n // Step 3: Build name index (from all files including aliases, for resolution)\n const nameIndex = buildNameIndex(allFiles);\n\n // Step 4: Index each canonical file\n await yieldToEventLoop();\n let filesSinceYield = 0;\n\n for (const group of inodeGroups) {\n if (filesSinceYield >= VAULT_YIELD_EVERY) {\n await yieldToEventLoop();\n filesSinceYield = 0;\n }\n filesSinceYield++;\n\n const { canonical } = group;\n\n let content: string;\n try {\n content = readFileSync(canonical.absPath, \"utf8\");\n } catch {\n result.filesSkipped++;\n continue;\n }\n\n const hash = sha256File(content);\n\n // Change detection: skip if hash is unchanged\n const existing = await backend.getVaultFile(canonical.vaultRelPath);\n if (existing?.hash === hash) {\n result.filesSkipped++;\n continue;\n }\n\n // Delete old chunks for this vault path\n await backend.deleteChunksForFile(vaultProjectId, canonical.vaultRelPath);\n\n // Chunk the content\n const chunks = chunkMarkdown(content);\n const updatedAt = Date.now();\n\n // Extract title from first H1 heading or filename\n const titleMatch = /^#\\s+(.+)$/m.exec(content);\n const title = titleMatch\n ? titleMatch[1]!.trim()\n : basename(canonical.vaultRelPath, \".md\");\n\n // Build chunk rows\n const chunkRows: ChunkRow[] = [];\n for (let i = 0; i < chunks.length; i++) {\n const chunk = chunks[i]!;\n const id = chunkId(\n vaultProjectId,\n canonical.vaultRelPath,\n i,\n chunk.startLine,\n chunk.endLine,\n );\n chunkRows.push({\n id,\n projectId: vaultProjectId,\n source: \"vault\",\n tier: \"topic\",\n path: canonical.vaultRelPath,\n startLine: chunk.startLine,\n endLine: chunk.endLine,\n hash: chunk.hash,\n text: chunk.text,\n updatedAt,\n });\n }\n\n if (chunkRows.length > 0) {\n await backend.insertChunks(chunkRows);\n }\n\n // Upsert vault file record\n const vaultFileRow: VaultFileRow = {\n vaultPath: canonical.vaultRelPath,\n inode: canonical.inode,\n device: canonical.device,\n hash,\n title,\n indexedAt: updatedAt,\n };\n await backend.upsertVaultFile(vaultFileRow);\n\n result.filesIndexed++;\n result.chunksCreated += chunks.length;\n }\n\n // Step 5: Record aliases in vault_aliases\n await yieldToEventLoop();\n\n const allAliases: VaultAliasRow[] = [];\n for (const group of inodeGroups) {\n for (const alias of group.aliases) {\n allAliases.push({\n vaultPath: alias.vaultRelPath,\n canonicalPath: group.canonical.vaultRelPath,\n inode: alias.inode,\n device: alias.device,\n });\n result.aliasesRecorded++;\n }\n }\n\n const canonicalPaths = new Set(inodeGroups.map((g) => g.canonical.vaultRelPath));\n for (const canonPath of canonicalPaths) {\n await backend.deleteVaultAliases(canonPath);\n }\n if (allAliases.length > 0) {\n await backend.upsertVaultAliases(allAliases);\n }\n\n // Step 6: Rebuild vault_name_index\n await yieldToEventLoop();\n\n const nameEntries: VaultNameEntry[] = [];\n for (const [name, paths] of nameIndex) {\n for (const path of paths) {\n nameEntries.push({ name, vaultPath: path });\n }\n }\n await backend.replaceNameIndex(nameEntries);\n\n // Step 7: Rebuild vault_links\n await yieldToEventLoop();\n\n const linkRows: VaultLinkRow[] = [];\n const allSourcePaths: string[] = [];\n\n let linkParseYield = 0;\n for (const group of inodeGroups) {\n if (linkParseYield++ % VAULT_YIELD_EVERY === 0) {\n await yieldToEventLoop();\n }\n\n const { canonical } = group;\n allSourcePaths.push(canonical.vaultRelPath);\n\n let content: string;\n try {\n content = readFileSync(canonical.absPath, \"utf8\");\n } catch {\n continue;\n }\n\n const parsedLinks = parseLinks(content);\n for (const link of parsedLinks) {\n const target = resolveWikilink(link.raw, nameIndex, canonical.vaultRelPath);\n let linkType: string;\n if (link.isMdLink) {\n linkType = link.isEmbed ? \"md-embed\" : \"md-link\";\n } else {\n linkType = link.isEmbed ? \"embed\" : \"wikilink\";\n }\n linkRows.push({\n sourcePath: canonical.vaultRelPath,\n targetRaw: link.raw,\n targetPath: target,\n linkType,\n lineNumber: link.lineNumber,\n confidence: \"EXTRACTED\",\n });\n }\n }\n\n // Replace all links for all sources in batches of 500\n const LINK_BATCH_SIZE = 500;\n for (let i = 0; i < allSourcePaths.length; i += LINK_BATCH_SIZE) {\n const batchSources = allSourcePaths.slice(i, i + LINK_BATCH_SIZE);\n const batchLinks = linkRows.filter((r) => batchSources.includes(r.sourcePath));\n await backend.replaceLinksForSources(batchSources, batchLinks);\n await yieldToEventLoop();\n }\n\n result.linksExtracted = linkRows.length;\n result.deadLinksFound = linkRows.filter((r) => r.targetPath === null).length;\n\n // Step 8: Compute and upsert vault_health metrics\n await yieldToEventLoop();\n\n const outboundMap = new Map<string, number>();\n const deadMap = new Map<string, number>();\n const inboundMap = new Map<string, number>();\n\n for (const row of linkRows) {\n outboundMap.set(row.sourcePath, (outboundMap.get(row.sourcePath) ?? 0) + 1);\n if (row.targetPath === null) {\n deadMap.set(row.sourcePath, (deadMap.get(row.sourcePath) ?? 0) + 1);\n } else {\n inboundMap.set(row.targetPath, (inboundMap.get(row.targetPath) ?? 0) + 1);\n }\n }\n\n const computedAt = Date.now();\n let orphanCount = 0;\n\n const HEALTH_BATCH_SIZE = 500;\n for (let i = 0; i < inodeGroups.length; i += HEALTH_BATCH_SIZE) {\n const batch = inodeGroups.slice(i, i + HEALTH_BATCH_SIZE);\n const healthRows: VaultHealthRow[] = batch.map((group) => {\n const path = group.canonical.vaultRelPath;\n const inbound = inboundMap.get(path) ?? 0;\n const outbound = outboundMap.get(path) ?? 0;\n const dead = deadMap.get(path) ?? 0;\n const isOrphan = inbound === 0;\n if (isOrphan) orphanCount++;\n return {\n vaultPath: path,\n inboundCount: inbound,\n outboundCount: outbound,\n deadLinkCount: dead,\n isOrphan,\n computedAt,\n };\n });\n await backend.upsertVaultHealth(healthRows);\n await yieldToEventLoop();\n }\n\n result.orphansFound = orphanCount;\n result.elapsed = Date.now() - startTime;\n\n return result;\n}\n"],"mappings":"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"}
@@ -1,11 +0,0 @@
1
- import "./utils-C9HsYDpO.mjs";
2
- import "./migrate-BD7D8EEh.mjs";
3
- import "./factory-BozZa81q.mjs";
4
- import "./context-handover-cache-DFNnlWv2.mjs";
5
- import "./config-CQCmH_gV.mjs";
6
- import "./run-env-BE5xyCwy.mjs";
7
- import "./server-BY6AedmC.mjs";
8
- import "./state-n13uPK5B.mjs";
9
- import { i as stopWorker, n as notifyNewWork, r as startWorker, t as enqueueRegistryScan } from "./work-queue-worker-BbrC3I_R.mjs";
10
-
11
- export { enqueueRegistryScan };