@tekmidian/pai 0.9.0 → 0.9.1

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 (48) hide show
  1. package/dist/{auto-route-C-DrW6BL.mjs → auto-route-CruBrTf-.mjs} +2 -2
  2. package/dist/{auto-route-C-DrW6BL.mjs.map → auto-route-CruBrTf-.mjs.map} +1 -1
  3. package/dist/cli/index.mjs +345 -23
  4. package/dist/cli/index.mjs.map +1 -1
  5. package/dist/{clusters-JIDQW65f.mjs → clusters-CRlPBpq8.mjs} +1 -1
  6. package/dist/{clusters-JIDQW65f.mjs.map → clusters-CRlPBpq8.mjs.map} +1 -1
  7. package/dist/daemon/index.mjs +6 -6
  8. package/dist/{daemon-VIFoKc_z.mjs → daemon-kp49BE7u.mjs} +74 -21
  9. package/dist/daemon-kp49BE7u.mjs.map +1 -0
  10. package/dist/{detector-jGBuYQJM.mjs → detector-CNU3zCwP.mjs} +1 -1
  11. package/dist/{detector-jGBuYQJM.mjs.map → detector-CNU3zCwP.mjs.map} +1 -1
  12. package/dist/{factory-e0k1HWuc.mjs → factory-DKDPRhAN.mjs} +3 -3
  13. package/dist/{factory-e0k1HWuc.mjs.map → factory-DKDPRhAN.mjs.map} +1 -1
  14. package/dist/{indexer-backend-jcJFsmB4.mjs → indexer-backend-CIIlrYh6.mjs} +1 -1
  15. package/dist/{indexer-backend-jcJFsmB4.mjs.map → indexer-backend-CIIlrYh6.mjs.map} +1 -1
  16. package/dist/kg-B5ysyRLC.mjs +94 -0
  17. package/dist/kg-B5ysyRLC.mjs.map +1 -0
  18. package/dist/kg-extraction-BlGM40q7.mjs +211 -0
  19. package/dist/kg-extraction-BlGM40q7.mjs.map +1 -0
  20. package/dist/{latent-ideas-bTJo6Omd.mjs → latent-ideas-DvWBRHsy.mjs} +2 -2
  21. package/dist/{latent-ideas-bTJo6Omd.mjs.map → latent-ideas-DvWBRHsy.mjs.map} +1 -1
  22. package/dist/{neighborhood-BYYbEkUJ.mjs → neighborhood-u8ytjmWq.mjs} +1 -1
  23. package/dist/{neighborhood-BYYbEkUJ.mjs.map → neighborhood-u8ytjmWq.mjs.map} +1 -1
  24. package/dist/{note-context-BK24bX8Y.mjs → note-context-CG2_e-0W.mjs} +1 -1
  25. package/dist/{note-context-BK24bX8Y.mjs.map → note-context-CG2_e-0W.mjs.map} +1 -1
  26. package/dist/{postgres-DvEPooLO.mjs → postgres-BGERehmX.mjs} +1 -1
  27. package/dist/{postgres-DvEPooLO.mjs.map → postgres-BGERehmX.mjs.map} +1 -1
  28. package/dist/{query-feedback-Dv43XKHM.mjs → query-feedback-CQSumXDy.mjs} +1 -1
  29. package/dist/{query-feedback-Dv43XKHM.mjs.map → query-feedback-CQSumXDy.mjs.map} +1 -1
  30. package/dist/skills/Reconstruct/SKILL.md +36 -0
  31. package/dist/{sqlite-l-s9xPjY.mjs → sqlite-BJrME_vg.mjs} +1 -1
  32. package/dist/{sqlite-l-s9xPjY.mjs.map → sqlite-BJrME_vg.mjs.map} +1 -1
  33. package/dist/{state-C6_vqz7w.mjs → state-BIlxNRUn.mjs} +1 -1
  34. package/dist/{state-C6_vqz7w.mjs.map → state-BIlxNRUn.mjs.map} +1 -1
  35. package/dist/{themes-BvYF0W8T.mjs → themes-9jxFn3Rf.mjs} +1 -1
  36. package/dist/{themes-BvYF0W8T.mjs.map → themes-9jxFn3Rf.mjs.map} +1 -1
  37. package/dist/{tools-C4SBZHga.mjs → tools-8t7BQrm9.mjs} +13 -104
  38. package/dist/tools-8t7BQrm9.mjs.map +1 -0
  39. package/dist/{trace-CRx9lPuc.mjs → trace-C2XrzssW.mjs} +1 -1
  40. package/dist/{trace-CRx9lPuc.mjs.map → trace-C2XrzssW.mjs.map} +1 -1
  41. package/dist/{vault-indexer-B-aJpRZC.mjs → vault-indexer-TTCl1QOL.mjs} +1 -1
  42. package/dist/{vault-indexer-B-aJpRZC.mjs.map → vault-indexer-TTCl1QOL.mjs.map} +1 -1
  43. package/dist/{zettelkasten-DhBKZQHF.mjs → zettelkasten-BdaMzTGQ.mjs} +3 -3
  44. package/dist/{zettelkasten-DhBKZQHF.mjs.map → zettelkasten-BdaMzTGQ.mjs.map} +1 -1
  45. package/package.json +1 -1
  46. package/dist/daemon-VIFoKc_z.mjs.map +0 -1
  47. package/dist/indexer-D53l5d1U.mjs +0 -1
  48. package/dist/tools-C4SBZHga.mjs.map +0 -1
