@tekmidian/pai 0.65.1 → 0.66.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 (115) hide show
  1. package/dist/{async-CNn36gm4.mjs → async-C2Bm_Lal.mjs} +2 -2
  2. package/dist/{async-CNn36gm4.mjs.map → async-C2Bm_Lal.mjs.map} +1 -1
  3. package/dist/{auto-route-sLMU-NnM.mjs → auto-route-o0BOsXn7.mjs} +2 -2
  4. package/dist/{auto-route-sLMU-NnM.mjs.map → auto-route-o0BOsXn7.mjs.map} +1 -1
  5. package/dist/{chain-CLb6hbFS.mjs → chain-D4PuQ2c-.mjs} +4 -4
  6. package/dist/{chain-CLb6hbFS.mjs.map → chain-D4PuQ2c-.mjs.map} +1 -1
  7. package/dist/cli/index.mjs +14 -12
  8. package/dist/cli/index.mjs.map +1 -1
  9. package/dist/cli/program.mjs +12 -12
  10. package/dist/{clusters-PgIUYT_v.mjs → clusters-BCtD3fbe.mjs} +1 -1
  11. package/dist/{clusters-PgIUYT_v.mjs.map → clusters-BCtD3fbe.mjs.map} +1 -1
  12. package/dist/{context-handover-cache-mGxq2-8f.mjs → context-handover-cache-pkHzmL3c.mjs} +4 -4
  13. package/dist/{context-handover-cache-mGxq2-8f.mjs.map → context-handover-cache-pkHzmL3c.mjs.map} +1 -1
  14. package/dist/daemon/index.mjs +14 -12
  15. package/dist/daemon/index.mjs.map +1 -1
  16. package/dist/{daemon-DqCB3fO-.mjs → daemon-BEoVgVfJ.mjs} +25 -25
  17. package/dist/{daemon-DqCB3fO-.mjs.map → daemon-BEoVgVfJ.mjs.map} +1 -1
  18. package/dist/daemon-DPAYU9hp.mjs +19 -0
  19. package/dist/daemon-mcp/index.mjs +9 -7
  20. package/dist/daemon-mcp/index.mjs.map +1 -1
  21. package/dist/detector-BEPFsINR.mjs +3 -0
  22. package/dist/{detector-CIHEXKsV.mjs → detector-BRTtAWrM.mjs} +1 -1
  23. package/dist/{detector-CIHEXKsV.mjs.map → detector-BRTtAWrM.mjs.map} +1 -1
  24. package/dist/{embeddings-BbNVXa_0.mjs → embeddings-Bx3q0QOY.mjs} +1 -1
  25. package/dist/{embeddings-BbNVXa_0.mjs.map → embeddings-Bx3q0QOY.mjs.map} +1 -1
  26. package/dist/{embeddings-CDfM63uC.mjs → embeddings-CcscYWwk.mjs} +1 -1
  27. package/dist/env-JNEIrQWg.mjs +14 -0
  28. package/dist/env-JNEIrQWg.mjs.map +1 -0
  29. package/dist/{factory-y36qGegI.mjs → factory-CNpPQQ-5.mjs} +7 -7
  30. package/dist/{factory-y36qGegI.mjs.map → factory-CNpPQQ-5.mjs.map} +1 -1
  31. package/dist/{factory-CaswxuJ0.mjs → factory-DY7x73mE.mjs} +1 -1
  32. package/dist/{fallback-CupzGkuJ.mjs → fallback-B5svk75f.mjs} +15 -7
  33. package/dist/fallback-B5svk75f.mjs.map +1 -0
  34. package/dist/hooks/block-sleep-poll.mjs.map +2 -2
  35. package/dist/hooks/load-project-context.mjs +16 -0
  36. package/dist/hooks/load-project-context.mjs.map +2 -2
  37. package/dist/hooks/route-agents-to-worker.mjs +16 -0
  38. package/dist/hooks/route-agents-to-worker.mjs.map +2 -2
  39. package/dist/hooks/security-validator.mjs +16 -0
  40. package/dist/hooks/security-validator.mjs.map +2 -2
  41. package/dist/hooks/worker-guard.mjs +10 -0
  42. package/dist/hooks/worker-guard.mjs.map +3 -3
  43. package/dist/hooks/worker-proxy.mjs +16 -0
  44. package/dist/hooks/worker-proxy.mjs.map +2 -2
  45. package/dist/hooks/worker-status-line.mjs +16 -0
  46. package/dist/hooks/worker-status-line.mjs.map +2 -2
  47. package/dist/hooks/worker-supervision.mjs +16 -0
  48. package/dist/hooks/worker-supervision.mjs.map +2 -2
  49. package/dist/{indexer-backend-Cnc7Tf5C.mjs → indexer-backend-DFF2FrYx.mjs} +1 -1
  50. package/dist/{kg-entity-COTUj1ZC.mjs → kg-entity-DCOcsVFD.mjs} +1 -1
  51. package/dist/{kg-entity-COTUj1ZC.mjs.map → kg-entity-DCOcsVFD.mjs.map} +1 -1
  52. package/dist/{latent-ideas-ByvMjVcb.mjs → latent-ideas-wwAXquGe.mjs} +3 -3
  53. package/dist/{latent-ideas-ByvMjVcb.mjs.map → latent-ideas-wwAXquGe.mjs.map} +1 -1
  54. package/dist/{link-boost-UiE-uooT.mjs → link-boost-CnI7UVMJ.mjs} +1 -1
  55. package/dist/{link-boost-UiE-uooT.mjs.map → link-boost-CnI7UVMJ.mjs.map} +1 -1
  56. package/dist/{main-resolver-kHW6FewU.mjs → main-resolver-BKz_OuEh.mjs} +2 -2
  57. package/dist/{main-resolver-BeYWNzrt.mjs → main-resolver-DPPwHNtn.mjs} +4 -4
  58. package/dist/{main-resolver-BeYWNzrt.mjs.map → main-resolver-DPPwHNtn.mjs.map} +1 -1
  59. package/dist/{neighborhood-CvRHlqdR.mjs → neighborhood-2FsmzoxG.mjs} +2 -2
  60. package/dist/{neighborhood-CvRHlqdR.mjs.map → neighborhood-2FsmzoxG.mjs.map} +1 -1
  61. package/dist/{note-context-BrbfUIoP.mjs → note-context-De63k8na.mjs} +1 -1
  62. package/dist/{note-context-BrbfUIoP.mjs.map → note-context-De63k8na.mjs.map} +1 -1
  63. package/dist/{planner-DAq4Yx-H.mjs → planner-EJPZnnif.mjs} +6 -6
  64. package/dist/{planner-DAq4Yx-H.mjs.map → planner-EJPZnnif.mjs.map} +1 -1
  65. package/dist/{postgres-DYtZg7J7.mjs → postgres-Ceqsa64C.mjs} +3 -3
  66. package/dist/{postgres-DYtZg7J7.mjs.map → postgres-Ceqsa64C.mjs.map} +1 -1
  67. package/dist/{program-CWf9mT7Z.mjs → program-DSETzrWg.mjs} +91 -51
  68. package/dist/{program-CWf9mT7Z.mjs.map → program-DSETzrWg.mjs.map} +1 -1
  69. package/dist/{registry-postgres-DSrkxqfF.mjs → registry-postgres-nOfyBSIW.mjs} +1 -1
  70. package/dist/{registry-postgres-DSrkxqfF.mjs.map → registry-postgres-nOfyBSIW.mjs.map} +1 -1
  71. package/dist/{registry-sqlite-B2436JgX.mjs → registry-sqlite-DrCW1aRK.mjs} +1 -1
  72. package/dist/{registry-sqlite-B2436JgX.mjs.map → registry-sqlite-DrCW1aRK.mjs.map} +1 -1
  73. package/dist/router-CXUGsv85.mjs +3 -0
  74. package/dist/{router-aqLMvNMg.mjs → router-a5G7q8_3.mjs} +1 -1
  75. package/dist/{router-aqLMvNMg.mjs.map → router-a5G7q8_3.mjs.map} +1 -1
  76. package/dist/{run-uAkNItb6.mjs → run-Z4akoa8G.mjs} +326 -24
  77. package/dist/run-Z4akoa8G.mjs.map +1 -0
  78. package/dist/{run-env-BHWnXqle.mjs → run-env-DRcp7A8K.mjs} +16 -2
  79. package/dist/run-env-DRcp7A8K.mjs.map +1 -0
  80. package/dist/{server-DlL1QI2k.mjs → server-DgmAHyFK.mjs} +2 -2
  81. package/dist/{server-DlL1QI2k.mjs.map → server-DgmAHyFK.mjs.map} +1 -1
  82. package/dist/{session-keepalive-BWEjcRrh.mjs → session-keepalive-BDhytXEo.mjs} +4 -4
  83. package/dist/{session-keepalive-BWEjcRrh.mjs.map → session-keepalive-BDhytXEo.mjs.map} +1 -1
  84. package/dist/{sources-DX4ElmrE.mjs → sources-CijVso3n.mjs} +1 -1
  85. package/dist/{sources-DX4ElmrE.mjs.map → sources-CijVso3n.mjs.map} +1 -1
  86. package/dist/{sqlite-BrEu3avy.mjs → sqlite-CqsTy6xo.mjs} +3 -3
  87. package/dist/{sqlite-BrEu3avy.mjs.map → sqlite-CqsTy6xo.mjs.map} +1 -1
  88. package/dist/{themes-BgqahYVM.mjs → themes-CTaOj3e1.mjs} +2 -2
  89. package/dist/{themes-BgqahYVM.mjs.map → themes-CTaOj3e1.mjs.map} +1 -1
  90. package/dist/{tools-gMGDIpJ9.mjs → tools-BcOKPBtg.mjs} +16 -16
  91. package/dist/{tools-gMGDIpJ9.mjs.map → tools-BcOKPBtg.mjs.map} +1 -1
  92. package/dist/{tools-BbqQIHPe.mjs → tools-C-_l0o8Z.mjs} +4 -4
  93. package/dist/{trace-CmAB7iJZ.mjs → trace-CtV0RO6n.mjs} +1 -1
  94. package/dist/{trace-CmAB7iJZ.mjs.map → trace-CtV0RO6n.mjs.map} +1 -1
  95. package/dist/{vault-indexer-Ddq51X20.mjs → vault-indexer-BG1sEVyM.mjs} +1 -1
  96. package/dist/{vault-indexer-Ddq51X20.mjs.map → vault-indexer-BG1sEVyM.mjs.map} +1 -1
  97. package/dist/{wakeup-CZw88uXf.mjs → wakeup-D8n33hYV.mjs} +2 -2
  98. package/dist/{wakeup-CZw88uXf.mjs.map → wakeup-D8n33hYV.mjs.map} +1 -1
  99. package/dist/{work-queue-worker-DCzfH0d1.mjs → work-queue-worker-4tLa5gpQ.mjs} +3 -3
  100. package/dist/{work-queue-worker-DCzfH0d1.mjs.map → work-queue-worker-4tLa5gpQ.mjs.map} +1 -1
  101. package/dist/work-queue-worker-rFZvTO3p.mjs +11 -0
  102. package/dist/{zettelkasten-Dwj67e4p.mjs → zettelkasten-CtGHQWPU.mjs} +3 -3
  103. package/dist/{zettelkasten-Dwj67e4p.mjs.map → zettelkasten-CtGHQWPU.mjs.map} +1 -1
  104. package/docs/commands/README.md +1 -0
  105. package/docs/commands/worker.md +30 -0
  106. package/package.json +1 -1
  107. package/src/hooks/ts/lib/worker-guard.ts +4 -0
  108. package/statusline-command.sh +1 -1
  109. package/dist/daemon-BjaPR39W.mjs +0 -19
  110. package/dist/detector-C3Q7mxQU.mjs +0 -3
  111. package/dist/fallback-CupzGkuJ.mjs.map +0 -1
  112. package/dist/router-S6C5BxzZ.mjs +0 -3
  113. package/dist/run-env-BHWnXqle.mjs.map +0 -1
  114. package/dist/run-uAkNItb6.mjs.map +0 -1
  115. package/dist/work-queue-worker-Taba_vcA.mjs +0 -11