@@ -221,6 +221,42 @@ git commit -m "docs: reconstruct session notes for YYYY-MM-DD to YYYY-MM-DD"
221
221
 
222
222
  ---
223
223
 
224
+ ### Step 9 — Extract Knowledge Graph Triples
225
+
226
+ After reconstructing the session notes, extract structured facts as triples and add them to the temporal knowledge graph. This populates the KG with verifiable facts from the historical record so that future queries (`kg_query`, `pai kg query`) can answer questions about past decisions.
227
+
228
+ For each reconstructed note, call `kg_add` to store key facts. Examples of facts worth extracting:
229
+
230
+ - **Project decisions**: `subject=<project>`, `predicate=decided_to`, `object=<decision>`
231
+ - **Version bumps**: `subject=<project>`, `predicate=shipped_version`, `object=<version>`
232
+ - **Architectural choices**: `subject=<component>`, `predicate=uses`, `object=<technology>`
233
+ - **Status changes**: `subject=<project>`, `predicate=status`, `object=<current_state>`
234
+ - **Dependencies**: `subject=<project>`, `predicate=depends_on`, `object=<library>`
235
+ - **Ownership / location**: `subject=<file_or_module>`, `predicate=lives_at`, `object=<path>`
236
+
237
+ **Procedure for each fact:**
238
+
239
+ 1. Call `kg_query` first with the same `(subject, predicate)` to check if a fact already exists.
240
+ 2. If a new fact supersedes an old one (same subject + predicate, different object), call `kg_invalidate` on the old triple's id BEFORE adding the new one.
241
+ 3. Call `kg_add` with the new fact, setting `source_session` to the reconstructed note filename and `confidence: "EXTRACTED"`.
242
+
243
+ **Quality rules:**
244
+
245
+ - Only extract **atomic, verifiable facts** that can be traced back to a commit, a user message, or an explicit decision in the note.
246
+ - Skip opinions, speculation ("seems like", "probably"), and aspirational statements ("we should").
247
+ - Use `snake_case` predicates.
248
+ - Maximum **15 triples per note** — pick the most important.
249
+ - Never invent project names, versions, or paths. Copy them verbatim.
250
+
251
+ **Alternative (batch mode):** If reconstructing many notes at once, instead of calling `kg_add` interactively you may run `pai kg backfill --project <slug>` from the terminal after the notes are written. The backfill walks every note in `Notes/YYYY/MM/`, runs the same extractor the session-summary-worker uses, and is idempotent (state file at `~/.config/pai/kg-backfill-state.json`).
252
+
253
+ After extraction, append a one-line note to the summary output:
254
+ ```
255
+ KG: extracted N triples (M added, K superseded) across the reconstructed notes.
256
+ ```
257
+
258
+ ---
259
+
224
260
  ### Anti-Defaults
225
261
 
226
262
  - **NEVER overwrite existing notes.** Skip silently, report in summary.
@@ -261,4 +261,4 @@ var SQLiteBackend = class {
261
261
 
262
262
  //#endregion
263
263
  export { SQLiteBackend };
264
- //# sourceMappingURL=sqlite-l-s9xPjY.mjs.map
264
+ //# sourceMappingURL=sqlite-BJrME_vg.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"sqlite-l-s9xPjY.mjs","names":[],"sources":["../src/storage/sqlite.ts"],"sourcesContent":["/**\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 { StorageBackend, ChunkRow, FileRow, FederationStats } from \"./interface.js\";\nimport type { SearchResult, SearchOptions } from \"../memory/search.js\";\nimport { searchMemory, searchMemorySemantic } from \"../memory/search.js\";\n\nexport class SQLiteBackend implements StorageBackend {\n readonly backendType = \"sqlite\" as const;\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.\n * Used by the daemon to pass to indexAll() which still uses the synchronous API directly.\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 // -------------------------------------------------------------------------\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(projectId?: number): 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 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 rows = this.db\n .prepare(`SELECT id, text, project_id, path FROM memory_chunks ${where}\n ORDER BY 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 .all(...params) 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"],"mappings":"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"}
1
+ {"version":3,"file":"sqlite-BJrME_vg.mjs","names":[],"sources":["../src/storage/sqlite.ts"],"sourcesContent":["/**\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 { StorageBackend, ChunkRow, FileRow, FederationStats } from \"./interface.js\";\nimport type { SearchResult, SearchOptions } from \"../memory/search.js\";\nimport { searchMemory, searchMemorySemantic } from \"../memory/search.js\";\n\nexport class SQLiteBackend implements StorageBackend {\n readonly backendType = \"sqlite\" as const;\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.\n * Used by the daemon to pass to indexAll() which still uses the synchronous API directly.\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 // -------------------------------------------------------------------------\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(projectId?: number): 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 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 rows = this.db\n .prepare(`SELECT id, text, project_id, path FROM memory_chunks ${where}\n ORDER BY 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 .all(...params) 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"],"mappings":";;;;AAeA,IAAa,gBAAb,MAAqD;CACnD,AAAS,cAAc;CAEvB,AAAQ;CAER,YAAY,IAAc;AACxB,OAAK,KAAK;;;;;;CAOZ,WAAqB;AACnB,SAAO,KAAK;;CAOd,MAAM,QAAuB;AAC3B,MAAI;AACF,QAAK,GAAG,OAAO;UACT;;CAKV,MAAM,WAAqC;AAOzC,SAAO;GAAE,OALP,KAAK,GAAG,QAAQ,yCAAyC,CAAC,KAAK,CAC/D;GAIc,QAFd,KAAK,GAAG,QAAQ,0CAA0C,CAAC,KAAK,CAChE;GACsB;;CAO1B,MAAM,YAAY,WAAmB,MAA2C;AAI9E,SAHY,KAAK,GACd,QAAQ,kEAAkE,CAC1E,IAAI,WAAW,KAAK,EACX;;CAGd,MAAM,WAAW,MAA8B;AAC7C,OAAK,GACF,QACC;;;;;;;mCAQD,CACA,IAAI,KAAK,WAAW,KAAK,MAAM,KAAK,QAAQ,KAAK,MAAM,KAAK,MAAM,KAAK,OAAO,KAAK,KAAK;;CAO7F,MAAM,YAAY,WAAmB,MAAiC;AAIpE,SAHa,KAAK,GACf,QAAQ,iEAAiE,CACzE,IAAI,WAAW,KAAK,CACX,KAAK,MAAM,EAAE,GAAG;;CAG9B,MAAM,oBAAoB,WAAmB,MAA6B;EACxE,MAAM,MAAM,MAAM,KAAK,YAAY,WAAW,KAAK;EACnD,MAAM,YAAY,KAAK,GAAG,QAAQ,sCAAsC;EACxE,MAAM,eAAe,KAAK,GAAG,QAC3B,8DACD;AACD,OAAK,GAAG,kBAAkB;AACxB,QAAK,MAAM,MAAM,IACf,WAAU,IAAI,GAAG;AAEnB,gBAAa,IAAI,WAAW,KAAK;IACjC,EAAE;;CAGN,MAAM,aAAa,QAAmC;AACpD,MAAI,OAAO,WAAW,EAAG;EAEzB,MAAM,cAAc,KAAK,GAAG,QAC1B;8CAED;EACD,MAAM,YAAY,KAAK,GAAG,QACxB;wCAED;AAED,OAAK,GAAG,kBAAkB;AACxB,QAAK,MAAM,KAAK,QAAQ;AACtB,gBAAY,IACV,EAAE,IACF,EAAE,WACF,EAAE,QACF,EAAE,MACF,EAAE,MACF,EAAE,WACF,EAAE,SACF,EAAE,MACF,EAAE,MACF,EAAE,UACH;AACD,cAAU,IACR,EAAE,MACF,EAAE,IACF,EAAE,WACF,EAAE,MACF,EAAE,QACF,EAAE,MACF,EAAE,WACF,EAAE,QACH;;IAEH,EAAE;;CAGN,MAAM,sBAAsB,WAAsC;AAIhE,SAHa,KAAK,GACf,QAAQ,+DAA+D,CACvE,IAAI,UAAU,CACL,KAAK,MAAM,EAAE,KAAK;;CAGhC,MAAM,YAAY,WAAmB,OAAgC;AACnE,MAAI,MAAM,WAAW,EAAG;EACxB,MAAM,YAAY,KAAK,GAAG,QAAQ,sCAAsC;EACxE,MAAM,eAAe,KAAK,GAAG,QAC3B,8DACD;EACD,MAAM,aAAa,KAAK,GAAG,QACzB,6DACD;AACD,OAAK,GAAG,kBAAkB;AACxB,QAAK,MAAM,QAAQ,OAAO;IACxB,MAAM,MAAM,KAAK,GACd,QAAQ,iEAAiE,CACzE,IAAI,WAAW,KAAK;AACvB,SAAK,MAAM,EAAE,QAAQ,IACnB,WAAU,IAAI,GAAG;AAEnB,iBAAa,IAAI,WAAW,KAAK;AACjC,eAAW,IAAI,WAAW,KAAK;;IAEjC,EAAE;;CAGN,MAAM,sBAAsB,WAAoG;EAC9H,MAAM,aAAa,CAAC,oBAAoB;EACxC,MAAM,SAA8B,EAAE;AAEtC,MAAI,cAAc,QAAW;AAC3B,cAAW,KAAK,iBAAiB;AACjC,UAAO,KAAK,UAAU;;EAGxB,MAAM,QAAQ,WAAW,WAAW,KAAK,QAAQ;AAsBjD,SAnBa,KAAK,GACf,QAAQ,wDAAwD,MAAM;;;;;;;;;;;;;;;;iBAgB5D,CACV,IAAI,GAAG,OAAO;;CAInB,MAAM,gBAAgB,SAAiB,WAAkC;AACvE,OAAK,GACF,QAAQ,sDAAsD,CAC9D,IAAI,WAAW,QAAQ;;CAO5B,MAAM,cAAc,OAAe,MAA+C;AAChF,SAAO,aAAa,KAAK,IAAI,OAAO,KAAK;;CAG3C,MAAM,eAAe,gBAA8B,MAA+C;AAChG,SAAO,qBAAqB,KAAK,IAAI,gBAAgB,KAAK;;CAO5D,AAAQ,oBAA2B;AACjC,QAAM,IAAI,MAAM,gDAAgD;;CAGlE,MAAM,kBAAiC;AAAE,OAAK,mBAAmB;;CACjE,MAAM,kBAAiC;AAAE,OAAK,mBAAmB;;CACjE,MAAM,eAA8B;AAAE,OAAK,mBAAmB;;CAC9D,MAAM,sBAAqC;AAAE,OAAK,mBAAmB;;CACrE,MAAM,mBAAqC;AAAE,OAAK,mBAAmB;;CACrE,MAAM,sBAAwC;AAAE,OAAK,mBAAmB;;CACxE,MAAM,kBAAmC;AAAE,OAAK,mBAAmB;;CACnE,MAAM,qBAAoC;AAAE,OAAK,mBAAmB;;CACpE,MAAM,qBAAoC;AAAE,OAAK,mBAAmB;;CACpE,MAAM,yBAAwC;AAAE,OAAK,mBAAmB;;CACxE,MAAM,qBAAuC;AAAE,OAAK,mBAAmB;;CACvE,MAAM,mBAAqC;AAAE,OAAK,mBAAmB;;CACrE,MAAM,oBAAsC;AAAE,OAAK,mBAAmB;;CACtE,MAAM,oBAAmC;AAAE,OAAK,mBAAmB;;CACnE,MAAM,iBAAgC;AAAE,OAAK,mBAAmB;;CAChE,MAAM,aAA+B;AAAE,OAAK,mBAAmB;;CAC/D,MAAM,eAAiC;AAAE,OAAK,mBAAmB;;CACjE,MAAM,kBAAiC;AAAE,OAAK,mBAAmB;;CACjE,MAAM,mBAAkC;AAAE,OAAK,mBAAmB;;CAClE,MAAM,mBAAqC;AAAE,OAAK,mBAAmB;;CACrE,MAAM,uBAAyC;AAAE,OAAK,mBAAmB;;CACzE,MAAM,uBAAyC;AAAE,OAAK,mBAAmB;;CACzE,MAAM,4BAA8C;AAAE,OAAK,mBAAmB;;CAC9E,MAAM,yBAA2C;AAAE,OAAK,mBAAmB;;CAC3E,MAAM,0BAA4C;AAAE,OAAK,mBAAmB;;CAC5E,MAAM,mBAAqC;AAAE,OAAK,mBAAmB;;CACrE,MAAM,qBAAuC;AAAE,OAAK,mBAAmB;;CACvE,MAAM,4BAA6C;AAAE,OAAK,mBAAmB;;CAC7E,MAAM,uBAAwC;AAAE,OAAK,mBAAmB;;CACxE,MAAM,4BAA6C;AAAE,OAAK,mBAAmB;;CAC7E,MAAM,uBAAwC;AAAE,OAAK,mBAAmB;;CACxE,MAAM,8BAAgD;AAAE,OAAK,mBAAmB;;CAChF,MAAM,yBAA2C;AAAE,OAAK,mBAAmB;;CAC3E,MAAM,oBAAsC;AAAE,OAAK,mBAAmB;;CACtE,MAAM,uBAAyC;AAAE,OAAK,mBAAmB;;CACzE,MAAM,kBAAoC;AAAE,OAAK,mBAAmB;;CACpE,MAAM,qBAAuC;AAAE,OAAK,mBAAmB;;CACvE,MAAM,+BAAiD;AAAE,OAAK,mBAAmB;;CACjF,MAAM,0BAA4C;AAAE,OAAK,mBAAmB;;CAC5E,MAAM,uBAAyC;AAAE,OAAK,mBAAmB;;CACzE,MAAM,8BAAgD;AAAE,OAAK,mBAAmB;;CAChF,MAAM,yBAA2C;AAAE,OAAK,mBAAmB;;CAC3E,MAAM,oBAAsC;AAAE,OAAK,mBAAmB;;CACtE,MAAM,8BAAgD;AAAE,OAAK,mBAAmB;;CAChF,MAAM,yBAA2C;AAAE,OAAK,mBAAmB;;CAC3E,MAAM,gBAA+B;AAAE,OAAK,mBAAmB"}
@@ -99,4 +99,4 @@ function setLastVaultIndexTime(v) {
99
99
 
100
100
  //#endregion
101
101
  export { setStorageBackend as C, state_exports as D, startTime as E, storageBackend as O, setStartTime as S, shutdownRequested as T, setLastIndexTime as _, indexSchedulerTimer as a, setRegistryDb as b, lastVaultIndexTime as c, setDaemonConfig as d, setEmbedInProgress as f, setLastEmbedTime as g, setIndexSchedulerTimer as h, indexInProgress as i, vaultIndexInProgress as k, notificationConfig as l, setIndexInProgress as m, embedInProgress as n, lastEmbedTime as o, setEmbedSchedulerTimer as p, embedSchedulerTimer as r, lastIndexTime as s, daemonConfig as t, registryDb as u, setLastVaultIndexTime as v, setVaultIndexInProgress as w, setShutdownRequested as x, setNotificationConfig as y };
102
- //# sourceMappingURL=state-C6_vqz7w.mjs.map
102
+ //# sourceMappingURL=state-BIlxNRUn.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"state-C6_vqz7w.mjs","names":[],"sources":["../src/daemon/daemon/state.ts"],"sourcesContent":["/**\n * Shared mutable daemon state — module-level singletons used across all daemon sub-modules.\n */\n\nimport { openRegistry } from \"../../registry/db.js\";\nimport type { StorageBackend } from \"../../storage/interface.js\";\nimport type { NotificationConfig } from \"../../notifications/types.js\";\nimport type { PaiDaemonConfig } from \"../config.js\";\n\n// ---------------------------------------------------------------------------\n// Core singletons (assigned at startup by serve())\n// ---------------------------------------------------------------------------\n\nexport let registryDb: ReturnType<typeof openRegistry>;\nexport let storageBackend: StorageBackend;\nexport let daemonConfig: PaiDaemonConfig;\nexport let startTime = Date.now();\n\n// ---------------------------------------------------------------------------\n// Scheduler state\n// ---------------------------------------------------------------------------\n\n/** True while a project index pass is running. */\nexport let indexInProgress = false;\nexport let lastIndexTime = 0;\nexport let indexSchedulerTimer: ReturnType<typeof setInterval> | null = null;\n\n/** True while an embedding pass is running. */\nexport let embedInProgress = false;\nexport let lastEmbedTime = 0;\nexport let embedSchedulerTimer: ReturnType<typeof setInterval> | null = null;\n\n/** True while a vault index pass is running. */\nexport let vaultIndexInProgress = false;\nexport let lastVaultIndexTime = 0;\n\n// ---------------------------------------------------------------------------\n// Notification state\n// ---------------------------------------------------------------------------\n\n/** Mutable notification config — loaded from disk at startup, patchable at runtime. */\nexport let notificationConfig: NotificationConfig;\n\n// ---------------------------------------------------------------------------\n// Graceful shutdown flag\n// ---------------------------------------------------------------------------\n\n/**\n * Set to true when a SIGTERM/SIGINT is received so that long-running loops\n * (embed, index) can detect the signal and exit their inner loops before the\n * pool/backend is closed.\n */\nexport let shutdownRequested = false;\n\n// ---------------------------------------------------------------------------\n// Setters (TypeScript requires assignment functions for exported `let` vars\n// that need to be mutated across module boundaries)\n// ---------------------------------------------------------------------------\n\nexport function setRegistryDb(db: ReturnType<typeof openRegistry>): void { registryDb = db; }\nexport function setStorageBackend(b: StorageBackend): void { storageBackend = b; }\nexport function setDaemonConfig(c: PaiDaemonConfig): void { daemonConfig = c; }\nexport function setStartTime(t: number): void { startTime = t; }\nexport function setNotificationConfig(c: NotificationConfig): void { notificationConfig = c; }\nexport function setShutdownRequested(v: boolean): void { shutdownRequested = v; }\nexport function setIndexInProgress(v: boolean): void { indexInProgress = v; }\nexport function setLastIndexTime(v: number): void { lastIndexTime = v; }\nexport function setIndexSchedulerTimer(v: ReturnType<typeof setInterval> | null): void { indexSchedulerTimer = v; }\nexport function setEmbedInProgress(v: boolean): void { embedInProgress = v; }\nexport function setLastEmbedTime(v: number): void { lastEmbedTime = v; }\nexport function setEmbedSchedulerTimer(v: ReturnType<typeof setInterval> | null): void { embedSchedulerTimer = v; }\nexport function setVaultIndexInProgress(v: boolean): void { vaultIndexInProgress = v; }\nexport function setLastVaultIndexTime(v: number): void { lastVaultIndexTime = v; }\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAaA,IAAW;AACX,IAAW;AACX,IAAW;AACX,IAAW,YAAY,KAAK,KAAK;;AAOjC,IAAW,kBAAkB;AAC7B,IAAW,gBAAgB;AAC3B,IAAW,sBAA6D;;AAGxE,IAAW,kBAAkB;AAC7B,IAAW,gBAAgB;AAC3B,IAAW,sBAA6D;;AAGxE,IAAW,uBAAuB;AAClC,IAAW,qBAAqB;;AAOhC,IAAW;;;;;;AAWX,IAAW,oBAAoB;AAO/B,SAAgB,cAAc,IAA2C;AAAE,cAAa;;AACxF,SAAgB,kBAAkB,GAAyB;AAAE,kBAAiB;;AAC9E,SAAgB,gBAAgB,GAA0B;AAAE,gBAAe;;AAC3E,SAAgB,aAAa,GAAiB;AAAE,aAAY;;AAC5D,SAAgB,sBAAsB,GAA6B;AAAE,sBAAqB;;AAC1F,SAAgB,qBAAqB,GAAkB;AAAE,qBAAoB;;AAC7E,SAAgB,mBAAmB,GAAkB;AAAE,mBAAkB;;AACzE,SAAgB,iBAAiB,GAAiB;AAAE,iBAAgB;;AACpE,SAAgB,uBAAuB,GAAgD;AAAE,uBAAsB;;AAC/G,SAAgB,mBAAmB,GAAkB;AAAE,mBAAkB;;AACzE,SAAgB,iBAAiB,GAAiB;AAAE,iBAAgB;;AACpE,SAAgB,uBAAuB,GAAgD;AAAE,uBAAsB;;AAC/G,SAAgB,wBAAwB,GAAkB;AAAE,wBAAuB;;AACnF,SAAgB,sBAAsB,GAAiB;AAAE,sBAAqB"}
1
+ {"version":3,"file":"state-BIlxNRUn.mjs","names":[],"sources":["../src/daemon/daemon/state.ts"],"sourcesContent":["/**\n * Shared mutable daemon state — module-level singletons used across all daemon sub-modules.\n */\n\nimport { openRegistry } from \"../../registry/db.js\";\nimport type { StorageBackend } from \"../../storage/interface.js\";\nimport type { NotificationConfig } from \"../../notifications/types.js\";\nimport type { PaiDaemonConfig } from \"../config.js\";\n\n// ---------------------------------------------------------------------------\n// Core singletons (assigned at startup by serve())\n// ---------------------------------------------------------------------------\n\nexport let registryDb: ReturnType<typeof openRegistry>;\nexport let storageBackend: StorageBackend;\nexport let daemonConfig: PaiDaemonConfig;\nexport let startTime = Date.now();\n\n// ---------------------------------------------------------------------------\n// Scheduler state\n// ---------------------------------------------------------------------------\n\n/** True while a project index pass is running. */\nexport let indexInProgress = false;\nexport let lastIndexTime = 0;\nexport let indexSchedulerTimer: ReturnType<typeof setInterval> | null = null;\n\n/** True while an embedding pass is running. */\nexport let embedInProgress = false;\nexport let lastEmbedTime = 0;\nexport let embedSchedulerTimer: ReturnType<typeof setInterval> | null = null;\n\n/** True while a vault index pass is running. */\nexport let vaultIndexInProgress = false;\nexport let lastVaultIndexTime = 0;\n\n// ---------------------------------------------------------------------------\n// Notification state\n// ---------------------------------------------------------------------------\n\n/** Mutable notification config — loaded from disk at startup, patchable at runtime. */\nexport let notificationConfig: NotificationConfig;\n\n// ---------------------------------------------------------------------------\n// Graceful shutdown flag\n// ---------------------------------------------------------------------------\n\n/**\n * Set to true when a SIGTERM/SIGINT is received so that long-running loops\n * (embed, index) can detect the signal and exit their inner loops before the\n * pool/backend is closed.\n */\nexport let shutdownRequested = false;\n\n// ---------------------------------------------------------------------------\n// Setters (TypeScript requires assignment functions for exported `let` vars\n// that need to be mutated across module boundaries)\n// ---------------------------------------------------------------------------\n\nexport function setRegistryDb(db: ReturnType<typeof openRegistry>): void { registryDb = db; }\nexport function setStorageBackend(b: StorageBackend): void { storageBackend = b; }\nexport function setDaemonConfig(c: PaiDaemonConfig): void { daemonConfig = c; }\nexport function setStartTime(t: number): void { startTime = t; }\nexport function setNotificationConfig(c: NotificationConfig): void { notificationConfig = c; }\nexport function setShutdownRequested(v: boolean): void { shutdownRequested = v; }\nexport function setIndexInProgress(v: boolean): void { indexInProgress = v; }\nexport function setLastIndexTime(v: number): void { lastIndexTime = v; }\nexport function setIndexSchedulerTimer(v: ReturnType<typeof setInterval> | null): void { indexSchedulerTimer = v; }\nexport function setEmbedInProgress(v: boolean): void { embedInProgress = v; }\nexport function setLastEmbedTime(v: number): void { lastEmbedTime = v; }\nexport function setEmbedSchedulerTimer(v: ReturnType<typeof setInterval> | null): void { embedSchedulerTimer = v; }\nexport function setVaultIndexInProgress(v: boolean): void { vaultIndexInProgress = v; }\nexport function setLastVaultIndexTime(v: number): void { lastVaultIndexTime = v; }\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAaA,IAAW;AACX,IAAW;AACX,IAAW;AACX,IAAW,YAAY,KAAK,KAAK;;AAOjC,IAAW,kBAAkB;AAC7B,IAAW,gBAAgB;AAC3B,IAAW,sBAA6D;;AAGxE,IAAW,kBAAkB;AAC7B,IAAW,gBAAgB;AAC3B,IAAW,sBAA6D;;AAGxE,IAAW,uBAAuB;AAClC,IAAW,qBAAqB;;AAOhC,IAAW;;;;;;AAWX,IAAW,oBAAoB;AAO/B,SAAgB,cAAc,IAA2C;AAAE,cAAa;;AACxF,SAAgB,kBAAkB,GAAyB;AAAE,kBAAiB;;AAC9E,SAAgB,gBAAgB,GAA0B;AAAE,gBAAe;;AAC3E,SAAgB,aAAa,GAAiB;AAAE,aAAY;;AAC5D,SAAgB,sBAAsB,GAA6B;AAAE,sBAAqB;;AAC1F,SAAgB,qBAAqB,GAAkB;AAAE,qBAAoB;;AAC7E,SAAgB,mBAAmB,GAAkB;AAAE,mBAAkB;;AACzE,SAAgB,iBAAiB,GAAiB;AAAE,iBAAgB;;AACpE,SAAgB,uBAAuB,GAAgD;AAAE,uBAAsB;;AAC/G,SAAgB,mBAAmB,GAAkB;AAAE,mBAAkB;;AACzE,SAAgB,iBAAiB,GAAiB;AAAE,iBAAgB;;AACpE,SAAgB,uBAAuB,GAAgD;AAAE,uBAAsB;;AAC/G,SAAgB,wBAAwB,GAAkB;AAAE,wBAAuB;;AACnF,SAAgB,sBAAsB,GAAiB;AAAE,sBAAqB"}
@@ -145,4 +145,4 @@ async function zettelThemes(backend, opts) {
145
145
 
146
146
  //#endregion
147
147
  export { zettelThemes as t };
148
- //# sourceMappingURL=themes-BvYF0W8T.mjs.map
148
+ //# sourceMappingURL=themes-9jxFn3Rf.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"themes-BvYF0W8T.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
+ {"version":3,"file":"themes-9jxFn3Rf.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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"}
@@ -2,6 +2,7 @@ import { t as __exportAll } from "./rolldown-runtime-95iHPtFO.mjs";
2
2
  import { t as STOP_WORDS } from "./stop-words-BaMEGVeY.mjs";
3
3
  import { i as searchMemoryHybrid, n as populateSlugs } from "./search-DC1qhkKn.mjs";
4
4
  import { r as formatDetectionJson, t as detectProject } from "./detect-CdaA48EI.mjs";
5
+ import { i as kgQuery, n as kgContradictions, r as kgInvalidate, t as kgAdd } from "./kg-B5ysyRLC.mjs";
5
6
  import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
6
7
  import { homedir } from "node:os";
7
8
  import { basename, isAbsolute, join, resolve } from "node:path";
@@ -124,7 +125,7 @@ async function toolMemorySearch(registryDb, federation, params, searchDefaults)
124
125
  return `${header}\n${raw.length > snippetLength ? raw.slice(0, snippetLength) + "..." : raw}`;
125
126
  }).join("\n\n---\n\n");
126
127
  try {
127
- const { saveQueryResult } = await import("./query-feedback-Dv43XKHM.mjs").then((n) => n.t);
128
+ const { saveQueryResult } = await import("./query-feedback-CQSumXDy.mjs").then((n) => n.t);
128
129
  saveQueryResult({
129
130
  query: params.query,
130
131
  timestamp: Date.now(),
@@ -649,7 +650,7 @@ function toolSessionList(registryDb, params) {
649
650
  */
650
651
  async function toolSessionRoute(registryDb, federation, params) {
651
652
  try {
652
- const { autoRoute, formatAutoRouteJson } = await import("./auto-route-C-DrW6BL.mjs");
653
+ const { autoRoute, formatAutoRouteJson } = await import("./auto-route-CruBrTf-.mjs");
653
654
  const result = await autoRoute(registryDb, federation, params.cwd, params.context);
654
655
  if (!result) return { content: [{
655
656
  type: "text",
@@ -713,7 +714,7 @@ function toolRegistrySearch(registryDb, params) {
713
714
  //#region src/mcp/tools/zettel.ts
714
715
  async function toolZettelExplore(backend, params) {
715
716
  try {
716
- const { zettelExplore } = await import("./zettelkasten-DhBKZQHF.mjs");
717
+ const { zettelExplore } = await import("./zettelkasten-BdaMzTGQ.mjs");
717
718
  const result = await zettelExplore(backend, {
718
719
  startNote: params.start_note,
719
720
  depth: params.depth,
@@ -736,7 +737,7 @@ async function toolZettelExplore(backend, params) {
736
737
  }
737
738
  async function toolZettelHealth(backend, params) {
738
739
  try {
739
- const { zettelHealth } = await import("./zettelkasten-DhBKZQHF.mjs");
740
+ const { zettelHealth } = await import("./zettelkasten-BdaMzTGQ.mjs");
740
741
  const result = await zettelHealth(backend, {
741
742
  scope: params.scope,
742
743
  projectPath: params.project_path,
@@ -759,7 +760,7 @@ async function toolZettelHealth(backend, params) {
759
760
  }
760
761
  async function toolZettelSurprise(backend, params) {
761
762
  try {
762
- const { zettelSurprise } = await import("./zettelkasten-DhBKZQHF.mjs");
763
+ const { zettelSurprise } = await import("./zettelkasten-BdaMzTGQ.mjs");
763
764
  const results = await zettelSurprise(backend, {
764
765
  referencePath: params.reference_path,
765
766
  vaultProjectId: params.vault_project_id,
@@ -783,7 +784,7 @@ async function toolZettelSurprise(backend, params) {
783
784
  }
784
785
  async function toolZettelSuggest(backend, params) {
785
786
  try {
786
- const { zettelSuggest } = await import("./zettelkasten-DhBKZQHF.mjs");
787
+ const { zettelSuggest } = await import("./zettelkasten-BdaMzTGQ.mjs");
787
788
  const results = await zettelSuggest(backend, {
788
789
  notePath: params.note_path,
789
790
  vaultProjectId: params.vault_project_id,
@@ -806,7 +807,7 @@ async function toolZettelSuggest(backend, params) {
806
807
  }
807
808
  async function toolZettelConverse(backend, params) {
808
809
  try {
809
- const { zettelConverse } = await import("./zettelkasten-DhBKZQHF.mjs");
810
+ const { zettelConverse } = await import("./zettelkasten-BdaMzTGQ.mjs");
810
811
  const result = await zettelConverse(backend, {
811
812
  question: params.question,
812
813
  vaultProjectId: params.vault_project_id,
@@ -814,7 +815,7 @@ async function toolZettelConverse(backend, params) {
814
815
  limit: params.limit
815
816
  });
816
817
  try {
817
- const { saveQueryResult } = await import("./query-feedback-Dv43XKHM.mjs").then((n) => n.t);
818
+ const { saveQueryResult } = await import("./query-feedback-CQSumXDy.mjs").then((n) => n.t);
818
819
  saveQueryResult({
819
820
  query: params.question,
820
821
  timestamp: Date.now(),
@@ -842,7 +843,7 @@ async function toolZettelConverse(backend, params) {
842
843
  }
843
844
  async function toolZettelThemes(backend, params) {
844
845
  try {
845
- const { zettelThemes } = await import("./zettelkasten-DhBKZQHF.mjs");
846
+ const { zettelThemes } = await import("./zettelkasten-BdaMzTGQ.mjs");
846
847
  const result = await zettelThemes(backend, {
847
848
  vaultProjectId: params.vault_project_id,
848
849
  lookbackDays: params.lookback_days,
@@ -866,7 +867,7 @@ async function toolZettelThemes(backend, params) {
866
867
  }
867
868
  async function toolZettelGodNotes(backend, params) {
868
869
  try {
869
- const { zettelGodNotes } = await import("./zettelkasten-DhBKZQHF.mjs");
870
+ const { zettelGodNotes } = await import("./zettelkasten-BdaMzTGQ.mjs");
870
871
  const result = await zettelGodNotes(backend, {
871
872
  limit: params.limit,
872
873
  minInbound: params.min_inbound
@@ -887,7 +888,7 @@ async function toolZettelGodNotes(backend, params) {
887
888
  }
888
889
  async function toolZettelCommunities(backend, params) {
889
890
  try {
890
- const { zettelCommunities } = await import("./zettelkasten-DhBKZQHF.mjs");
891
+ const { zettelCommunities } = await import("./zettelkasten-BdaMzTGQ.mjs");
891
892
  const result = await zettelCommunities(backend, {
892
893
  minSize: params.min_size,
893
894
  maxCommunities: params.max_communities,
@@ -908,98 +909,6 @@ async function toolZettelCommunities(backend, params) {
908
909
  }
909
910
  }
910
911
 
911
- //#endregion
912
- //#region src/memory/kg.ts
913
- function rowToTriple(row) {
914
- return {
915
- id: row.id,
916
- subject: row.subject,
917
- predicate: row.predicate,
918
- object: row.object,
919
- project_id: row.project_id,
920
- source_session: row.source_session,
921
- valid_from: new Date(row.valid_from),
922
- valid_to: row.valid_to ? new Date(row.valid_to) : void 0,
923
- confidence: row.confidence,
924
- created_at: new Date(row.created_at)
925
- };
926
- }
927
- /**
928
- * Add a new triple to the knowledge graph.
929
- * Returns the inserted triple.
930
- */
931
- async function kgAdd(pool, params) {
932
- const confidence = params.confidence ?? "EXTRACTED";
933
- return rowToTriple((await pool.query(`INSERT INTO kg_triples
934
- (subject, predicate, object, project_id, source_session, confidence)
935
- VALUES ($1, $2, $3, $4, $5, $6)
936
- RETURNING *`, [
937
- params.subject,
938
- params.predicate,
939
- params.object,
940
- params.project_id ?? null,
941
- params.source_session ?? null,
942
- confidence
943
- ])).rows[0]);
944
- }
945
- /**
946
- * Query triples by subject, predicate, object, and/or project.
947
- * Supports point-in-time queries via as_of.
948
- * By default only returns currently-valid triples (valid_to IS NULL).
949
- */
950
- async function kgQuery(pool, params) {
951
- const conditions = [];
952
- const values = [];
953
- let idx = 1;
954
- if (params.subject !== void 0) {
955
- conditions.push(`subject = $${idx++}`);
956
- values.push(params.subject);
957
- }
958
- if (params.predicate !== void 0) {
959
- conditions.push(`predicate = $${idx++}`);
960
- values.push(params.predicate);
961
- }
962
- if (params.object !== void 0) {
963
- conditions.push(`object = $${idx++}`);
964
- values.push(params.object);
965
- }
966
- if (params.project_id !== void 0) {
967
- conditions.push(`project_id = $${idx++}`);
968
- values.push(params.project_id);
969
- }
970
- if (params.as_of !== void 0) {
971
- conditions.push(`valid_from <= $${idx++}`);
972
- values.push(params.as_of);
973
- conditions.push(`(valid_to IS NULL OR valid_to > $${idx++})`);
974
- values.push(params.as_of);
975
- } else if (!params.include_invalidated) conditions.push(`valid_to IS NULL`);
976
- const where = conditions.length > 0 ? `WHERE ${conditions.join(" AND ")}` : "";
977
- return (await pool.query(`SELECT * FROM kg_triples ${where} ORDER BY valid_from DESC`, values)).rows.map(rowToTriple);
978
- }
979
- /**
980
- * Invalidate a triple by setting valid_to = NOW().
981
- * Does not delete the row — preserves history.
982
- */
983
- async function kgInvalidate(pool, tripleId) {
984
- await pool.query(`UPDATE kg_triples SET valid_to = NOW() WHERE id = $1 AND valid_to IS NULL`, [tripleId]);
985
- }
986
- /**
987
- * Find contradictions: cases where the same (subject, predicate) pair has
988
- * multiple currently-valid objects.
989
- */
990
- async function kgContradictions(pool, subject) {
991
- return (await pool.query(`SELECT subject, predicate, array_agg(object ORDER BY object) AS objects
992
- FROM kg_triples
993
- WHERE subject = $1
994
- AND valid_to IS NULL
995
- GROUP BY subject, predicate
996
- HAVING COUNT(*) > 1`, [subject])).rows.map((row) => ({
997
- subject: row.subject,
998
- predicate: row.predicate,
999
- objects: row.objects
1000
- }));
1001
- }
1002
-
1003
912
  //#endregion
1004
913
  //#region src/mcp/tools/kg.ts
1005
914
  async function toolKgAdd(pool, params) {
@@ -1728,4 +1637,4 @@ var tools_exports = /* @__PURE__ */ __exportAll({
1728
1637
 
1729
1638
  //#endregion
1730
1639
  export { toolSessionList as a, toolProjectHealth as c, toolProjectTodo as d, toolMemoryGet as f, toolRegistrySearch as i, toolProjectInfo as l, toolMemoryTaxonomy as n, toolSessionRoute as o, toolMemorySearch as p, toolMemoryWakeup as r, toolProjectDetect as s, tools_exports as t, toolProjectList as u };
1731
- //# sourceMappingURL=tools-C4SBZHga.mjs.map
1640
+ //# sourceMappingURL=tools-8t7BQrm9.mjs.map