@@ -1 +1 @@
1
- {"version":3,"file":"sqlite-BrEu3avy.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, fileContentHash } 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 = fileContentHash(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
+ {"version":3,"file":"sqlite-CqsTy6xo.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, fileContentHash } 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 = fileContentHash(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,5 +1,5 @@
1
1
  import { t as STOP_WORDS } from "./stop-words-BdQuaE9K.mjs";
2
- import { n as cosineSimilarity, r as deserializeEmbedding } from "./embeddings-BbNVXa_0.mjs";
2
+ import { n as cosineSimilarity, r as deserializeEmbedding } from "./embeddings-Bx3q0QOY.mjs";
3
3
 
4
4
  //#region src/zettelkasten/themes.ts
5
5
  const MAX_CHUNKS = 5e3;
@@ -145,4 +145,4 @@ async function zettelThemes(backend, opts) {
145
145
 
146
146
  //#endregion
147
147
  export { zettelThemes as t };
148
- //# sourceMappingURL=themes-BgqahYVM.mjs.map
148
+ //# sourceMappingURL=themes-CTaOj3e1.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"themes-BgqahYVM.mjs","names":[],"sources":["../src/zettelkasten/themes.ts"],"sourcesContent":["import type { StorageBackend } from \"../storage/interface.js\";\nimport { deserializeEmbedding, cosineSimilarity } from \"../memory/embeddings.js\";\nimport { STOP_WORDS } from \"../utils/stop-words.js\";\n\nexport interface ThemeOptions {\n vaultProjectId: number;\n lookbackDays?: number;\n minClusterSize?: number;\n maxThemes?: number;\n similarityThreshold?: number;\n}\n\nexport interface ThemeCluster {\n id: number;\n label: string;\n notes: Array<{\n path: string;\n title: string | null;\n }>;\n size: number;\n folderDiversity: number;\n avgRecency: number;\n linkedRatio: number;\n suggestIndexNote: boolean;\n}\n\nexport interface ThemeResult {\n themes: ThemeCluster[];\n totalNotesAnalyzed: number;\n timeWindow: { from: number; to: number };\n}\n\nconst MAX_CHUNKS = 5000;\n\n// STOP_WORDS imported from utils/stop-words.ts\n\nfunction getTopFolder(vaultPath: string): string {\n const parts = vaultPath.split(\"/\");\n return parts.length > 1 ? parts[0] : \"\";\n}\n\nfunction generateLabel(titles: Array<string | null>): string {\n const wordCounts = new Map<string, number>();\n for (const title of titles) {\n if (!title) continue;\n const words = title\n .toLowerCase()\n .replace(/[^a-z0-9\\s]/g, \" \")\n .split(/\\s+/)\n .filter((w) => w.length > 2 && !STOP_WORDS.has(w));\n for (const word of words) {\n wordCounts.set(word, (wordCounts.get(word) ?? 0) + 1);\n }\n }\n const sorted = [...wordCounts.entries()].sort((a, b) => b[1] - a[1]);\n return sorted\n .slice(0, 3)\n .map(([w]) => w)\n .join(\" / \");\n}\n\nasync function computeLinkedRatio(backend: StorageBackend, paths: string[]): Promise<number> {\n if (paths.length < 2) return 0;\n const totalPairs = (paths.length * (paths.length - 1)) / 2;\n const pathSet = new Set(paths);\n let linkedPairs = 0;\n\n for (const path of paths) {\n const links = await backend.getLinksFromSource(path);\n for (const link of links) {\n if (link.targetPath && pathSet.has(link.targetPath)) {\n linkedPairs++;\n }\n }\n }\n\n // Each bidirectional pair might be counted once per direction; divide by 2 to normalize\n const uniquePairs = linkedPairs / 2;\n return Math.min(1, uniquePairs / totalPairs);\n}\n\ntype ClusterNode = {\n paths: string[];\n titles: Array<string | null>;\n indexedAts: number[];\n centroid: Float32Array;\n};\n\nfunction averageEmbeddings(embeddings: Float32Array[]): Float32Array {\n if (embeddings.length === 0) return new Float32Array(0);\n const dim = embeddings[0].length;\n const sum = new Float32Array(dim);\n for (const vec of embeddings) {\n for (let i = 0; i < dim; i++) {\n sum[i] += vec[i];\n }\n }\n const avg = new Float32Array(dim);\n for (let i = 0; i < dim; i++) {\n avg[i] = sum[i] / embeddings.length;\n }\n return avg;\n}\n\n/**\n * Detect emerging themes in recently-modified notes using agglomerative single-linkage\n * clustering of note-level embeddings.\n */\nexport async function zettelThemes(\n backend: StorageBackend,\n opts: ThemeOptions,\n): Promise<ThemeResult> {\n const lookbackDays = opts.lookbackDays ?? 30;\n const minClusterSize = opts.minClusterSize ?? 3;\n const maxThemes = opts.maxThemes ?? 10;\n const similarityThreshold = opts.similarityThreshold ?? 0.65;\n\n const now = Date.now();\n const from = now - lookbackDays * 86400000;\n\n // Step 1: get recent notes\n const recentFiles = await backend.getRecentVaultFiles(from);\n const recentNotes = recentFiles.map(f => ({ vault_path: f.vaultPath, title: f.title, indexed_at: f.indexedAt }));\n\n // Step 2: get file-level embeddings from memory_chunks\n const chunkRows = await backend.getChunksWithEmbeddings(opts.vaultProjectId, MAX_CHUNKS);\n\n const embeddingsByPath = new Map<string, Float32Array[]>();\n for (const row of chunkRows) {\n const vec = deserializeEmbedding(row.embedding);\n const arr = embeddingsByPath.get(row.path);\n if (!arr) {\n embeddingsByPath.set(row.path, [vec]);\n } else {\n arr.push(vec);\n }\n }\n\n const fileEmbeddings = new Map<string, Float32Array>();\n for (const [path, vecs] of embeddingsByPath) {\n fileEmbeddings.set(path, averageEmbeddings(vecs));\n }\n\n // Step 3: build initial clusters — only include notes that have embeddings\n const clusters: ClusterNode[] = [];\n for (const note of recentNotes) {\n const embedding = fileEmbeddings.get(note.vault_path);\n if (!embedding) continue;\n clusters.push({\n paths: [note.vault_path],\n titles: [note.title],\n indexedAts: [note.indexed_at],\n centroid: embedding,\n });\n }\n\n const totalNotesAnalyzed = clusters.length;\n\n // Step 4: agglomerative single-linkage clustering\n // Stop when no two clusters have similarity >= threshold\n let merged = true;\n while (merged && clusters.length > 1) {\n merged = false;\n let bestSim = similarityThreshold;\n let bestI = -1;\n let bestJ = -1;\n\n for (let i = 0; i < clusters.length; i++) {\n for (let j = i + 1; j < clusters.length; j++) {\n const sim = cosineSimilarity(clusters[i].centroid, clusters[j].centroid);\n if (sim > bestSim) {\n bestSim = sim;\n bestI = i;\n bestJ = j;\n }\n }\n }\n\n if (bestI === -1) break;\n\n // Merge cluster j into cluster i\n const ci = clusters[bestI];\n const cj = clusters[bestJ];\n const mergedPaths = [...ci.paths, ...cj.paths];\n const mergedTitles = [...ci.titles, ...cj.titles];\n const mergedIndexedAts = [...ci.indexedAts, ...cj.indexedAts];\n\n // Recompute centroid from averaged embeddings of all member paths\n const memberEmbeddings: Float32Array[] = [];\n for (const p of mergedPaths) {\n const emb = fileEmbeddings.get(p);\n if (emb) memberEmbeddings.push(emb);\n }\n\n clusters[bestI] = {\n paths: mergedPaths,\n titles: mergedTitles,\n indexedAts: mergedIndexedAts,\n centroid: averageEmbeddings(memberEmbeddings),\n };\n\n clusters.splice(bestJ, 1);\n merged = true;\n }\n\n // Step 5: filter and annotate clusters\n const themes: ThemeCluster[] = [];\n let clusterIndex = 0;\n\n for (const cluster of clusters) {\n if (cluster.paths.length < minClusterSize) continue;\n\n const label = generateLabel(cluster.titles) || `Theme ${clusterIndex + 1}`;\n const avgRecency =\n cluster.indexedAts.reduce((sum, t) => sum + t, 0) / cluster.indexedAts.length;\n\n const uniqueFolders = new Set(cluster.paths.map(getTopFolder));\n const folderDiversity = uniqueFolders.size / cluster.paths.length;\n\n const linkedRatio = await computeLinkedRatio(backend, cluster.paths);\n const suggestIndexNote = linkedRatio < 0.3 && cluster.paths.length >= 5;\n\n themes.push({\n id: clusterIndex++,\n label,\n notes: cluster.paths.map((path, idx) => ({\n path,\n title: cluster.titles[idx],\n })),\n size: cluster.paths.length,\n folderDiversity,\n avgRecency,\n linkedRatio,\n suggestIndexNote,\n });\n }\n\n // Step 6: rank by size * folderDiversity * recency_ratio\n themes.sort(\n (a, b) =>\n b.size * b.folderDiversity * (b.avgRecency / now) -\n a.size * a.folderDiversity * (a.avgRecency / now),\n );\n\n return {\n themes: themes.slice(0, maxThemes),\n totalNotesAnalyzed,\n timeWindow: { from, to: now },\n };\n}\n"],"mappings":";;;;AAgCA,MAAM,aAAa;AAInB,SAAS,aAAa,WAA2B;CAC/C,MAAM,QAAQ,UAAU,MAAM,IAAI;AAClC,QAAO,MAAM,SAAS,IAAI,MAAM,KAAK;;AAGvC,SAAS,cAAc,QAAsC;CAC3D,MAAM,6BAAa,IAAI,KAAqB;AAC5C,MAAK,MAAM,SAAS,QAAQ;AAC1B,MAAI,CAAC,MAAO;EACZ,MAAM,QAAQ,MACX,aAAa,CACb,QAAQ,gBAAgB,IAAI,CAC5B,MAAM,MAAM,CACZ,QAAQ,MAAM,EAAE,SAAS,KAAK,CAAC,WAAW,IAAI,EAAE,CAAC;AACpD,OAAK,MAAM,QAAQ,MACjB,YAAW,IAAI,OAAO,WAAW,IAAI,KAAK,IAAI,KAAK,EAAE;;AAIzD,QADe,CAAC,GAAG,WAAW,SAAS,CAAC,CAAC,MAAM,GAAG,MAAM,EAAE,KAAK,EAAE,GAAG,CAEjE,MAAM,GAAG,EAAE,CACX,KAAK,CAAC,OAAO,EAAE,CACf,KAAK,MAAM;;AAGhB,eAAe,mBAAmB,SAAyB,OAAkC;AAC3F,KAAI,MAAM,SAAS,EAAG,QAAO;CAC7B,MAAM,aAAc,MAAM,UAAU,MAAM,SAAS,KAAM;CACzD,MAAM,UAAU,IAAI,IAAI,MAAM;CAC9B,IAAI,cAAc;AAElB,MAAK,MAAM,QAAQ,OAAO;EACxB,MAAM,QAAQ,MAAM,QAAQ,mBAAmB,KAAK;AACpD,OAAK,MAAM,QAAQ,MACjB,KAAI,KAAK,cAAc,QAAQ,IAAI,KAAK,WAAW,CACjD;;CAMN,MAAM,cAAc,cAAc;AAClC,QAAO,KAAK,IAAI,GAAG,cAAc,WAAW;;AAU9C,SAAS,kBAAkB,YAA0C;AACnE,KAAI,WAAW,WAAW,EAAG,QAAO,IAAI,aAAa,EAAE;CACvD,MAAM,MAAM,WAAW,GAAG;CAC1B,MAAM,MAAM,IAAI,aAAa,IAAI;AACjC,MAAK,MAAM,OAAO,WAChB,MAAK,IAAI,IAAI,GAAG,IAAI,KAAK,IACvB,KAAI,MAAM,IAAI;CAGlB,MAAM,MAAM,IAAI,aAAa,IAAI;AACjC,MAAK,IAAI,IAAI,GAAG,IAAI,KAAK,IACvB,KAAI,KAAK,IAAI,KAAK,WAAW;AAE/B,QAAO;;;;;;AAOT,eAAsB,aACpB,SACA,MACsB;CACtB,MAAM,eAAe,KAAK,gBAAgB;CAC1C,MAAM,iBAAiB,KAAK,kBAAkB;CAC9C,MAAM,YAAY,KAAK,aAAa;CACpC,MAAM,sBAAsB,KAAK,uBAAuB;CAExD,MAAM,MAAM,KAAK,KAAK;CACtB,MAAM,OAAO,MAAM,eAAe;CAIlC,MAAM,eADc,MAAM,QAAQ,oBAAoB,KAAK,EAC3B,KAAI,OAAM;EAAE,YAAY,EAAE;EAAW,OAAO,EAAE;EAAO,YAAY,EAAE;EAAW,EAAE;CAGhH,MAAM,YAAY,MAAM,QAAQ,wBAAwB,KAAK,gBAAgB,WAAW;CAExF,MAAM,mCAAmB,IAAI,KAA6B;AAC1D,MAAK,MAAM,OAAO,WAAW;EAC3B,MAAM,MAAM,qBAAqB,IAAI,UAAU;EAC/C,MAAM,MAAM,iBAAiB,IAAI,IAAI,KAAK;AAC1C,MAAI,CAAC,IACH,kBAAiB,IAAI,IAAI,MAAM,CAAC,IAAI,CAAC;MAErC,KAAI,KAAK,IAAI;;CAIjB,MAAM,iCAAiB,IAAI,KAA2B;AACtD,MAAK,MAAM,CAAC,MAAM,SAAS,iBACzB,gBAAe,IAAI,MAAM,kBAAkB,KAAK,CAAC;CAInD,MAAM,WAA0B,EAAE;AAClC,MAAK,MAAM,QAAQ,aAAa;EAC9B,MAAM,YAAY,eAAe,IAAI,KAAK,WAAW;AACrD,MAAI,CAAC,UAAW;AAChB,WAAS,KAAK;GACZ,OAAO,CAAC,KAAK,WAAW;GACxB,QAAQ,CAAC,KAAK,MAAM;GACpB,YAAY,CAAC,KAAK,WAAW;GAC7B,UAAU;GACX,CAAC;;CAGJ,MAAM,qBAAqB,SAAS;CAIpC,IAAI,SAAS;AACb,QAAO,UAAU,SAAS,SAAS,GAAG;AACpC,WAAS;EACT,IAAI,UAAU;EACd,IAAI,QAAQ;EACZ,IAAI,QAAQ;AAEZ,OAAK,IAAI,IAAI,GAAG,IAAI,SAAS,QAAQ,IACnC,MAAK,IAAI,IAAI,IAAI,GAAG,IAAI,SAAS,QAAQ,KAAK;GAC5C,MAAM,MAAM,iBAAiB,SAAS,GAAG,UAAU,SAAS,GAAG,SAAS;AACxE,OAAI,MAAM,SAAS;AACjB,cAAU;AACV,YAAQ;AACR,YAAQ;;;AAKd,MAAI,UAAU,GAAI;EAGlB,MAAM,KAAK,SAAS;EACpB,MAAM,KAAK,SAAS;EACpB,MAAM,cAAc,CAAC,GAAG,GAAG,OAAO,GAAG,GAAG,MAAM;EAC9C,MAAM,eAAe,CAAC,GAAG,GAAG,QAAQ,GAAG,GAAG,OAAO;EACjD,MAAM,mBAAmB,CAAC,GAAG,GAAG,YAAY,GAAG,GAAG,WAAW;EAG7D,MAAM,mBAAmC,EAAE;AAC3C,OAAK,MAAM,KAAK,aAAa;GAC3B,MAAM,MAAM,eAAe,IAAI,EAAE;AACjC,OAAI,IAAK,kBAAiB,KAAK,IAAI;;AAGrC,WAAS,SAAS;GAChB,OAAO;GACP,QAAQ;GACR,YAAY;GACZ,UAAU,kBAAkB,iBAAiB;GAC9C;AAED,WAAS,OAAO,OAAO,EAAE;AACzB,WAAS;;CAIX,MAAM,SAAyB,EAAE;CACjC,IAAI,eAAe;AAEnB,MAAK,MAAM,WAAW,UAAU;AAC9B,MAAI,QAAQ,MAAM,SAAS,eAAgB;EAE3C,MAAM,QAAQ,cAAc,QAAQ,OAAO,IAAI,SAAS,eAAe;EACvE,MAAM,aACJ,QAAQ,WAAW,QAAQ,KAAK,MAAM,MAAM,GAAG,EAAE,GAAG,QAAQ,WAAW;EAGzE,MAAM,kBADgB,IAAI,IAAI,QAAQ,MAAM,IAAI,aAAa,CAAC,CACxB,OAAO,QAAQ,MAAM;EAE3D,MAAM,cAAc,MAAM,mBAAmB,SAAS,QAAQ,MAAM;EACpE,MAAM,mBAAmB,cAAc,MAAO,QAAQ,MAAM,UAAU;AAEtE,SAAO,KAAK;GACV,IAAI;GACJ;GACA,OAAO,QAAQ,MAAM,KAAK,MAAM,SAAS;IACvC;IACA,OAAO,QAAQ,OAAO;IACvB,EAAE;GACH,MAAM,QAAQ,MAAM;GACpB;GACA;GACA;GACA;GACD,CAAC;;AAIJ,QAAO,MACJ,GAAG,MACF,EAAE,OAAO,EAAE,mBAAmB,EAAE,aAAa,OAC7C,EAAE,OAAO,EAAE,mBAAmB,EAAE,aAAa,KAChD;AAED,QAAO;EACL,QAAQ,OAAO,MAAM,GAAG,UAAU;EAClC;EACA,YAAY;GAAE;GAAM,IAAI;GAAK;EAC9B"}
1
+ {"version":3,"file":"themes-CTaOj3e1.mjs","names":[],"sources":["../src/zettelkasten/themes.ts"],"sourcesContent":["import type { StorageBackend } from \"../storage/interface.js\";\nimport { deserializeEmbedding, cosineSimilarity } from \"../memory/embeddings.js\";\nimport { STOP_WORDS } from \"../utils/stop-words.js\";\n\nexport interface ThemeOptions {\n vaultProjectId: number;\n lookbackDays?: number;\n minClusterSize?: number;\n maxThemes?: number;\n similarityThreshold?: number;\n}\n\nexport interface ThemeCluster {\n id: number;\n label: string;\n notes: Array<{\n path: string;\n title: string | null;\n }>;\n size: number;\n folderDiversity: number;\n avgRecency: number;\n linkedRatio: number;\n suggestIndexNote: boolean;\n}\n\nexport interface ThemeResult {\n themes: ThemeCluster[];\n totalNotesAnalyzed: number;\n timeWindow: { from: number; to: number };\n}\n\nconst MAX_CHUNKS = 5000;\n\n// STOP_WORDS imported from utils/stop-words.ts\n\nfunction getTopFolder(vaultPath: string): string {\n const parts = vaultPath.split(\"/\");\n return parts.length > 1 ? parts[0] : \"\";\n}\n\nfunction generateLabel(titles: Array<string | null>): string {\n const wordCounts = new Map<string, number>();\n for (const title of titles) {\n if (!title) continue;\n const words = title\n .toLowerCase()\n .replace(/[^a-z0-9\\s]/g, \" \")\n .split(/\\s+/)\n .filter((w) => w.length > 2 && !STOP_WORDS.has(w));\n for (const word of words) {\n wordCounts.set(word, (wordCounts.get(word) ?? 0) + 1);\n }\n }\n const sorted = [...wordCounts.entries()].sort((a, b) => b[1] - a[1]);\n return sorted\n .slice(0, 3)\n .map(([w]) => w)\n .join(\" / \");\n}\n\nasync function computeLinkedRatio(backend: StorageBackend, paths: string[]): Promise<number> {\n if (paths.length < 2) return 0;\n const totalPairs = (paths.length * (paths.length - 1)) / 2;\n const pathSet = new Set(paths);\n let linkedPairs = 0;\n\n for (const path of paths) {\n const links = await backend.getLinksFromSource(path);\n for (const link of links) {\n if (link.targetPath && pathSet.has(link.targetPath)) {\n linkedPairs++;\n }\n }\n }\n\n // Each bidirectional pair might be counted once per direction; divide by 2 to normalize\n const uniquePairs = linkedPairs / 2;\n return Math.min(1, uniquePairs / totalPairs);\n}\n\ntype ClusterNode = {\n paths: string[];\n titles: Array<string | null>;\n indexedAts: number[];\n centroid: Float32Array;\n};\n\nfunction averageEmbeddings(embeddings: Float32Array[]): Float32Array {\n if (embeddings.length === 0) return new Float32Array(0);\n const dim = embeddings[0].length;\n const sum = new Float32Array(dim);\n for (const vec of embeddings) {\n for (let i = 0; i < dim; i++) {\n sum[i] += vec[i];\n }\n }\n const avg = new Float32Array(dim);\n for (let i = 0; i < dim; i++) {\n avg[i] = sum[i] / embeddings.length;\n }\n return avg;\n}\n\n/**\n * Detect emerging themes in recently-modified notes using agglomerative single-linkage\n * clustering of note-level embeddings.\n */\nexport async function zettelThemes(\n backend: StorageBackend,\n opts: ThemeOptions,\n): Promise<ThemeResult> {\n const lookbackDays = opts.lookbackDays ?? 30;\n const minClusterSize = opts.minClusterSize ?? 3;\n const maxThemes = opts.maxThemes ?? 10;\n const similarityThreshold = opts.similarityThreshold ?? 0.65;\n\n const now = Date.now();\n const from = now - lookbackDays * 86400000;\n\n // Step 1: get recent notes\n const recentFiles = await backend.getRecentVaultFiles(from);\n const recentNotes = recentFiles.map(f => ({ vault_path: f.vaultPath, title: f.title, indexed_at: f.indexedAt }));\n\n // Step 2: get file-level embeddings from memory_chunks\n const chunkRows = await backend.getChunksWithEmbeddings(opts.vaultProjectId, MAX_CHUNKS);\n\n const embeddingsByPath = new Map<string, Float32Array[]>();\n for (const row of chunkRows) {\n const vec = deserializeEmbedding(row.embedding);\n const arr = embeddingsByPath.get(row.path);\n if (!arr) {\n embeddingsByPath.set(row.path, [vec]);\n } else {\n arr.push(vec);\n }\n }\n\n const fileEmbeddings = new Map<string, Float32Array>();\n for (const [path, vecs] of embeddingsByPath) {\n fileEmbeddings.set(path, averageEmbeddings(vecs));\n }\n\n // Step 3: build initial clusters — only include notes that have embeddings\n const clusters: ClusterNode[] = [];\n for (const note of recentNotes) {\n const embedding = fileEmbeddings.get(note.vault_path);\n if (!embedding) continue;\n clusters.push({\n paths: [note.vault_path],\n titles: [note.title],\n indexedAts: [note.indexed_at],\n centroid: embedding,\n });\n }\n\n const totalNotesAnalyzed = clusters.length;\n\n // Step 4: agglomerative single-linkage clustering\n // Stop when no two clusters have similarity >= threshold\n let merged = true;\n while (merged && clusters.length > 1) {\n merged = false;\n let bestSim = similarityThreshold;\n let bestI = -1;\n let bestJ = -1;\n\n for (let i = 0; i < clusters.length; i++) {\n for (let j = i + 1; j < clusters.length; j++) {\n const sim = cosineSimilarity(clusters[i].centroid, clusters[j].centroid);\n if (sim > bestSim) {\n bestSim = sim;\n bestI = i;\n bestJ = j;\n }\n }\n }\n\n if (bestI === -1) break;\n\n // Merge cluster j into cluster i\n const ci = clusters[bestI];\n const cj = clusters[bestJ];\n const mergedPaths = [...ci.paths, ...cj.paths];\n const mergedTitles = [...ci.titles, ...cj.titles];\n const mergedIndexedAts = [...ci.indexedAts, ...cj.indexedAts];\n\n // Recompute centroid from averaged embeddings of all member paths\n const memberEmbeddings: Float32Array[] = [];\n for (const p of mergedPaths) {\n const emb = fileEmbeddings.get(p);\n if (emb) memberEmbeddings.push(emb);\n }\n\n clusters[bestI] = {\n paths: mergedPaths,\n titles: mergedTitles,\n indexedAts: mergedIndexedAts,\n centroid: averageEmbeddings(memberEmbeddings),\n };\n\n clusters.splice(bestJ, 1);\n merged = true;\n }\n\n // Step 5: filter and annotate clusters\n const themes: ThemeCluster[] = [];\n let clusterIndex = 0;\n\n for (const cluster of clusters) {\n if (cluster.paths.length < minClusterSize) continue;\n\n const label = generateLabel(cluster.titles) || `Theme ${clusterIndex + 1}`;\n const avgRecency =\n cluster.indexedAts.reduce((sum, t) => sum + t, 0) / cluster.indexedAts.length;\n\n const uniqueFolders = new Set(cluster.paths.map(getTopFolder));\n const folderDiversity = uniqueFolders.size / cluster.paths.length;\n\n const linkedRatio = await computeLinkedRatio(backend, cluster.paths);\n const suggestIndexNote = linkedRatio < 0.3 && cluster.paths.length >= 5;\n\n themes.push({\n id: clusterIndex++,\n label,\n notes: cluster.paths.map((path, idx) => ({\n path,\n title: cluster.titles[idx],\n })),\n size: cluster.paths.length,\n folderDiversity,\n avgRecency,\n linkedRatio,\n suggestIndexNote,\n });\n }\n\n // Step 6: rank by size * folderDiversity * recency_ratio\n themes.sort(\n (a, b) =>\n b.size * b.folderDiversity * (b.avgRecency / now) -\n a.size * a.folderDiversity * (a.avgRecency / now),\n );\n\n return {\n themes: themes.slice(0, maxThemes),\n totalNotesAnalyzed,\n timeWindow: { from, to: now },\n };\n}\n"],"mappings":"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"}
@@ -1,7 +1,7 @@
1
1
  import { t as buildOutline } from "./chunker-BH4i-F2b.mjs";
2
2
  import { i as populateSlugs } from "./search-BOSphCJ1.mjs";
3
- import { a as formatDetectionJson, r as detectProject, t as buildWakeupContext } from "./wakeup-CZw88uXf.mjs";
4
- import { n as cosineSimilarity } from "./embeddings-BbNVXa_0.mjs";
3
+ import { a as formatDetectionJson, r as detectProject, t as buildWakeupContext } from "./wakeup-D8n33hYV.mjs";
4
+ import { n as cosineSimilarity } from "./embeddings-Bx3q0QOY.mjs";
5
5
  import { existsSync, readFileSync, statSync } from "node:fs";
6
6
  import { isAbsolute, join, resolve } from "node:path";
7
7
 
@@ -71,7 +71,7 @@ async function toolMemorySearch(registry, federation, params, searchDefaults) {
71
71
  let results;
72
72
  if (mode === "keyword") results = await federation.searchKeyword(params.query, searchOpts);
73
73
  else if (mode === "semantic" || mode === "hybrid") {
74
- const { generateEmbedding } = await import("./embeddings-CDfM63uC.mjs");
74
+ const { generateEmbedding } = await import("./embeddings-CcscYWwk.mjs");
75
75
  const queryEmbedding = await generateEmbedding(params.query, true);
76
76
  if (mode === "semantic") results = await federation.searchSemantic(queryEmbedding, searchOpts);
77
77
  else {
@@ -86,7 +86,7 @@ async function toolMemorySearch(registry, federation, params, searchDefaults) {
86
86
  }
87
87
  } else results = await federation.searchKeyword(params.query, searchOpts);
88
88
  try {
89
- const { applyLinkBoost } = await import("./link-boost-UiE-uooT.mjs");
89
+ const { applyLinkBoost } = await import("./link-boost-CnI7UVMJ.mjs");
90
90
  const paths = [...new Set(results.map((r) => r.path))];
91
91
  if (paths.length > 1) {
92
92
  const edges = [];
@@ -649,7 +649,7 @@ async function toolSessionList(registry, params) {
649
649
  */
650
650
  async function toolSessionRoute(registry, federation, params) {
651
651
  try {
652
- const { autoRoute, formatAutoRouteJson } = await import("./auto-route-sLMU-NnM.mjs");
652
+ const { autoRoute, formatAutoRouteJson } = await import("./auto-route-o0BOsXn7.mjs");
653
653
  const result = await autoRoute(registry, federation, params.cwd, params.context);
654
654
  if (!result) return { content: [{
655
655
  type: "text",
@@ -706,7 +706,7 @@ async function toolRegistrySearch(registry, params) {
706
706
  //#region src/mcp/tools/zettel.ts
707
707
  async function toolZettelExplore(backend, params) {
708
708
  try {
709
- const { zettelExplore } = await import("./zettelkasten-Dwj67e4p.mjs");
709
+ const { zettelExplore } = await import("./zettelkasten-CtGHQWPU.mjs");
710
710
  const result = await zettelExplore(backend, {
711
711
  startNote: params.start_note,
712
712
  depth: params.depth,
@@ -729,7 +729,7 @@ async function toolZettelExplore(backend, params) {
729
729
  }
730
730
  async function toolZettelHealth(backend, params) {
731
731
  try {
732
- const { zettelHealth } = await import("./zettelkasten-Dwj67e4p.mjs");
732
+ const { zettelHealth } = await import("./zettelkasten-CtGHQWPU.mjs");
733
733
  const result = await zettelHealth(backend, {
734
734
  scope: params.scope,
735
735
  projectPath: params.project_path,
@@ -752,7 +752,7 @@ async function toolZettelHealth(backend, params) {
752
752
  }
753
753
  async function toolZettelSurprise(backend, params) {
754
754
  try {
755
- const { zettelSurprise } = await import("./zettelkasten-Dwj67e4p.mjs");
755
+ const { zettelSurprise } = await import("./zettelkasten-CtGHQWPU.mjs");
756
756
  const results = await zettelSurprise(backend, {
757
757
  referencePath: params.reference_path,
758
758
  vaultProjectId: params.vault_project_id,
@@ -776,7 +776,7 @@ async function toolZettelSurprise(backend, params) {
776
776
  }
777
777
  async function toolZettelSuggest(backend, params) {
778
778
  try {
779
- const { zettelSuggest } = await import("./zettelkasten-Dwj67e4p.mjs");
779
+ const { zettelSuggest } = await import("./zettelkasten-CtGHQWPU.mjs");
780
780
  const results = await zettelSuggest(backend, {
781
781
  notePath: params.note_path,
782
782
  vaultProjectId: params.vault_project_id,
@@ -799,7 +799,7 @@ async function toolZettelSuggest(backend, params) {
799
799
  }
800
800
  async function toolZettelConverse(backend, params) {
801
801
  try {
802
- const { zettelConverse } = await import("./zettelkasten-Dwj67e4p.mjs");
802
+ const { zettelConverse } = await import("./zettelkasten-CtGHQWPU.mjs");
803
803
  const result = await zettelConverse(backend, {
804
804
  question: params.question,
805
805
  vaultProjectId: params.vault_project_id,
@@ -835,7 +835,7 @@ async function toolZettelConverse(backend, params) {
835
835
  }
836
836
  async function toolZettelThemes(backend, params) {
837
837
  try {
838
- const { zettelThemes } = await import("./zettelkasten-Dwj67e4p.mjs");
838
+ const { zettelThemes } = await import("./zettelkasten-CtGHQWPU.mjs");
839
839
  const result = await zettelThemes(backend, {
840
840
  vaultProjectId: params.vault_project_id,
841
841
  lookbackDays: params.lookback_days,
@@ -859,7 +859,7 @@ async function toolZettelThemes(backend, params) {
859
859
  }
860
860
  async function toolZettelGodNotes(backend, params) {
861
861
  try {
862
- const { zettelGodNotes } = await import("./zettelkasten-Dwj67e4p.mjs");
862
+ const { zettelGodNotes } = await import("./zettelkasten-CtGHQWPU.mjs");
863
863
  const result = await zettelGodNotes(backend, {
864
864
  limit: params.limit,
865
865
  minInbound: params.min_inbound
@@ -880,7 +880,7 @@ async function toolZettelGodNotes(backend, params) {
880
880
  }
881
881
  async function toolZettelCommunities(backend, params) {
882
882
  try {
883
- const { zettelCommunities } = await import("./zettelkasten-Dwj67e4p.mjs");
883
+ const { zettelCommunities } = await import("./zettelkasten-CtGHQWPU.mjs");
884
884
  const result = await zettelCommunities(backend, {
885
885
  minSize: params.min_size,
886
886
  maxCommunities: params.max_communities,
@@ -1356,7 +1356,7 @@ async function bfsExpand(federation, entityNames, hops, projectId) {
1356
1356
  */
1357
1357
  async function rerankTriples(triples, queryVec, maxTriples) {
1358
1358
  if (triples.length === 0) return [];
1359
- const { generateEmbedding } = await import("./embeddings-CDfM63uC.mjs");
1359
+ const { generateEmbedding } = await import("./embeddings-CcscYWwk.mjs");
1360
1360
  return (await Promise.all(triples.map(async (triple) => {
1361
1361
  const text = `${triple.subject} ${triple.predicate} ${triple.object}`;
1362
1362
  try {
@@ -1449,7 +1449,7 @@ async function toolMemoryKgSearch(federation, params) {
1449
1449
  }],
1450
1450
  isError: true
1451
1451
  };
1452
- const { generateEmbedding } = await import("./embeddings-CDfM63uC.mjs");
1452
+ const { generateEmbedding } = await import("./embeddings-CcscYWwk.mjs");
1453
1453
  const result = await graphCompletionSearch(federation, await generateEmbedding(params.query, true), {
1454
1454
  seedCount: params.wide_k ?? 50,
1455
1455
  hops: params.neighborhood_depth ?? 1,
@@ -1486,4 +1486,4 @@ async function toolMemoryKgSearch(federation, params) {
1486
1486
 
1487
1487
  //#endregion
1488
1488
  export { detectProjectFromPath as A, toolProjectInfo as C, toolMemoryGet as D, combineHybridResults as E, lookupProjectId as M, toolMemoryOutline as O, toolProjectHealth as S, toolProjectTodo as T, toolZettelThemes as _, toolMemoryWakeup as a, toolSessionRoute as b, toolKgInvalidate as c, toolZettelConverse as d, toolZettelExplore as f, toolZettelSurprise as g, toolZettelSuggest as h, toolMemoryTaxonomy as i, formatProject as j, toolMemorySearch as k, toolKgQuery as l, toolZettelHealth as m, toolMemoryFeedback as n, toolKgAdd as o, toolZettelGodNotes as p, toolMemoryTunnels as r, toolKgContradictions as s, toolMemoryKgSearch as t, toolZettelCommunities as u, toolRegistrySearch as v, toolProjectList as w, toolProjectDetect as x, toolSessionList as y };
1489
- //# sourceMappingURL=tools-gMGDIpJ9.mjs.map
1489
+ //# sourceMappingURL=tools-BcOKPBtg.mjs.map