@tekmidian/pai 0.63.0 → 0.64.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (150) hide show
  1. package/ARCHITECTURE.md +3 -0
  2. package/README.md +19 -0
  3. package/dist/{aibroker-client-BYG-GtAg.mjs → aibroker-client-BpfuGPe5.mjs} +1 -1
  4. package/dist/{aibroker-client-BK1h--W_.mjs → aibroker-client-DRld7g6z.mjs} +2 -2
  5. package/dist/{aibroker-client-BK1h--W_.mjs.map → aibroker-client-DRld7g6z.mjs.map} +1 -1
  6. package/dist/{async-DNVWtQl2.mjs → async-CY_cd_8j.mjs} +5 -4
  7. package/dist/async-CY_cd_8j.mjs.map +1 -0
  8. package/dist/{auto-route-CF8WNaoy.mjs → auto-route-CPlam4iv.mjs} +2 -2
  9. package/dist/{auto-route-CF8WNaoy.mjs.map → auto-route-CPlam4iv.mjs.map} +1 -1
  10. package/dist/{chain-CUfE6WYK.mjs → chain-o-g8AgE3.mjs} +4 -4
  11. package/dist/{chain-CUfE6WYK.mjs.map → chain-o-g8AgE3.mjs.map} +1 -1
  12. package/dist/chunker-BH4i-F2b.mjs +300 -0
  13. package/dist/chunker-BH4i-F2b.mjs.map +1 -0
  14. package/dist/cli/index.mjs +20 -20
  15. package/dist/cli/program.mjs +20 -20
  16. package/dist/{clusters-Ci1mseh2.mjs → clusters-DsMf20PP.mjs} +2 -2
  17. package/dist/{clusters-Ci1mseh2.mjs.map → clusters-DsMf20PP.mjs.map} +1 -1
  18. package/dist/{config-CQCmH_gV.mjs → config-2wkz744W.mjs} +3 -3
  19. package/dist/{config-CQCmH_gV.mjs.map → config-2wkz744W.mjs.map} +1 -1
  20. package/dist/{config-DjXq_W42.mjs → config-CUTg6VDq.mjs} +1 -1
  21. package/dist/{context-handover-cache-DFNnlWv2.mjs → context-handover-cache-BOpjLsKe.mjs} +6 -6
  22. package/dist/{context-handover-cache-DFNnlWv2.mjs.map → context-handover-cache-BOpjLsKe.mjs.map} +1 -1
  23. package/dist/daemon/index.mjs +16 -16
  24. package/dist/{daemon-Cex3dT9f.mjs → daemon-BYieXduQ.mjs} +31 -30
  25. package/dist/{daemon-Cex3dT9f.mjs.map → daemon-BYieXduQ.mjs.map} +1 -1
  26. package/dist/daemon-mcp/index.mjs +20 -11
  27. package/dist/daemon-mcp/index.mjs.map +1 -1
  28. package/dist/daemon-q3xjHwa4.mjs +19 -0
  29. package/dist/{detector-3g7XuU1r.mjs → detector-BAlmrLQ0.mjs} +2 -2
  30. package/dist/{detector-3g7XuU1r.mjs.map → detector-BAlmrLQ0.mjs.map} +1 -1
  31. package/dist/detector-C-YmZsOi.mjs +3 -0
  32. package/dist/{embeddings-DfrYNIng.mjs → embeddings-DK9XfQic.mjs} +1 -1
  33. package/dist/{embeddings-DfrYNIng.mjs.map → embeddings-DK9XfQic.mjs.map} +1 -1
  34. package/dist/{embeddings-BNv3TQC5.mjs → embeddings-Xg8XLf2K.mjs} +1 -1
  35. package/dist/{factory-UO9_DFMN.mjs → factory-BMK0tC1b.mjs} +1 -1
  36. package/dist/{factory-BozZa81q.mjs → factory-C4We3xR_.mjs} +13 -13
  37. package/dist/{factory-BozZa81q.mjs.map → factory-C4We3xR_.mjs.map} +1 -1
  38. package/dist/{fallback-ktbR0aR7.mjs → fallback-ECiqCuh6.mjs} +5 -5
  39. package/dist/{fallback-ktbR0aR7.mjs.map → fallback-ECiqCuh6.mjs.map} +1 -1
  40. package/dist/federation-db-BZ8PyxFe.mjs +3 -0
  41. package/dist/{federation-db-w8e-sKjp.mjs → federation-db-CUgy0WYs.mjs} +2 -2
  42. package/dist/{federation-db-w8e-sKjp.mjs.map → federation-db-CUgy0WYs.mjs.map} +1 -1
  43. package/dist/{helpers-Dj-B314s.mjs → helpers-BRCJg0G3.mjs} +2 -208
  44. package/dist/helpers-BRCJg0G3.mjs.map +1 -0
  45. package/dist/index.d.mts +26 -8
  46. package/dist/index.d.mts.map +1 -1
  47. package/dist/index.mjs +5 -4
  48. package/dist/indexer-backend-CJ0RGgMz.mjs +5 -0
  49. package/dist/{ipc-client-Dasm515c.mjs → ipc-client-EhWY8XL0.mjs} +2 -2
  50. package/dist/{ipc-client-Dasm515c.mjs.map → ipc-client-EhWY8XL0.mjs.map} +1 -1
  51. package/dist/{kg-entity-Cj8Q6YmU.mjs → kg-entity-1uqCnk4u.mjs} +1 -1
  52. package/dist/{kg-entity-Cj8Q6YmU.mjs.map → kg-entity-1uqCnk4u.mjs.map} +1 -1
  53. package/dist/{latent-ideas-BRy6HNwg.mjs → latent-ideas-CSuKfiq3.mjs} +4 -4
  54. package/dist/{latent-ideas-BRy6HNwg.mjs.map → latent-ideas-CSuKfiq3.mjs.map} +1 -1
  55. package/dist/{link-boost-BptbAPuv.mjs → link-boost-BtjzfE3c.mjs} +1 -1
  56. package/dist/{link-boost-BptbAPuv.mjs.map → link-boost-BtjzfE3c.mjs.map} +1 -1
  57. package/dist/{main-resolver-BIft_FVF.mjs → main-resolver-DyOrwNYw.mjs} +6 -6
  58. package/dist/{main-resolver-BIft_FVF.mjs.map → main-resolver-DyOrwNYw.mjs.map} +1 -1
  59. package/dist/main-resolver-vN09bkXv.mjs +7 -0
  60. package/dist/merge-CXGz5wXz.mjs +3 -0
  61. package/dist/{merge-xoyfZQ4W.mjs → merge-D58n_-LZ.mjs} +1 -1
  62. package/dist/{merge-xoyfZQ4W.mjs.map → merge-D58n_-LZ.mjs.map} +1 -1
  63. package/dist/{neighborhood-D8uscnGy.mjs → neighborhood-DNuRelRB.mjs} +2 -2
  64. package/dist/{neighborhood-D8uscnGy.mjs.map → neighborhood-DNuRelRB.mjs.map} +1 -1
  65. package/dist/{note-context-CgffaUMh.mjs → note-context-D9JZ4-7o.mjs} +1 -1
  66. package/dist/{note-context-CgffaUMh.mjs.map → note-context-D9JZ4-7o.mjs.map} +1 -1
  67. package/dist/{pai-home-CeOIYltV.mjs → pai-home-UncxWxlX.mjs} +1 -1
  68. package/dist/{pai-home-CeOIYltV.mjs.map → pai-home-UncxWxlX.mjs.map} +1 -1
  69. package/dist/{planner-DAQMTMjP.mjs → planner-DgC3oSvb.mjs} +7 -7
  70. package/dist/{planner-DAQMTMjP.mjs.map → planner-DgC3oSvb.mjs.map} +1 -1
  71. package/dist/{postgres-CxD6_Dca.mjs → postgres-CcsRKir-.mjs} +6 -5
  72. package/dist/{postgres-CxD6_Dca.mjs.map → postgres-CcsRKir-.mjs.map} +1 -1
  73. package/dist/{program-De1GVVM4.mjs → program-JhF2GgJY.mjs} +66 -66
  74. package/dist/program-JhF2GgJY.mjs.map +1 -0
  75. package/dist/query-feedback-BPHFFSu1.mjs +3 -0
  76. package/dist/{query-feedback-xz5axTj3.mjs → query-feedback-BPa0dISE.mjs} +2 -2
  77. package/dist/{query-feedback-xz5axTj3.mjs.map → query-feedback-BPa0dISE.mjs.map} +1 -1
  78. package/dist/{registry-db-Bkpbp7Gf.mjs → registry-db-DCzdI4sC.mjs} +2 -2
  79. package/dist/{registry-db-Bkpbp7Gf.mjs.map → registry-db-DCzdI4sC.mjs.map} +1 -1
  80. package/dist/registry-db-Hphlj3H4.mjs +3 -0
  81. package/dist/{registry-postgres-B07-hmn8.mjs → registry-postgres-BUnkHKYs.mjs} +2 -2
  82. package/dist/{registry-postgres-B07-hmn8.mjs.map → registry-postgres-BUnkHKYs.mjs.map} +1 -1
  83. package/dist/{registry-sqlite-CkGND_tm.mjs → registry-sqlite-CKYNwkUv.mjs} +2 -2
  84. package/dist/{registry-sqlite-CkGND_tm.mjs.map → registry-sqlite-CKYNwkUv.mjs.map} +1 -1
  85. package/dist/{reranker-BS5gqGeF.mjs → reranker-CFiEzHQu.mjs} +1 -1
  86. package/dist/{reranker-CRVawoEh.mjs → reranker-DKv80KO5.mjs} +1 -1
  87. package/dist/{reranker-CRVawoEh.mjs.map → reranker-DKv80KO5.mjs.map} +1 -1
  88. package/dist/{router-DjFZIoDl.mjs → router-CGZATlvm.mjs} +1 -1
  89. package/dist/{router-DjFZIoDl.mjs.map → router-CGZATlvm.mjs.map} +1 -1
  90. package/dist/router-CoA8Uy1m.mjs +3 -0
  91. package/dist/{run-CZVCCvXE.mjs → run-W2rV_9j0.mjs} +5 -5
  92. package/dist/{run-CZVCCvXE.mjs.map → run-W2rV_9j0.mjs.map} +1 -1
  93. package/dist/{run-env-BE5xyCwy.mjs → run-env-Br4Bc7Vz.mjs} +3 -3
  94. package/dist/{run-env-BE5xyCwy.mjs.map → run-env-Br4Bc7Vz.mjs.map} +1 -1
  95. package/dist/{runtime-paths-BRwDeBEg.mjs → runtime-paths-QGQJAekd.mjs} +1 -1
  96. package/dist/{runtime-paths-BRwDeBEg.mjs.map → runtime-paths-QGQJAekd.mjs.map} +1 -1
  97. package/dist/{search-BaTbyWjK.mjs → search-BOSphCJ1.mjs} +2 -2
  98. package/dist/{search-BaTbyWjK.mjs.map → search-BOSphCJ1.mjs.map} +1 -1
  99. package/dist/search-Bf3Kub3F.mjs +3 -0
  100. package/dist/{server-BY6AedmC.mjs → server-B71rem4q.mjs} +3 -3
  101. package/dist/{server-BY6AedmC.mjs.map → server-B71rem4q.mjs.map} +1 -1
  102. package/dist/{session-keepalive-CWGzLBVU.mjs → session-keepalive-Dgil9hjw.mjs} +7 -7
  103. package/dist/{session-keepalive-CWGzLBVU.mjs.map → session-keepalive-Dgil9hjw.mjs.map} +1 -1
  104. package/dist/{sources-BBe3AZ-W.mjs → sources-LcGptLA-.mjs} +1 -1
  105. package/dist/{sources-BBe3AZ-W.mjs.map → sources-LcGptLA-.mjs.map} +1 -1
  106. package/dist/{sqlite-CfKfuggn.mjs → sqlite-CAJcw2zL.mjs} +8 -7
  107. package/dist/sqlite-CAJcw2zL.mjs.map +1 -0
  108. package/dist/{state-n13uPK5B.mjs → state-CHltNjXI.mjs} +1 -1
  109. package/dist/{state-n13uPK5B.mjs.map → state-CHltNjXI.mjs.map} +1 -1
  110. package/dist/{state-CwlGAKgF.mjs → state-S9wlKarB.mjs} +1 -1
  111. package/dist/{stop-words-ClCwO8mZ.mjs → stop-words-BdQuaE9K.mjs} +1 -1
  112. package/dist/{stop-words-ClCwO8mZ.mjs.map → stop-words-BdQuaE9K.mjs.map} +1 -1
  113. package/dist/{themes-B2v7RNnA.mjs → themes-D1FQFthd.mjs} +3 -3
  114. package/dist/{themes-B2v7RNnA.mjs.map → themes-D1FQFthd.mjs.map} +1 -1
  115. package/dist/{tools-DFdPbX-X.mjs → tools-BFC-113F.mjs} +84 -68
  116. package/dist/tools-BFC-113F.mjs.map +1 -0
  117. package/dist/tools-BW7OXf-N.mjs +6 -0
  118. package/dist/{trace-BRflVyBi.mjs → trace-IDKK1VFs.mjs} +1 -1
  119. package/dist/{trace-BRflVyBi.mjs.map → trace-IDKK1VFs.mjs.map} +1 -1
  120. package/dist/{vault-indexer-mCFus_AM.mjs → vault-indexer-y6YC2-mh.mjs} +4 -3
  121. package/dist/vault-indexer-y6YC2-mh.mjs.map +1 -0
  122. package/dist/{wakeup-DO1O4udw.mjs → wakeup-CNZQZzsY.mjs} +3 -3
  123. package/dist/{wakeup-DO1O4udw.mjs.map → wakeup-CNZQZzsY.mjs.map} +1 -1
  124. package/dist/work-queue-worker-CkShUa0z.mjs +11 -0
  125. package/dist/{work-queue-worker-BbrC3I_R.mjs → work-queue-worker-Dy5FT2ak.mjs} +4 -4
  126. package/dist/{work-queue-worker-BbrC3I_R.mjs.map → work-queue-worker-Dy5FT2ak.mjs.map} +1 -1
  127. package/dist/{zettelkasten-BPxSOalU.mjs → zettelkasten-Dx-63BEk.mjs} +4 -4
  128. package/dist/{zettelkasten-BPxSOalU.mjs.map → zettelkasten-Dx-63BEk.mjs.map} +1 -1
  129. package/gemini-extension.json +1 -1
  130. package/package.json +1 -1
  131. package/pai-plugin.json +1 -0
  132. package/dist/async-DNVWtQl2.mjs.map +0 -1
  133. package/dist/daemon-C8-uzOuV.mjs +0 -19
  134. package/dist/detector-DgDn56j-.mjs +0 -3
  135. package/dist/federation-db-CJDw_Pwq.mjs +0 -3
  136. package/dist/helpers-Dj-B314s.mjs.map +0 -1
  137. package/dist/indexer-backend-BN9rpp9O.mjs +0 -5
  138. package/dist/main-resolver-C9-poMdi.mjs +0 -7
  139. package/dist/merge-BncjJTfb.mjs +0 -3
  140. package/dist/program-De1GVVM4.mjs.map +0 -1
  141. package/dist/query-feedback-COYCnvHQ.mjs +0 -3
  142. package/dist/registry-db-DwXpf7yF.mjs +0 -3
  143. package/dist/router-C5hPk9YP.mjs +0 -3
  144. package/dist/search-BQHL5wDr.mjs +0 -3
  145. package/dist/sqlite-CfKfuggn.mjs.map +0 -1
  146. package/dist/tools-B52IvaU6.mjs +0 -6
  147. package/dist/tools-DFdPbX-X.mjs.map +0 -1
  148. package/dist/vault-indexer-mCFus_AM.mjs.map +0 -1
  149. package/dist/work-queue-worker-z-AATZ2t.mjs +0 -11
  150. /package/dist/{indexer-backend-Cc-_NK3q.mjs → indexer-backend-DotpxHJd.mjs} +0 -0
@@ -0,0 +1,3 @@
1
+ import { n as oldFederationPath, r as openFederation, t as federationDbPath } from "./federation-db-CUgy0WYs.mjs";
2
+
3
+ export { openFederation };
@@ -1,4 +1,4 @@
1
- import { a as paiHomePath, o as resolvePaiFile } from "./pai-home-CeOIYltV.mjs";
1
+ import { a as paiHomePath, o as resolvePaiFile } from "./pai-home-UncxWxlX.mjs";
2
2
  import { mkdirSync } from "node:fs";
3
3
  import { homedir } from "node:os";
4
4
  import { dirname, join } from "node:path";
@@ -136,4 +136,4 @@ function openFederation(path = federationDbPath()) {
136
136
 
137
137
  //#endregion
138
138
  export { oldFederationPath as n, openFederation as r, federationDbPath as t };
139
- //# sourceMappingURL=federation-db-w8e-sKjp.mjs.map
139
+ //# sourceMappingURL=federation-db-CUgy0WYs.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"federation-db-w8e-sKjp.mjs","names":[],"sources":["../src/storage/sqlite/federation-schema.ts","../src/storage/sqlite/federation-db.ts"],"sourcesContent":["/**\n * SQLite DDL for the PAI federation database (federation.db).\n *\n * The federation DB is the cross-project search index — a single SQLite file\n * at ~/.pai/federation.db that holds chunked text from every registered\n * project's memory/ and Notes/ directories.\n *\n * Tables:\n * - memory_files — file-level metadata (hash, mtime, size) for change detection\n * - memory_chunks — chunked text with line numbers, tier classification, and optional embedding\n * - memory_fts — FTS5 virtual table backed by memory_chunks text\n *\n * Vault tables (vault_files, vault_aliases, vault_links, vault_name_index, vault_health)\n * have been migrated to Postgres (docker/init.sql) and are no longer created here.\n *\n * Schema version history:\n * v1 — initial schema (BM25 search only)\n * v2 — added embedding BLOB column to memory_chunks (Phase 2.5, vector search)\n * v3 — added vault tables (now removed — vault tables live in Postgres)\n */\n\nimport type { Database } from \"better-sqlite3\";\n\n/** Current schema version. Bump when adding new columns or tables. */\nexport const SCHEMA_VERSION = 5;\n\nexport const FEDERATION_SCHEMA_SQL = `\nPRAGMA journal_mode = WAL;\nPRAGMA foreign_keys = ON;\n\nCREATE TABLE IF NOT EXISTS memory_files (\n project_id INTEGER NOT NULL,\n path TEXT NOT NULL,\n source TEXT NOT NULL DEFAULT 'memory',\n tier TEXT NOT NULL DEFAULT 'topic',\n hash TEXT NOT NULL,\n mtime INTEGER NOT NULL,\n size INTEGER NOT NULL,\n PRIMARY KEY (project_id, path)\n);\n\nCREATE TABLE IF NOT EXISTS memory_chunks (\n id TEXT PRIMARY KEY,\n project_id INTEGER NOT NULL,\n source TEXT NOT NULL DEFAULT 'memory',\n tier TEXT NOT NULL DEFAULT 'topic',\n path TEXT NOT NULL,\n start_line INTEGER NOT NULL,\n end_line INTEGER NOT NULL,\n hash TEXT NOT NULL,\n text TEXT NOT NULL,\n updated_at INTEGER NOT NULL,\n last_accessed_at INTEGER,\n relevance_score REAL DEFAULT 0.5,\n embedding BLOB\n);\n\nCREATE VIRTUAL TABLE IF NOT EXISTS memory_fts USING fts5(\n text,\n id UNINDEXED,\n project_id UNINDEXED,\n path UNINDEXED,\n source UNINDEXED,\n tier UNINDEXED,\n start_line UNINDEXED,\n end_line UNINDEXED\n);\n\nCREATE INDEX IF NOT EXISTS idx_mc_project ON memory_chunks(project_id);\nCREATE INDEX IF NOT EXISTS idx_mc_source ON memory_chunks(project_id, source);\nCREATE INDEX IF NOT EXISTS idx_mc_tier ON memory_chunks(tier);\nCREATE INDEX IF NOT EXISTS idx_mf_project ON memory_files(project_id);\n\nCREATE TABLE IF NOT EXISTS kg_entities (\n entity_id TEXT PRIMARY KEY,\n tenant_id TEXT NOT NULL DEFAULT 'default',\n name TEXT NOT NULL,\n type TEXT NOT NULL DEFAULT 'unknown',\n description TEXT,\n first_seen INTEGER,\n last_seen INTEGER,\n mention_count INTEGER NOT NULL DEFAULT 1,\n feedback_weight REAL NOT NULL DEFAULT 0.5,\n UNIQUE(tenant_id, entity_id)\n);\n\nCREATE INDEX IF NOT EXISTS idx_kge_tenant ON kg_entities(tenant_id);\nCREATE INDEX IF NOT EXISTS idx_kge_name ON kg_entities(tenant_id, name);\nCREATE INDEX IF NOT EXISTS idx_kge_type ON kg_entities(tenant_id, type);\n`;\n\n/**\n * Apply the full federation schema to an open database.\n *\n * Idempotent — all statements use IF NOT EXISTS so calling this on an\n * already-initialised database is safe.\n *\n * Also runs any necessary migrations for existing databases (e.g. adding the\n * embedding column to an older schema that was created without it).\n */\nexport function initializeFederationSchema(db: Database): void {\n db.exec(FEDERATION_SCHEMA_SQL);\n runMigrations(db);\n}\n\n// ---------------------------------------------------------------------------\n// Migrations\n// ---------------------------------------------------------------------------\n\n/**\n * Apply incremental migrations to an existing database.\n *\n * Each migration is idempotent — safe to call on a database that has already\n * been migrated.\n */\nfunction runMigrations(db: Database): void {\n const columns = db.prepare(\"PRAGMA table_info(memory_chunks)\").all() as Array<{\n name: string;\n }>;\n\n // Migration v1→v2: add embedding BLOB column (schema v2, Phase 2.5)\n const hasEmbedding = columns.some((c) => c.name === \"embedding\");\n if (!hasEmbedding) {\n db.exec(\"ALTER TABLE memory_chunks ADD COLUMN embedding BLOB\");\n }\n\n // Create the partial index for embedded chunks (safe now that the column exists)\n db.exec(\n \"CREATE INDEX IF NOT EXISTS idx_mc_embedding ON memory_chunks(id) WHERE embedding IS NOT NULL\",\n );\n\n // Migration v4→v5: add last_accessed_at and relevance_score columns (QW2 + MR2)\n const hasLastAccessedAt = columns.some((c) => c.name === \"last_accessed_at\");\n if (!hasLastAccessedAt) {\n db.exec(\"ALTER TABLE memory_chunks ADD COLUMN last_accessed_at INTEGER\");\n }\n\n const hasRelevanceScore = columns.some((c) => c.name === \"relevance_score\");\n if (!hasRelevanceScore) {\n db.exec(\"ALTER TABLE memory_chunks ADD COLUMN relevance_score REAL DEFAULT 0.5\");\n }\n}\n","/**\n * Database connection helper for the PAI federation DB.\n *\n * Uses better-sqlite3 (synchronous API) to open or create federation.db.\n * On first open it runs the full DDL via initializeFederationSchema().\n */\n\nimport { mkdirSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\nimport BetterSqlite3 from \"better-sqlite3\";\nimport type { Database } from \"better-sqlite3\";\nimport { initializeFederationSchema } from \"./federation-schema.js\";\nimport { paiHomePath, resolvePaiFile } from \"../../config/pai-home.js\";\n\nexport type { Database };\n\n/** Old federation.db path, inside the ~/.pai/ directory (pre-2026-09-19). */\nexport function oldFederationPath(): string {\n return join(homedir(), \".pai\", \"federation.db\");\n}\n\n/** Federation DB path: PAI_HOME/federation.db if present, else the old\n * ~/.pai/federation.db (one-time stderr notice), else the new path. */\nexport function federationDbPath(): string {\n return resolvePaiFile(paiHomePath(\"federation.db\"), [oldFederationPath()], \"pai config migrate --federation\");\n}\n\n/**\n * Open (or create) the PAI federation database.\n *\n * @param path Absolute path to federation.db. Defaults to PAI_HOME/federation.db\n * (falling back to the pre-2026-09-19 ~/.pai/federation.db).\n * @returns An open better-sqlite3 Database instance.\n *\n * Side effects on first call:\n * - Creates the parent directory if it does not exist.\n * - Enables WAL journal mode.\n * - Runs initializeFederationSchema() to ensure tables exist.\n */\nexport function openFederation(path: string = federationDbPath()): Database {\n // Ensure the directory exists before SQLite tries to create the file\n mkdirSync(dirname(path), { recursive: true });\n\n const db = new BetterSqlite3(path);\n\n // WAL gives better concurrent read performance and crash safety\n db.pragma(\"journal_mode = WAL\");\n db.pragma(\"foreign_keys = ON\");\n\n // Apply schema (idempotent — all statements use IF NOT EXISTS)\n initializeFederationSchema(db);\n\n return db;\n}\n"],"mappings":";;;;;;;AA0BA,MAAa,wBAAwB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0ErC,SAAgB,2BAA2B,IAAoB;AAC7D,IAAG,KAAK,sBAAsB;AAC9B,eAAc,GAAG;;;;;;;;AAanB,SAAS,cAAc,IAAoB;CACzC,MAAM,UAAU,GAAG,QAAQ,mCAAmC,CAAC,KAAK;AAMpE,KAAI,CADiB,QAAQ,MAAM,MAAM,EAAE,SAAS,YAAY,CAE9D,IAAG,KAAK,sDAAsD;AAIhE,IAAG,KACD,+FACD;AAID,KAAI,CADsB,QAAQ,MAAM,MAAM,EAAE,SAAS,mBAAmB,CAE1E,IAAG,KAAK,gEAAgE;AAI1E,KAAI,CADsB,QAAQ,MAAM,MAAM,EAAE,SAAS,kBAAkB,CAEzE,IAAG,KAAK,wEAAwE;;;;;;;;;;;;ACzHpF,SAAgB,oBAA4B;AAC1C,QAAO,KAAK,SAAS,EAAE,QAAQ,gBAAgB;;;;AAKjD,SAAgB,mBAA2B;AACzC,QAAO,eAAe,YAAY,gBAAgB,EAAE,CAAC,mBAAmB,CAAC,EAAE,kCAAkC;;;;;;;;;;;;;;AAe/G,SAAgB,eAAe,OAAe,kBAAkB,EAAY;AAE1E,WAAU,QAAQ,KAAK,EAAE,EAAE,WAAW,MAAM,CAAC;CAE7C,MAAM,KAAK,IAAI,cAAc,KAAK;AAGlC,IAAG,OAAO,qBAAqB;AAC/B,IAAG,OAAO,oBAAoB;AAG9B,4BAA2B,GAAG;AAE9B,QAAO"}
1
+ {"version":3,"file":"federation-db-CUgy0WYs.mjs","names":[],"sources":["../src/storage/sqlite/federation-schema.ts","../src/storage/sqlite/federation-db.ts"],"sourcesContent":["/**\n * SQLite DDL for the PAI federation database (federation.db).\n *\n * The federation DB is the cross-project search index — a single SQLite file\n * at ~/.pai/federation.db that holds chunked text from every registered\n * project's memory/ and Notes/ directories.\n *\n * Tables:\n * - memory_files — file-level metadata (hash, mtime, size) for change detection\n * - memory_chunks — chunked text with line numbers, tier classification, and optional embedding\n * - memory_fts — FTS5 virtual table backed by memory_chunks text\n *\n * Vault tables (vault_files, vault_aliases, vault_links, vault_name_index, vault_health)\n * have been migrated to Postgres (docker/init.sql) and are no longer created here.\n *\n * Schema version history:\n * v1 — initial schema (BM25 search only)\n * v2 — added embedding BLOB column to memory_chunks (Phase 2.5, vector search)\n * v3 — added vault tables (now removed — vault tables live in Postgres)\n */\n\nimport type { Database } from \"better-sqlite3\";\n\n/** Current schema version. Bump when adding new columns or tables. */\nexport const SCHEMA_VERSION = 5;\n\nexport const FEDERATION_SCHEMA_SQL = `\nPRAGMA journal_mode = WAL;\nPRAGMA foreign_keys = ON;\n\nCREATE TABLE IF NOT EXISTS memory_files (\n project_id INTEGER NOT NULL,\n path TEXT NOT NULL,\n source TEXT NOT NULL DEFAULT 'memory',\n tier TEXT NOT NULL DEFAULT 'topic',\n hash TEXT NOT NULL,\n mtime INTEGER NOT NULL,\n size INTEGER NOT NULL,\n PRIMARY KEY (project_id, path)\n);\n\nCREATE TABLE IF NOT EXISTS memory_chunks (\n id TEXT PRIMARY KEY,\n project_id INTEGER NOT NULL,\n source TEXT NOT NULL DEFAULT 'memory',\n tier TEXT NOT NULL DEFAULT 'topic',\n path TEXT NOT NULL,\n start_line INTEGER NOT NULL,\n end_line INTEGER NOT NULL,\n hash TEXT NOT NULL,\n text TEXT NOT NULL,\n updated_at INTEGER NOT NULL,\n last_accessed_at INTEGER,\n relevance_score REAL DEFAULT 0.5,\n embedding BLOB\n);\n\nCREATE VIRTUAL TABLE IF NOT EXISTS memory_fts USING fts5(\n text,\n id UNINDEXED,\n project_id UNINDEXED,\n path UNINDEXED,\n source UNINDEXED,\n tier UNINDEXED,\n start_line UNINDEXED,\n end_line UNINDEXED\n);\n\nCREATE INDEX IF NOT EXISTS idx_mc_project ON memory_chunks(project_id);\nCREATE INDEX IF NOT EXISTS idx_mc_source ON memory_chunks(project_id, source);\nCREATE INDEX IF NOT EXISTS idx_mc_tier ON memory_chunks(tier);\nCREATE INDEX IF NOT EXISTS idx_mf_project ON memory_files(project_id);\n\nCREATE TABLE IF NOT EXISTS kg_entities (\n entity_id TEXT PRIMARY KEY,\n tenant_id TEXT NOT NULL DEFAULT 'default',\n name TEXT NOT NULL,\n type TEXT NOT NULL DEFAULT 'unknown',\n description TEXT,\n first_seen INTEGER,\n last_seen INTEGER,\n mention_count INTEGER NOT NULL DEFAULT 1,\n feedback_weight REAL NOT NULL DEFAULT 0.5,\n UNIQUE(tenant_id, entity_id)\n);\n\nCREATE INDEX IF NOT EXISTS idx_kge_tenant ON kg_entities(tenant_id);\nCREATE INDEX IF NOT EXISTS idx_kge_name ON kg_entities(tenant_id, name);\nCREATE INDEX IF NOT EXISTS idx_kge_type ON kg_entities(tenant_id, type);\n`;\n\n/**\n * Apply the full federation schema to an open database.\n *\n * Idempotent — all statements use IF NOT EXISTS so calling this on an\n * already-initialised database is safe.\n *\n * Also runs any necessary migrations for existing databases (e.g. adding the\n * embedding column to an older schema that was created without it).\n */\nexport function initializeFederationSchema(db: Database): void {\n db.exec(FEDERATION_SCHEMA_SQL);\n runMigrations(db);\n}\n\n// ---------------------------------------------------------------------------\n// Migrations\n// ---------------------------------------------------------------------------\n\n/**\n * Apply incremental migrations to an existing database.\n *\n * Each migration is idempotent — safe to call on a database that has already\n * been migrated.\n */\nfunction runMigrations(db: Database): void {\n const columns = db.prepare(\"PRAGMA table_info(memory_chunks)\").all() as Array<{\n name: string;\n }>;\n\n // Migration v1→v2: add embedding BLOB column (schema v2, Phase 2.5)\n const hasEmbedding = columns.some((c) => c.name === \"embedding\");\n if (!hasEmbedding) {\n db.exec(\"ALTER TABLE memory_chunks ADD COLUMN embedding BLOB\");\n }\n\n // Create the partial index for embedded chunks (safe now that the column exists)\n db.exec(\n \"CREATE INDEX IF NOT EXISTS idx_mc_embedding ON memory_chunks(id) WHERE embedding IS NOT NULL\",\n );\n\n // Migration v4→v5: add last_accessed_at and relevance_score columns (QW2 + MR2)\n const hasLastAccessedAt = columns.some((c) => c.name === \"last_accessed_at\");\n if (!hasLastAccessedAt) {\n db.exec(\"ALTER TABLE memory_chunks ADD COLUMN last_accessed_at INTEGER\");\n }\n\n const hasRelevanceScore = columns.some((c) => c.name === \"relevance_score\");\n if (!hasRelevanceScore) {\n db.exec(\"ALTER TABLE memory_chunks ADD COLUMN relevance_score REAL DEFAULT 0.5\");\n }\n}\n","/**\n * Database connection helper for the PAI federation DB.\n *\n * Uses better-sqlite3 (synchronous API) to open or create federation.db.\n * On first open it runs the full DDL via initializeFederationSchema().\n */\n\nimport { mkdirSync } from \"node:fs\";\nimport { homedir } from \"node:os\";\nimport { dirname, join } from \"node:path\";\nimport BetterSqlite3 from \"better-sqlite3\";\nimport type { Database } from \"better-sqlite3\";\nimport { initializeFederationSchema } from \"./federation-schema.js\";\nimport { paiHomePath, resolvePaiFile } from \"../../config/pai-home.js\";\n\nexport type { Database };\n\n/** Old federation.db path, inside the ~/.pai/ directory (pre-2026-09-19). */\nexport function oldFederationPath(): string {\n return join(homedir(), \".pai\", \"federation.db\");\n}\n\n/** Federation DB path: PAI_HOME/federation.db if present, else the old\n * ~/.pai/federation.db (one-time stderr notice), else the new path. */\nexport function federationDbPath(): string {\n return resolvePaiFile(paiHomePath(\"federation.db\"), [oldFederationPath()], \"pai config migrate --federation\");\n}\n\n/**\n * Open (or create) the PAI federation database.\n *\n * @param path Absolute path to federation.db. Defaults to PAI_HOME/federation.db\n * (falling back to the pre-2026-09-19 ~/.pai/federation.db).\n * @returns An open better-sqlite3 Database instance.\n *\n * Side effects on first call:\n * - Creates the parent directory if it does not exist.\n * - Enables WAL journal mode.\n * - Runs initializeFederationSchema() to ensure tables exist.\n */\nexport function openFederation(path: string = federationDbPath()): Database {\n // Ensure the directory exists before SQLite tries to create the file\n mkdirSync(dirname(path), { recursive: true });\n\n const db = new BetterSqlite3(path);\n\n // WAL gives better concurrent read performance and crash safety\n db.pragma(\"journal_mode = WAL\");\n db.pragma(\"foreign_keys = ON\");\n\n // Apply schema (idempotent — all statements use IF NOT EXISTS)\n initializeFederationSchema(db);\n\n return db;\n}\n"],"mappings":";;;;;;;AA0BA,MAAa,wBAAwB;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0ErC,SAAgB,2BAA2B,IAAoB;AAC7D,IAAG,KAAK,sBAAsB;AAC9B,eAAc,GAAG;;;;;;;;AAanB,SAAS,cAAc,IAAoB;CACzC,MAAM,UAAU,GAAG,QAAQ,mCAAmC,CAAC,KAAK;AAMpE,KAAI,CADiB,QAAQ,MAAM,MAAM,EAAE,SAAS,YAAY,CAE9D,IAAG,KAAK,sDAAsD;AAIhE,IAAG,KACD,+FACD;AAID,KAAI,CADsB,QAAQ,MAAM,MAAM,EAAE,SAAS,mBAAmB,CAE1E,IAAG,KAAK,gEAAgE;AAI1E,KAAI,CADsB,QAAQ,MAAM,MAAM,EAAE,SAAS,kBAAkB,CAEzE,IAAG,KAAK,wEAAwE;;;;;;;;;;;;ACzHpF,SAAgB,oBAA4B;AAC1C,QAAO,KAAK,SAAS,EAAE,QAAQ,gBAAgB;;;;AAKjD,SAAgB,mBAA2B;AACzC,QAAO,eAAe,YAAY,gBAAgB,EAAE,CAAC,mBAAmB,CAAC,EAAE,kCAAkC;;;;;;;;;;;;;;AAe/G,SAAgB,eAAe,OAAe,kBAAkB,EAAY;AAE1E,WAAU,QAAQ,KAAK,EAAE,EAAE,WAAW,MAAM,CAAC;CAE7C,MAAM,KAAK,IAAI,cAAc,KAAK;AAGlC,IAAG,OAAO,qBAAqB;AAC/B,IAAG,OAAO,oBAAoB;AAG9B,4BAA2B,GAAG;AAE9B,QAAO"}
@@ -3,212 +3,6 @@ import { homedir } from "node:os";
3
3
  import { basename, join, normalize } from "node:path";
4
4
  import { createHash } from "node:crypto";
5
5
 
6
- //#region src/utils/hash.ts
7
- /**
8
- * Shared hashing utilities. Centralises all SHA-256 usage so every module
9
- * obtains digests through the same function rather than inlining createHash.
10
- */
11
- /**
12
- * Compute a SHA-256 hex digest of the given string.
13
- * Aliased as sha256File for compatibility with existing call-sites that use
14
- * that name to hash file contents.
15
- */
16
- function sha256(content) {
17
- return createHash("sha256").update(content).digest("hex");
18
- }
19
- /** Alias kept for backwards compatibility with memory/indexer call-sites. */
20
- const sha256File = sha256;
21
-
22
- //#endregion
23
- //#region src/memory/chunker.ts
24
- /**
25
- * Markdown text chunker for the PAI memory engine.
26
- *
27
- * Splits markdown files into overlapping text segments suitable for BM25
28
- * full-text indexing. Respects heading boundaries where possible, falling
29
- * back to paragraph and sentence splitting when sections are large.
30
- */
31
- const DEFAULT_MAX_TOKENS = 400;
32
- const DEFAULT_OVERLAP = 80;
33
- /**
34
- * Approximate token count using a words * 1.3 heuristic.
35
- * Matches the OpenClaw estimate approach.
36
- */
37
- function estimateTokens(text) {
38
- const wordCount = text.split(/\s+/).filter(Boolean).length;
39
- return Math.ceil(wordCount * 1.3);
40
- }
41
- /**
42
- * Split content into sections delimited by ## or ### headings.
43
- * Each section starts at its heading line (or at line 1 for a preamble).
44
- */
45
- function splitBySections(lines) {
46
- const sections = [];
47
- let current = [];
48
- for (const line of lines) {
49
- if (/^#{1,3}\s/.test(line.text) && current.length > 0) {
50
- const text = current.map((l) => l.text).join("\n");
51
- sections.push({
52
- lines: current,
53
- tokens: estimateTokens(text)
54
- });
55
- current = [];
56
- }
57
- current.push(line);
58
- }
59
- if (current.length > 0) {
60
- const text = current.map((l) => l.text).join("\n");
61
- sections.push({
62
- lines: current,
63
- tokens: estimateTokens(text)
64
- });
65
- }
66
- return sections;
67
- }
68
- /**
69
- * Split a LineBlock by double-newline paragraph boundaries.
70
- */
71
- function splitByParagraphs(block) {
72
- const paragraphs = [];
73
- let current = [];
74
- for (const line of block.lines) if (line.text.trim() === "" && current.length > 0) {
75
- const text = current.map((l) => l.text).join("\n");
76
- paragraphs.push({
77
- lines: [...current],
78
- tokens: estimateTokens(text)
79
- });
80
- current = [];
81
- } else current.push(line);
82
- if (current.length > 0) {
83
- const text = current.map((l) => l.text).join("\n");
84
- paragraphs.push({
85
- lines: current,
86
- tokens: estimateTokens(text)
87
- });
88
- }
89
- return paragraphs.length > 0 ? paragraphs : [block];
90
- }
91
- /**
92
- * Split a LineBlock by sentence boundaries (. ! ?) when even paragraphs are
93
- * too large. Works character-by-character within joined lines.
94
- */
95
- function splitBySentences(block, maxTokens) {
96
- const sentences = block.lines.map((l) => l.text).join(" ").split(/(?<=[.!?])\s+(?=[A-Z"'])/g);
97
- const result = [];
98
- let accText = "";
99
- const startLine = block.lines[0]?.lineNo ?? 1;
100
- const endLine = block.lines[block.lines.length - 1]?.lineNo ?? startLine;
101
- const totalLines = endLine - startLine + 1;
102
- const linesPerSentence = Math.max(1, Math.floor(totalLines / Math.max(1, sentences.length)));
103
- let sentenceIdx = 0;
104
- let approxLine = startLine;
105
- const flush = () => {
106
- if (!accText.trim()) return;
107
- const endApprox = Math.min(approxLine + linesPerSentence - 1, endLine);
108
- result.push({
109
- lines: [{
110
- text: accText.trim(),
111
- lineNo: approxLine
112
- }],
113
- tokens: estimateTokens(accText)
114
- });
115
- approxLine = endApprox + 1;
116
- accText = "";
117
- };
118
- for (const sentence of sentences) {
119
- sentenceIdx++;
120
- const candidateText = accText ? accText + " " + sentence : sentence;
121
- if (estimateTokens(candidateText) > maxTokens && accText) {
122
- flush();
123
- accText = sentence;
124
- } else accText = candidateText;
125
- }
126
- flush();
127
- return result.length > 0 ? result : [block];
128
- }
129
- /**
130
- * Extract the last `overlapTokens` worth of text from a list of previously
131
- * emitted chunks to prepend to the next chunk.
132
- */
133
- function buildOverlapPrefix(chunks, overlapTokens) {
134
- if (overlapTokens <= 0 || chunks.length === 0) return [];
135
- const lastChunk = chunks[chunks.length - 1];
136
- if (!lastChunk) return [];
137
- const lines = lastChunk.text.split("\n");
138
- const kept = [];
139
- let acc = 0;
140
- for (let i = lines.length - 1; i >= 0; i--) {
141
- const lineTokens = estimateTokens(lines[i] ?? "");
142
- acc += lineTokens;
143
- kept.unshift(lines[i] ?? "");
144
- if (acc >= overlapTokens) break;
145
- }
146
- const startLine = lastChunk.endLine - kept.length + 1;
147
- return kept.map((text, idx) => ({
148
- text,
149
- lineNo: Math.max(lastChunk.startLine, startLine + idx)
150
- }));
151
- }
152
- /**
153
- * Chunk a markdown file into overlapping segments for BM25 indexing.
154
- *
155
- * Strategy:
156
- * 1. Split by headings (##, ###) as natural boundaries.
157
- * 2. If a section exceeds maxTokens, split by paragraphs.
158
- * 3. If a paragraph still exceeds maxTokens, split by sentences.
159
- * 4. Apply overlap: each chunk includes the last `overlap` tokens from the
160
- * previous chunk.
161
- */
162
- /**
163
- * Strip `<private>...</private>` blocks from content before indexing.
164
- * Content within these tags is excluded from memory — never stored or searched.
165
- */
166
- function stripPrivateTags(content) {
167
- return content.replace(/<private>[\s\S]*?<\/private>/gi, "");
168
- }
169
- function chunkMarkdown(content, opts) {
170
- const maxTokens = opts?.maxTokens ?? DEFAULT_MAX_TOKENS;
171
- const overlapTokens = opts?.overlap ?? DEFAULT_OVERLAP;
172
- content = stripPrivateTags(content);
173
- if (!content.trim()) return [];
174
- const sections = splitBySections(content.split("\n").map((text, idx) => ({
175
- text,
176
- lineNo: idx + 1
177
- })));
178
- const finalBlocks = [];
179
- for (const section of sections) {
180
- if (section.tokens <= maxTokens) {
181
- finalBlocks.push(section);
182
- continue;
183
- }
184
- const paras = splitByParagraphs(section);
185
- for (const para of paras) {
186
- if (para.tokens <= maxTokens) {
187
- finalBlocks.push(para);
188
- continue;
189
- }
190
- const sentences = splitBySentences(para, maxTokens);
191
- finalBlocks.push(...sentences);
192
- }
193
- }
194
- const chunks = [];
195
- for (const block of finalBlocks) {
196
- if (block.lines.length === 0) continue;
197
- const text = [...buildOverlapPrefix(chunks, overlapTokens), ...block.lines].map((l) => l.text).join("\n").trim();
198
- if (!text) continue;
199
- const startLine = block.lines[0]?.lineNo ?? 1;
200
- const endLine = block.lines[block.lines.length - 1]?.lineNo ?? startLine;
201
- chunks.push({
202
- text,
203
- startLine,
204
- endLine,
205
- hash: sha256(text)
206
- });
207
- }
208
- return chunks;
209
- }
210
-
211
- //#endregion
212
6
  //#region src/memory/indexer/helpers.ts
213
7
  /**
214
8
  * Shared helpers for the PAI memory indexers.
@@ -425,5 +219,5 @@ function parseSessionTitleChunk(fileName) {
425
219
  const INDEX_YIELD_EVERY = 10;
426
220
 
427
221
  //#endregion
428
- export { parseSessionTitleChunk as a, yieldToEventLoop as c, sha256 as d, sha256File as f, isPathTooBroadForContentScan as i, chunkMarkdown as l, chunkId as n, walkContentFiles as o, detectTier as r, walkMdFiles as s, INDEX_YIELD_EVERY as t, estimateTokens as u };
429
- //# sourceMappingURL=helpers-Dj-B314s.mjs.map
222
+ export { parseSessionTitleChunk as a, yieldToEventLoop as c, isPathTooBroadForContentScan as i, chunkId as n, walkContentFiles as o, detectTier as r, walkMdFiles as s, INDEX_YIELD_EVERY as t };
223
+ //# sourceMappingURL=helpers-BRCJg0G3.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"helpers-BRCJg0G3.mjs","names":[],"sources":["../src/memory/indexer/helpers.ts"],"sourcesContent":["/**\n * Shared helpers for the PAI memory indexers.\n *\n * Contains utilities used by both the sync (SQLite) and async (StorageBackend)\n * indexer paths: hashing, chunk ID generation, directory walking, and path guards.\n */\n\nimport { readdirSync, existsSync } from \"node:fs\";\nimport { createHash } from \"node:crypto\";\nimport { sha256File } from \"../../utils/hash.js\";\nimport { join, normalize } from \"node:path\";\nimport { homedir } from \"node:os\";\nimport { basename } from \"node:path\";\n\n// ---------------------------------------------------------------------------\n// Tier detection\n// ---------------------------------------------------------------------------\n\n/**\n * Classify a relative file path into one of the four memory tiers.\n *\n * Rules (in priority order):\n * - MEMORY.md anywhere in memory/ → 'evergreen'\n * - YYYY-MM-DD.md in memory/ → 'daily'\n * - anything else in memory/ → 'topic'\n * - anything in Notes/ → 'session'\n */\nexport function detectTier(\n relativePath: string,\n): \"evergreen\" | \"daily\" | \"topic\" | \"session\" {\n // Normalise to forward slashes and strip leading ./\n const p = relativePath.replace(/\\\\/g, \"/\").replace(/^\\.\\//, \"\");\n\n // Notes directory → session tier\n if (p.startsWith(\"Notes/\") || p === \"Notes\") {\n return \"session\";\n }\n\n const fileName = basename(p);\n\n // MEMORY.md (case-sensitive match) → evergreen\n if (fileName === \"MEMORY.md\") {\n return \"evergreen\";\n }\n\n // YYYY-MM-DD.md → daily\n if (/^\\d{4}-\\d{2}-\\d{2}\\.md$/.test(fileName)) {\n return \"daily\";\n }\n\n // Default for memory/ files\n return \"topic\";\n}\n\n// ---------------------------------------------------------------------------\n// Hashing and chunk ID generation\n// ---------------------------------------------------------------------------\n\n// sha256File imported from ../../utils/hash.js\nexport { sha256File } from \"../../utils/hash.js\";\n\n/**\n * Generate a deterministic chunk ID from its coordinates.\n * Format: sha256(\"projectId:path:chunkIndex:startLine:endLine\")\n *\n * The chunkIndex (0-based position within the file) is included so that\n * chunks with approximated line numbers (e.g. from splitBySentences) never\n * produce colliding IDs even when multiple chunks share the same startLine/endLine.\n */\nexport function chunkId(\n projectId: number,\n path: string,\n chunkIndex: number,\n startLine: number,\n endLine: number,\n): string {\n return createHash(\"sha256\")\n .update(`${projectId}:${path}:${chunkIndex}:${startLine}:${endLine}`)\n .digest(\"hex\");\n}\n\n// ---------------------------------------------------------------------------\n// Event loop yield\n// ---------------------------------------------------------------------------\n\n/**\n * Yield to the Node.js event loop so that IPC server can process requests\n * during long index runs.\n *\n * Uses setTimeout(10ms) rather than setImmediate — the 10ms pause gives the\n * event loop enough time to accept and process incoming IPC connections\n * (socket data, new connections, etc.). Without this, synchronous ONNX\n * inference blocks IPC for the full duration of each embedding (~50-100ms\n * per chunk).\n */\nexport function yieldToEventLoop(): Promise<void> {\n return new Promise((resolve) => setTimeout(resolve, 10));\n}\n\n// ---------------------------------------------------------------------------\n// Directory skip sets\n// ---------------------------------------------------------------------------\n\n/**\n * Directories to ALWAYS skip, at any depth, during any directory walk.\n * These are build artifacts, dependency trees, and VCS internals that\n * should never be indexed regardless of where they appear in the tree.\n */\nexport const ALWAYS_SKIP_DIRS = new Set([\n // Version control\n \".git\",\n // Dependency directories (any language)\n \"node_modules\",\n \"vendor\",\n \"Pods\", // CocoaPods (iOS/macOS)\n // Build / compile output\n \"dist\",\n \"build\",\n \"out\",\n \"DerivedData\", // Xcode\n \".next\", // Next.js\n // Python virtual environments and caches\n \".venv\",\n \"venv\",\n \"__pycache__\",\n // General caches\n \".cache\",\n \".bun\",\n // Backup snapshots (Carbon Copy Cloner, Time Machine, etc.)\n \"snaps\",\n \".Trashes\",\n // Worker worktrees — full repo copies checked out per worker run\n \"worktrees\",\n]);\n\n/**\n * Directories to skip when doing a root-level content scan.\n * These are either already handled by dedicated scans or should never be indexed.\n */\nexport const ROOT_SCAN_SKIP_DIRS = new Set([\n \"memory\",\n \"Notes\",\n \".claude\",\n \".DS_Store\",\n // Everything in ALWAYS_SKIP_DIRS is also excluded at root level\n ...ALWAYS_SKIP_DIRS,\n]);\n\n/**\n * Additional directories to skip at the content-scan level (first level below root).\n * These are common macOS/Linux home-directory or repo noise directories that are\n * never meaningful as project content.\n */\nexport const CONTENT_SCAN_SKIP_DIRS = new Set([\n // macOS home directory standard folders\n \"Library\",\n \"Applications\",\n \"Music\",\n \"Movies\",\n \"Pictures\",\n \"Desktop\",\n \"Downloads\",\n \"Public\",\n // Common dev noise\n \"coverage\",\n // Everything in ALWAYS_SKIP_DIRS is also excluded at this level\n ...ALWAYS_SKIP_DIRS,\n]);\n\n// ---------------------------------------------------------------------------\n// Directory walkers\n// ---------------------------------------------------------------------------\n\n/**\n * Safety cap: maximum number of .md files collected per project scan.\n * Prevents runaway scans on huge root paths (e.g. home directory).\n * Projects with more files than this are scanned up to the cap only.\n */\nconst MAX_FILES_PER_PROJECT = 5_000;\n\n/**\n * Maximum recursion depth for directory walks.\n * Prevents deep traversal of large directory trees (e.g. development repos).\n * Depth 0 = the given directory itself (no recursion).\n * Value 6 allows: root → subdirs → sub-subdirs → ... up to 6 levels.\n * Sufficient for memory/, Notes/, and typical docs structures.\n */\nconst MAX_WALK_DEPTH = 6;\n\n/**\n * Recursively collect all .md files under a directory.\n * Returns absolute paths. Stops early if the accumulated count hits the cap\n * or if the recursion depth exceeds MAX_WALK_DEPTH.\n *\n * @param dir Directory to scan.\n * @param acc Shared accumulator array (mutated in place for early exit).\n * @param cap Maximum number of files to collect (across all recursive calls).\n * @param depth Current recursion depth (0 = the initial call).\n */\nexport function walkMdFiles(\n dir: string,\n acc?: string[],\n cap = MAX_FILES_PER_PROJECT,\n depth = 0,\n): string[] {\n const results = acc ?? [];\n if (!existsSync(dir)) return results;\n if (results.length >= cap) return results;\n if (depth > MAX_WALK_DEPTH) return results;\n\n try {\n for (const entry of readdirSync(dir, { withFileTypes: true })) {\n if (results.length >= cap) break;\n if (entry.isSymbolicLink()) continue;\n // Skip known junk directories at every recursion depth\n if (ALWAYS_SKIP_DIRS.has(entry.name)) continue;\n const full = join(dir, entry.name);\n if (entry.isDirectory()) {\n walkMdFiles(full, results, cap, depth + 1);\n } else if (entry.isFile() && entry.name.endsWith(\".md\")) {\n results.push(full);\n }\n }\n } catch {\n // Unreadable directory — skip\n }\n return results;\n}\n\n/**\n * Recursively collect all .md files under rootPath, excluding directories\n * that are already covered by dedicated scans (memory/, Notes/) and\n * common noise directories (.git, node_modules, etc.).\n *\n * Returns absolute paths for files NOT already handled by the specific scanners.\n * Stops collecting once MAX_FILES_PER_PROJECT is reached.\n */\nexport function walkContentFiles(rootPath: string): string[] {\n if (!existsSync(rootPath)) return [];\n\n const results: string[] = [];\n try {\n for (const entry of readdirSync(rootPath, { withFileTypes: true })) {\n if (results.length >= MAX_FILES_PER_PROJECT) break;\n if (entry.isSymbolicLink()) continue;\n if (ROOT_SCAN_SKIP_DIRS.has(entry.name)) continue;\n if (CONTENT_SCAN_SKIP_DIRS.has(entry.name)) continue;\n\n const full = join(rootPath, entry.name);\n if (entry.isDirectory()) {\n walkMdFiles(full, results, MAX_FILES_PER_PROJECT);\n } else if (entry.isFile() && entry.name.endsWith(\".md\")) {\n // Skip root-level MEMORY.md — handled by the dedicated evergreen scan\n if (entry.name !== \"MEMORY.md\") {\n results.push(full);\n }\n }\n }\n } catch {\n // Unreadable directory — skip\n }\n return results;\n}\n\n// ---------------------------------------------------------------------------\n// Path safety guard\n// ---------------------------------------------------------------------------\n\n/** Paths that must never be indexed — system/temp dirs that can contain backup snapshots. */\nconst BLOCKED_ROOTS = new Set([\"/tmp\", \"/private/tmp\", \"/var\", \"/private/var\"]);\n\n/**\n * Returns true if rootPath should skip the recursive content scan.\n *\n * Skips content scanning for:\n * - The home directory itself or any ancestor (too broad — millions of files)\n * - Git repositories (code repos — index memory/ and Notes/ only, not all .md files)\n *\n * The content scan is still useful for Obsidian vaults, Notes folders, and\n * other doc-centric project trees where ALL markdown files are meaningful.\n *\n * The memory/, Notes/, and claude_notes_dir scans always run regardless.\n */\nexport function isPathTooBroadForContentScan(rootPath: string): boolean {\n const normalized = normalize(rootPath);\n\n // Block system/temp directories outright (CCC snapshots live here)\n if (BLOCKED_ROOTS.has(normalized)) return true;\n for (const blocked of BLOCKED_ROOTS) {\n if (normalized.startsWith(blocked + \"/\")) return true;\n }\n\n const home = homedir();\n\n // Skip the home directory itself or any ancestor of home\n if (home.startsWith(normalized) || normalized === \"/\") {\n return true;\n }\n\n // Skip home directory itself (depth 0)\n if (normalized.startsWith(home)) {\n const rel = normalized.slice(home.length).replace(/^\\//, \"\");\n const depth = rel ? rel.split(\"/\").length : 0;\n if (depth === 0) return true;\n }\n\n // Skip git repositories — content scan is only for doc-centric projects\n // (Obsidian vaults, knowledge bases). Code repos use memory/ and Notes/ only.\n if (existsSync(join(normalized, \".git\"))) {\n return true;\n }\n\n return false;\n}\n\n// ---------------------------------------------------------------------------\n// Session title parser\n// ---------------------------------------------------------------------------\n\nconst SESSION_TITLE_RE = /^(\\d{4})\\s*-\\s*(\\d{4}-\\d{2}-\\d{2})\\s*-\\s*(.+)\\.md$/;\n\n/**\n * Parse a session title from a Notes filename.\n * Format: \"NNNN - YYYY-MM-DD - Descriptive Title.md\"\n * Returns a synthetic chunk text like \"Session #0086 2026-02-23: Pai Daemon Background Service\"\n * or null if the filename doesn't match the expected pattern.\n */\nexport function parseSessionTitleChunk(fileName: string): string | null {\n const m = SESSION_TITLE_RE.exec(fileName);\n if (!m) return null;\n const [, num, date, title] = m;\n return `Session #${num} ${date}: ${title}`;\n}\n\n/** Number of files to process before yielding to the event loop inside indexProject. */\nexport const INDEX_YIELD_EVERY = 10;\n"],"mappings":"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"}
package/dist/index.d.mts CHANGED
@@ -433,18 +433,13 @@ declare function readPaiMarker(projectRoot: string): {
433
433
  declare function discoverPaiMarkers(searchDirs: string[]): PaiMarker[];
434
434
  //#endregion
435
435
  //#region src/memory/chunker.d.ts
436
- /**
437
- * Markdown text chunker for the PAI memory engine.
438
- *
439
- * Splits markdown files into overlapping text segments suitable for BM25
440
- * full-text indexing. Respects heading boundaries where possible, falling
441
- * back to paragraph and sentence splitting when sections are large.
442
- */
443
436
  interface Chunk {
444
437
  text: string;
445
438
  startLine: number;
446
439
  endLine: number;
447
440
  hash: string;
441
+ /** Ancestor heading titles, outermost first, including the chunk's own section heading. */
442
+ headingPath: string[];
448
443
  }
449
444
  interface ChunkOptions {
450
445
  /** Approximate maximum tokens per chunk. Default 400. */
@@ -457,6 +452,29 @@ interface ChunkOptions {
457
452
  * Matches the OpenClaw estimate approach.
458
453
  */
459
454
  declare function estimateTokens(text: string): number;
455
+ interface Heading {
456
+ level: number;
457
+ title: string;
458
+ line: number;
459
+ }
460
+ /**
461
+ * Parse ATX headings (levels 1-6) from lines. Lines inside ``` or ~~~ fences
462
+ * are never headings.
463
+ */
464
+ declare function parseHeadings(lines: string[]): Heading[];
465
+ interface OutlineNode {
466
+ level: number;
467
+ title: string;
468
+ startLine: number;
469
+ endLine: number;
470
+ tokens: number;
471
+ children: OutlineNode[];
472
+ }
473
+ /**
474
+ * Heading tree of a markdown file. A section ends on the line before the next
475
+ * heading of the same or higher level (or at EOF) and includes its children.
476
+ */
477
+ declare function buildOutline(content: string): OutlineNode[];
460
478
  declare function chunkMarkdown(content: string, opts?: ChunkOptions): Chunk[];
461
479
  //#endregion
462
480
  //#region src/memory/indexer/helpers.d.ts
@@ -548,5 +566,5 @@ interface RerankOptions {
548
566
  */
549
567
  declare function rerankResults(query: string, results: SearchResult[], opts?: RerankOptions): Promise<SearchResult[]>;
550
568
  //#endregion
551
- export { Chunk, ChunkOptions, IndexResult, MigrationResult, PaiMarker, RerankOptions, SearchOptions, SearchResult, buildFtsQuery, chunkMarkdown, configureRerankerModel, decodeEncodedDir, detectTier, discoverPaiMarkers, ensurePaiMarker, estimateTokens, migrateFromJson, parseSessionFilename, populateSlugs, readPaiMarker, rerankResults, slugify };
569
+ export { Chunk, ChunkOptions, Heading, IndexResult, MigrationResult, OutlineNode, PaiMarker, RerankOptions, SearchOptions, SearchResult, buildFtsQuery, buildOutline, chunkMarkdown, configureRerankerModel, decodeEncodedDir, detectTier, discoverPaiMarkers, ensurePaiMarker, estimateTokens, migrateFromJson, parseHeadings, parseSessionFilename, populateSlugs, readPaiMarker, rerankResults, slugify };
552
570
  //# sourceMappingURL=index.d.mts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.mts","names":[],"sources":["../src/registry/merge.ts","../src/storage/registry-interface.ts","../src/registry/migrate.ts","../src/registry/pai-marker.ts","../src/memory/chunker.ts","../src/memory/indexer/helpers.ts","../src/memory/indexer/types.ts","../src/memory/search.ts","../src/memory/reranker.ts"],"mappings":"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"}
1
+ {"version":3,"file":"index.d.mts","names":[],"sources":["../src/registry/merge.ts","../src/storage/registry-interface.ts","../src/registry/migrate.ts","../src/registry/pai-marker.ts","../src/memory/chunker.ts","../src/memory/indexer/helpers.ts","../src/memory/indexer/types.ts","../src/memory/search.ts","../src/memory/reranker.ts"],"mappings":";;AAcA;;;;;;;;;;;;UAAiB,SAAA;EACf,MAAA;EACA,QAAA;EACA,MAAA;EACA,QAAA;EAQU;EANV,QAAA;IAAY,EAAA;IAAY,IAAA;IAAc,EAAA;EAAA;EACtC,IAAA;EACA,OAAA;EACA,WAAA;EACA,KAAA;ECAU;EDEV,UAAA;AAAA;;;KCHU,WAAA;AAAA,KACA,aAAA;AAAA,KACA,aAAA;AAAA,KACA,QAAA;AAAA,KACA,iBAAA;AAAA,UAEK,OAAA;EACf,EAAA;EACA,IAAA;EACA,YAAA;EACA,SAAA;EACA,WAAA;EACA,IAAA,EAAM,WAAA;EACN,MAAA,EAAQ,aAAA;EACR,SAAA;EACA,aAAA;EACA,gBAAA;EACA,cAAA;EACA,UAAA;EACA,UAAA;EACA,WAAA;AAAA;AAAA,UAGe,uBAAA,SAAgC,OAAA;EAC/C,aAAA;EACA,WAAA;AAAA;AAAA,UAGe,OAAA;EACf,EAAA;EACA,UAAA;EACA,MAAA;EACA,IAAA;EACA,IAAA;EACA,KAAA;EACA,QAAA;EACA,MAAA,EAAQ,aAAA;EACR,iBAAA;EACA,WAAA;EACA,UAAA;EACA,SAAA;AAAA;AAAA,UAGe,kBAAA,SAA2B,OAAA;EAC1C,YAAA;EACA,YAAA;AAAA;AAAA,UAOe,UAAA;EACf,IAAA;EACA,WAAA;EACA,QAAA;EACA,UAAA;EACA,IAAA,GAAO,WAAA;EACP,MAAA,GAAS,aAAA;EACT,SAAA;EACA,SAAA;AAAA;;UAIe,uBAAA;EACf,QAAA;EACA,WAAA;EACA,QAAA;EACA,UAAA;EACA,SAAA;EACA,SAAA;AAAA;AAAA,UAGe,UAAA;EACf,SAAA;EACA,MAAA;EACA,IAAA;EACA,IAAA;EACA,KAAA;EACA,QAAA;EACA,MAAA,GAAS,aAAA;EACT,SAAA;AAAA;AAAA,UAGe,qBAAA;EACf,SAAA;EACA,SAAA;EACA,OAAA,EAAS,iBAAA;EACT,YAAA;EACA,UAAA;EACA,SAAA;AAAA;AAAA,UAGe,mBAAA;EACf,MAAA,GAAS,aAAA;EAnDM;EAqDf,eAAA;EACA,OAAA;EACA,OAAA;EACA,KAAA;AAAA;AAAA,UAGe,+BAAA;EACf,eAAA;AAAA;AAAA,UAGe,4BAAA;EACf,MAAA,GAAS,aAAA;EACT,KAAA;EACA,OAAA;AAAA;AAAA,UAGe,wBAAA;EAzDf;EA2DA,cAAA;AAAA;AAAA,UAGe,mBAAA;EACf,SAAA;EACA,MAAA,GAAS,aAAA;EACT,KAAA;AAAA;AAAA,UAGe,6BAAA;EACf,OAAA;EACA,KAAA;AAAA;AAAA,UAOe,eAAA;EAAA,SACN,WAAA;EAET,KAAA,IAAS,OAAA;EArET;;;;;EA4EA,aAAA,IAAiB,OAAA;EAMjB,cAAA,CAAe,EAAA,WAAa,OAAA,CAAQ,OAAA;EA3ErB;EA6Ef,gBAAA,CACE,IAAA,UACA,IAAA;IAAS,eAAA;IAA2B,SAAA;EAAA,IACnC,OAAA,CAAQ,OAAA;EACX,iBAAA,CACE,KAAA,UACA,IAAA;IAAS,eAAA;EAAA,IACR,OAAA,CAAQ,OAAA;EACX,oBAAA,CAAqB,QAAA,UAAkB,IAAA;IAAS,SAAA;EAAA,IAAuB,OAAA,CAAQ,OAAA;EAC/E,sBAAA,CAAuB,UAAA,UAAoB,IAAA;IAAS,SAAA;EAAA,IAAuB,OAAA,CAAQ,OAAA;EA9E1E;EAgFT,sBAAA,CAAuB,GAAA,WAAc,OAAA,CAAQ,OAAA;EAC7C,YAAA,CAAa,IAAA,GAAO,mBAAA,GAAsB,OAAA,CAAQ,OAAA;;EAElD,4BAAA,CAA6B,IAAA,GAAO,+BAAA,GAAkC,OAAA,CAAQ,OAAA;EAC9E,4BAAA,CAA6B,IAAA,GAAO,4BAAA,GAA+B,OAAA,CAAQ,uBAAA;EA/E3E;EAiFA,iBAAA,CAAkB,IAAA,GAAO,wBAAA,GAA2B,OAAA,CAClD,KAAA,CAAM,uBAAA;IAA4B,IAAA;EAAA;EAEpC,cAAA,CAAe,KAAA,UAAe,KAAA,YAAiB,OAAA,CAAQ,OAAA;EACvD,aAAA,CAAc,IAAA;IAAS,MAAA,GAAS,aAAA;EAAA,IAAkB,OAAA;EAClD,6BAAA,IAAiC,OAAA;EACjC,kBAAA,CAAmB,QAAA,WAAmB,OAAA;;EAEtC,gCAAA,CACE,gBAAA,UACA,IAAA,UACA,KAAA,YACC,OAAA,CAAQ,KAAA;IAAQ,IAAA;IAAc,SAAA;IAAmB,aAAA;EAAA;EAMpD,aAAA,CAAc,KAAA,EAAO,UAAA,GAAa,OAAA,CAAQ,OAAA;EAC1C,0BAAA,CAA2B,KAAA,EAAO,uBAAA,GAA0B,OAAA;IAAU,EAAA;IAAY,IAAA;IAAc,OAAA;EAAA;EAChG,iBAAA,CAAkB,EAAA,UAAY,KAAA;IAAS,QAAA;IAAmB,UAAA;EAAA,GAAuB,SAAA,YAAqB,OAAA;EACtG,mBAAA,CACE,EAAA,UACA,MAAA,EAAQ,aAAA,EACR,IAAA;IAAS,UAAA;IAA4B,SAAA;IAAoB,oBAAA,GAAuB,aAAA;EAAA,IAC/E,OAAA;EACH,wBAAA,CAAyB,EAAA,UAAY,WAAA,UAAqB,SAAA,YAAqB,OAAA;EAC/E,iBAAA,CAAkB,EAAA,UAAY,IAAA,EAAM,WAAA,EAAa,SAAA,YAAqB,OAAA;EACtE,0BAAA,CAA2B,EAAA,UAAY,MAAA,iBAAuB,SAAA,YAAqB,OAAA;EACnF,2BAAA,CAA4B,EAAA,UAAY,GAAA,iBAAoB,SAAA,YAAqB,OAAA;EACjF,yBAAA,CAA0B,EAAA,UAAY,IAAA,iBAAqB,SAAA,YAAqB,OAAA;EAChF,iBAAA,CAAkB,EAAA,UAAY,IAAA,UAAc,SAAA,WAAoB,OAAA;;EAEhE,qBAAA,CAAsB,YAAA,UAAsB,UAAA,WAAqB,OAAA;EACjE,aAAA,CAAc,EAAA,WAAa,OAAA;EApFZ;EAsFf,oBAAA,CAAqB,EAAA,WAAa,OAAA;EAMlC,kBAAA,CAAmB,SAAA,WAAoB,OAAA;EACvC,WAAA,IAAe,OAAA,CAAQ,KAAA;IAAQ,EAAA;IAAY,IAAA;EAAA;EA1F3C;EA4FA,SAAA,CAAU,IAAA,WAAe,OAAA;EACzB,aAAA,CAAc,SAAA,UAAmB,KAAA,WAAgB,OAAA;EACjD,aAAA,CAAc,SAAA,UAAmB,KAAA,WAAgB,OAAA;EACjD,gBAAA,CAAiB,SAAA,UAAmB,KAAA,WAAgB,OAAA;EACpD,kBAAA,CAAmB,aAAA,UAAuB,WAAA,UAAqB,KAAA,WAAgB,OAAA;EAC/E,iBAAA,CAAkB,SAAA,WAAoB,OAAA;EACtC,0BAAA,CAA2B,SAAA,WAAoB,OAAA;EAtFhC;EAwFf,eAAA,CAAgB,aAAA,UAAuB,WAAA,WAAsB,OAAA;EAC7D,2BAAA,CAA4B,SAAA,WAAoB,OAAA;EAtFvC;EA6FT,YAAA,CAAa,KAAA,WAAgB,OAAA;EAC7B,qBAAA,CAAsB,SAAA,WAAoB,OAAA;EAC1C,QAAA,CAAS,KAAA,UAAe,SAAA,WAAoB,OAAA;EAC5C,WAAA,CAAY,KAAA,WAAgB,OAAA;EAC5B,sBAAA,CAAuB,SAAA,WAAoB,OAAA;EA3EhC;EA6EX,kBAAA,CAAmB,aAAA,UAAuB,WAAA,WAAsB,OAAA;EA5Ee;EA8E/E,aAAA,CAAc,KAAA,UAAe,WAAA,WAAsB,OAAA;EA7EgC;EA+EnF,YAAA,IAAgB,OAAA,CAAQ,KAAA;IAAQ,KAAA;IAAe,IAAA;IAAc,SAAA;EAAA;EAM7D,cAAA,CAAe,EAAA,WAAa,OAAA,CAAQ,OAAA;EACpC,kBAAA,CAAmB,SAAA,UAAmB,MAAA,WAAiB,OAAA,CAAQ,OAAA;EAC/D,0BAAA,CAA2B,SAAA,WAAoB,OAAA,CAAQ,OAAA;EACvD,mBAAA,CAAoB,SAAA,WAAoB,OAAA;EACxC,sBAAA,CAAuB,SAAA,UAAmB,IAAA,GAAO,6BAAA,GAAgC,OAAA,CAAQ,OAAA;EAnFd;EAqF3E,YAAA,CAAa,IAAA,GAAO,mBAAA,GAAsB,OAAA,CAAQ,kBAAA;EAClD,qBAAA,CAAsB,SAAA,UAAmB,QAAA,WAAmB,OAAA;IAAU,EAAA;EAAA;EACtE,kBAAA,CAAmB,SAAA,UAAmB,MAAA,WAAiB,OAAA;EACvD,aAAA,CAAc,KAAA,EAAO,UAAA,GAAa,OAAA,CAAQ,OAAA;EAnFK;EAqF/C,qBAAA,CAAsB,KAAA,EAAO,UAAA,GAAa,OAAA;EAC1C,iBAAA,CAAkB,EAAA,UAAY,KAAA;IAAS,IAAA;IAAe,KAAA;IAAgB,QAAA;EAAA,IAAsB,OAAA;EAC5F,mBAAA,CAAoB,EAAA,UAAY,MAAA,UAAgB,IAAA;IAAS,QAAA;EAAA,IAAsB,OAAA;EAC/E,qBAAA,CAAsB,EAAA,UAAY,QAAA,WAAmB,OAAA;EAxEO;EA0E5D,mBAAA,CAAoB,EAAA,UAAY,MAAA,EAAQ,aAAA,EAAe,IAAA;IAAS,QAAA;EAAA,IAAsB,OAAA;EACtF,oBAAA,CAAqB,EAAA,UAAY,SAAA,UAAmB,IAAA;IAAS,MAAA;EAAA,IAAoB,OAAA;EACjF,aAAA,CAAc,EAAA,WAAa,OAAA;EAnEwD;EAqEnF,qBAAA,CAAsB,MAAA,UAAgB,MAAA,WAAiB,OAAA;EACvD,uBAAA,CAAwB,SAAA,WAAoB,OAAA;EAnEoB;EAqEhE,aAAA,IAAiB,OAAA;EACjB,wBAAA,CAAyB,SAAA,WAAoB,OAAA;EAC7C,6BAAA,IAAiC,OAAA;EAMjC,iBAAA,CAAkB,SAAA,WAAoB,OAAA;EACtC,aAAA,CAAc,SAAA,UAAmB,KAAA,WAAgB,OAAA;EACjD,aAAA,CAAc,SAAA,UAAmB,KAAA,WAAgB,OAAA;EACjD,gBAAA,CAAiB,SAAA,UAAmB,KAAA,WAAgB,OAAA;EACpD,kBAAA,CAAmB,aAAA,UAAuB,WAAA,UAAqB,KAAA,WAAgB,OAAA;EAC/E,kBAAA,CAAmB,SAAA,WAAoB,OAAA;EAMvC,OAAA,CAAQ,KAAA;IAAS,SAAA;IAAmB,eAAA;IAAyB,QAAA,EAAU,QAAA;IAAU,SAAA;EAAA,IAAsB,OAAA;EACvG,mBAAA,CAAoB,SAAA,WAAoB,OAAA,CAAQ,KAAA;IAAQ,EAAA;IAAY,iBAAA;EAAA;EACpE,mBAAA,CAAoB,SAAA,WAAoB,OAAA,CAAQ,KAAA;IAAQ,EAAA;IAAY,UAAA;EAAA;EACpE,UAAA,CAAW,SAAA,UAAmB,eAAA,WAA0B,OAAA;EACxD,UAAA,CAAW,EAAA,WAAa,OAAA;EACxB,2BAAA,CAA4B,SAAA,WAAoB,OAAA;EA9Ce;EAgD/D,8BAAA,CAA+B,SAAA,WAAoB,OAAA;EACnD,YAAA,CAAa,EAAA,UAAY,eAAA,WAA0B,OAAA;EACnD,iBAAA,CAAkB,EAAA,UAAY,SAAA,WAAoB,OAAA;EAhDV;EAkDxC,mBAAA,CAAoB,aAAA,UAAuB,WAAA,WAAsB,OAAA;EACjE,oBAAA,CAAqB,SAAA,WAAoB,OAAA;EAlDwC;EAoDjF,yBAAA,CAA0B,SAAA,WAAoB,OAAA;EAM9C,mBAAA,CAAoB,KAAA,EAAO,qBAAA,GAAwB,OAAA;EACnD,6BAAA,CAA8B,SAAA,WAAoB,OAAA;EAxDU;EA0D5D,2BAAA,CAA4B,aAAA,UAAuB,WAAA,WAAsB,OAAA;EACzE,2BAAA,CAA4B,aAAA,UAAuB,WAAA,WAAsB,OAAA;EACzE,8BAAA,CAA+B,SAAA,WAAoB,OAAA;EAMnD,gBAAA,CAAiB,QAAA,UAAkB,QAAA,WAAmB,OAAA,CAAQ,SAAA;EAC9D,iBAAA,CAAkB,IAAA,EAAM,SAAA,GAAY,OAAA;AAAA;;;;;;;AA3TtC;;;;;AACA;;;;;AAEA;;;;;;;;;;;;iBC6FgB,gBAAA,CACd,OAAA,UACA,SAAA,GAAY,GAAA;;;;;;iBAoCE,OAAA,CAAQ,KAAA;AAAA,UAgBd,aAAA;EACR,MAAA;EACA,IAAA;EACA,IAAA;EACA,KAAA;EACA,QAAA;AAAA;;;;;;iBAcc,oBAAA,CACd,QAAA,WACC,aAAA;AAAA,UAiCc,eAAA;EACf,gBAAA;EACA,eAAA;EACA,gBAAA;EACA,MAAA;AAAA;;;;;;;;;;;iBAaoB,eAAA,CACpB,OAAA,EAAS,eAAA,EACT,YAAA,YACC,OAAA,CAAQ,eAAA;;;UC5KM,SAAA;EHpDL;EGsDV,IAAA;;EAEA,IAAA;;EAEA,WAAA;AAAA;;;;AF5DF;;;;;AACA;;;iBE6MgB,eAAA,CACd,WAAA,UACA,IAAA,UACA,WAAA;;AF/MF;;;iBEqRgB,aAAA,CACd,WAAA;EACG,IAAA;EAAc,UAAA;EAAoB,MAAA;AAAA;;;AFpRvC;;;;;;;;iBEiTgB,kBAAA,CAAmB,UAAA,aAAuB,SAAA;;;UC5TzC,KAAA;EACf,IAAA;EACA,SAAA;EACA,OAAA;EACA,IAAA;EJEA;EIAA,WAAA;AAAA;AAAA,UAGe,YAAA;;EAEf,SAAA;;EAEA,OAAA;AAAA;;;;AHPF;iBGiBgB,cAAA,CAAe,IAAA;AAAA,UAWd,OAAA;EACf,KAAA;EACA,KAAA;EACA,IAAA;AAAA;;;;AH7BF;iBGoCgB,aAAA,CAAc,KAAA,aAAkB,OAAA;AAAA,UAgC/B,WAAA;EACf,KAAA;EACA,KAAA;EACA,SAAA;EACA,OAAA;EACA,MAAA;EACA,QAAA,EAAU,WAAA;AAAA;AHvEZ;;;;AAAA,iBG8EgB,YAAA,CAAa,OAAA,WAAkB,WAAA;AAAA,iBA6M/B,aAAA,CAAc,OAAA,UAAiB,IAAA,GAAO,YAAA,GAAe,KAAA;;;;AJ1SrE;;;;;;;;;;;;;;iBKagB,UAAA,CACd,YAAA;;;;ALdF;;UMViB,WAAA;EACf,cAAA;EACA,aAAA;EACA,YAAA;AAAA;;;UCOe,YAAA;EACf,SAAA;EACA,WAAA;EACA,IAAA;EACA,SAAA;EACA,OAAA;EACA,OAAA;EACA,KAAA;EACA,IAAA;EACA,MAAA;EACA,SAAA;EACA,cAAA;EACA,OAAA;AAAA;AAAA,UAGe,aAAA;;EAEf,UAAA;ENRqB;EMUrB,OAAA;ENVqB;EMYrB,KAAA;ENXU;EMaV,UAAA;;EAEA,QAAA;AAAA;AAAA,iBA0Cc,aAAA,CAAc,KAAA;;;;;iBA8BR,aAAA,CACpB,OAAA,EAAS,YAAA,IACT,QAAA,EAAU,eAAA,GACT,OAAA,CAAQ,YAAA;;;;;;;iBC5EK,sBAAA,CAAuB,KAAA;AAAA,UAoCtB,aAAA;EPnDL;EOqDV,IAAA;;;;APpDF;;EO0DE,aAAA;AAAA;;APzDF;;;;;AACA;;;iBOoEsB,aAAA,CACpB,KAAA,UACA,OAAA,EAAS,YAAA,IACT,IAAA,GAAO,aAAA,GACN,OAAA,CAAQ,YAAA"}
package/dist/index.mjs CHANGED
@@ -1,8 +1,9 @@
1
1
  import "./utils-C9HsYDpO.mjs";
2
2
  import { a as slugify, i as parseSessionFilename, n as decodeEncodedDir, r as migrateFromJson } from "./migrate-BD7D8EEh.mjs";
3
3
  import { n as ensurePaiMarker, r as readPaiMarker, t as discoverPaiMarkers } from "./pai-marker-D1MMswkz.mjs";
4
- import { l as chunkMarkdown, r as detectTier, u as estimateTokens } from "./helpers-Dj-B314s.mjs";
5
- import { i as populateSlugs, n as buildFtsQuery } from "./search-BaTbyWjK.mjs";
6
- import { n as rerankResults, t as configureRerankerModel } from "./reranker-CRVawoEh.mjs";
4
+ import { a as parseHeadings, n as chunkMarkdown, r as estimateTokens, t as buildOutline } from "./chunker-BH4i-F2b.mjs";
5
+ import { r as detectTier } from "./helpers-BRCJg0G3.mjs";
6
+ import { i as populateSlugs, n as buildFtsQuery } from "./search-BOSphCJ1.mjs";
7
+ import { n as rerankResults, t as configureRerankerModel } from "./reranker-DKv80KO5.mjs";
7
8
 
8
- export { buildFtsQuery, chunkMarkdown, configureRerankerModel, decodeEncodedDir, detectTier, discoverPaiMarkers, ensurePaiMarker, estimateTokens, migrateFromJson, parseSessionFilename, populateSlugs, readPaiMarker, rerankResults, slugify };
9
+ export { buildFtsQuery, buildOutline, chunkMarkdown, configureRerankerModel, decodeEncodedDir, detectTier, discoverPaiMarkers, ensurePaiMarker, estimateTokens, migrateFromJson, parseHeadings, parseSessionFilename, populateSlugs, readPaiMarker, rerankResults, slugify };
@@ -0,0 +1,5 @@
1
+ import { a as parseSessionTitleChunk } from "./helpers-BRCJg0G3.mjs";
2
+ import { i as indexProjectWithBackend, n as indexAllWithBackend, r as indexFileWithBackend, t as embedChunksWithBackend } from "./async-CY_cd_8j.mjs";
3
+ import "./indexer-backend-DotpxHJd.mjs";
4
+
5
+ export { embedChunksWithBackend };
@@ -1,4 +1,4 @@
1
- import { i as paiSocketPath } from "./runtime-paths-BRwDeBEg.mjs";
1
+ import { i as paiSocketPath } from "./runtime-paths-QGQJAekd.mjs";
2
2
  import { randomUUID } from "node:crypto";
3
3
  import { connect } from "node:net";
4
4
 
@@ -155,4 +155,4 @@ var PaiClient = class {
155
155
 
156
156
  //#endregion
157
157
  export { PaiClient as t };
158
- //# sourceMappingURL=ipc-client-Dasm515c.mjs.map
158
+ //# sourceMappingURL=ipc-client-EhWY8XL0.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"ipc-client-Dasm515c.mjs","names":[],"sources":["../src/daemon/ipc-client.ts"],"sourcesContent":["/**\n * ipc-client.ts — IPC client for the PAI Daemon MCP shim\n *\n * PaiClient connects to the Unix Domain Socket served by daemon.ts\n * and forwards tool calls to the daemon. Uses a fresh socket connection per\n * call (connect → write JSON + newline → read response line → parse → destroy).\n * This keeps the client stateless and avoids connection management complexity.\n *\n * Adapted from the Coogle ipc-client pattern (which was adapted from Whazaa).\n */\n\nimport { connect, Socket } from \"node:net\";\nimport { randomUUID } from \"node:crypto\";\nimport type {\n NotificationConfig,\n NotificationMode,\n NotificationEvent,\n SendResult,\n} from \"../notifications/types.js\";\nimport type { TopicCheckParams, TopicCheckResult } from \"../topics/detector.js\";\nimport type { AutoRouteResult } from \"../session/auto-route.js\";\nimport { paiSocketPath } from \"../runtime-paths.js\";\n\n// ---------------------------------------------------------------------------\n// Protocol types\n// ---------------------------------------------------------------------------\n\n/** Default socket path */\nexport const IPC_SOCKET_PATH = paiSocketPath();\n\n/** Timeout for IPC calls (60 seconds) */\nconst IPC_TIMEOUT_MS = 60_000;\n\ninterface IpcRequest {\n id: string;\n method: string;\n params: Record<string, unknown>;\n}\n\ninterface IpcResponse {\n id: string;\n ok: boolean;\n result?: unknown;\n error?: string;\n}\n\n// ---------------------------------------------------------------------------\n// Client\n// ---------------------------------------------------------------------------\n\n/**\n * Thin IPC proxy that forwards tool calls to pai-daemon over a Unix\n * Domain Socket. Each call opens a fresh connection, sends one NDJSON request,\n * reads the response, and closes. Stateless and simple.\n */\nexport class PaiClient {\n private readonly socketPath: string;\n\n constructor(socketPath?: string) {\n this.socketPath = socketPath ?? IPC_SOCKET_PATH;\n }\n\n /**\n * Call a PAI tool by name with the given params.\n * Returns the tool result or throws on error.\n *\n * `timeoutMs` overrides the default 60s wait — for a caller on a hook's\n * critical path (e.g. the threshold-triggered handover enqueue in\n * `cli/commands/session/autosave.ts`) where the actual work happens later,\n * asynchronously, in the daemon's worker loop, and only the cheap\n * enqueue handshake itself should ever be waited on.\n */\n async call(method: string, params: Record<string, unknown>, timeoutMs?: number): Promise<unknown> {\n return this.send(method, params, timeoutMs);\n }\n\n /**\n * Check daemon status.\n */\n async status(): Promise<Record<string, unknown>> {\n const result = await this.send(\"status\", {});\n return result as Record<string, unknown>;\n }\n\n /**\n * Trigger an immediate index run.\n */\n async triggerIndex(): Promise<void> {\n await this.send(\"index_now\", {});\n }\n\n // -------------------------------------------------------------------------\n // Notification methods\n // -------------------------------------------------------------------------\n\n /**\n * Get the current notification config from the daemon.\n */\n async getNotificationConfig(): Promise<{\n config: NotificationConfig;\n activeChannels: string[];\n }> {\n const result = await this.send(\"notification_get_config\", {});\n return result as { config: NotificationConfig; activeChannels: string[] };\n }\n\n /**\n * Patch the notification config on the daemon (and persist to disk).\n */\n async setNotificationConfig(patch: {\n mode?: NotificationMode;\n channels?: Partial<NotificationConfig[\"channels\"]>;\n routing?: Partial<NotificationConfig[\"routing\"]>;\n }): Promise<{ config: NotificationConfig }> {\n const result = await this.send(\"notification_set_config\", patch as Record<string, unknown>);\n return result as { config: NotificationConfig };\n }\n\n /**\n * Send a notification via the daemon (routes to configured channels).\n */\n async sendNotification(payload: {\n event: NotificationEvent;\n message: string;\n title?: string;\n }): Promise<SendResult> {\n const result = await this.send(\"notification_send\", payload as Record<string, unknown>);\n return result as SendResult;\n }\n\n // -------------------------------------------------------------------------\n // Topic detection methods\n // -------------------------------------------------------------------------\n\n /**\n * Check whether the provided context text has drifted to a different project\n * than the session's current routing.\n */\n async topicCheck(params: TopicCheckParams): Promise<TopicCheckResult> {\n const result = await this.send(\"topic_check\", params as unknown as Record<string, unknown>);\n return result as TopicCheckResult;\n }\n\n // -------------------------------------------------------------------------\n // Session routing methods\n // -------------------------------------------------------------------------\n\n /**\n * Automatically detect which project a session belongs to.\n * Tries path match, PAI.md marker walk, then topic detection (if context given).\n */\n async sessionAutoRoute(params: {\n cwd?: string;\n context?: string;\n }): Promise<AutoRouteResult | null> {\n // session_auto_route returns a ToolResult (content array). Extract the text\n // and parse JSON from it.\n const result = await this.send(\"session_auto_route\", params as Record<string, unknown>);\n const toolResult = result as { content?: Array<{ text: string }>; isError?: boolean };\n if (toolResult.isError) return null;\n const text = toolResult.content?.[0]?.text ?? \"\";\n // Text is either JSON (on match) or a human-readable \"no match\" message\n try {\n return JSON.parse(text) as AutoRouteResult;\n } catch {\n return null;\n }\n }\n\n // -------------------------------------------------------------------------\n // Internal transport\n // -------------------------------------------------------------------------\n\n /**\n * Send a single IPC request and wait for the response.\n * Opens a new socket connection per call — simple and reliable.\n */\n private send(\n method: string,\n params: Record<string, unknown>,\n timeoutMs: number = IPC_TIMEOUT_MS\n ): Promise<unknown> {\n const socketPath = this.socketPath;\n\n return new Promise((resolve, reject) => {\n let socket: Socket | null = null;\n let done = false;\n let buffer = \"\";\n let timer: ReturnType<typeof setTimeout> | null = null;\n\n function finish(error: Error | null, value?: unknown): void {\n if (done) return;\n done = true;\n if (timer !== null) {\n clearTimeout(timer);\n timer = null;\n }\n try {\n socket?.destroy();\n } catch {\n // ignore\n }\n if (error) {\n reject(error);\n } else {\n resolve(value);\n }\n }\n\n socket = connect(socketPath, () => {\n const request: IpcRequest = {\n id: randomUUID(),\n method,\n params,\n };\n socket!.write(JSON.stringify(request) + \"\\n\");\n });\n\n socket.on(\"data\", (chunk: Buffer) => {\n buffer += chunk.toString();\n const nl = buffer.indexOf(\"\\n\");\n if (nl === -1) return;\n\n const line = buffer.slice(0, nl);\n buffer = buffer.slice(nl + 1);\n\n let response: IpcResponse;\n try {\n response = JSON.parse(line) as IpcResponse;\n } catch {\n finish(new Error(`IPC parse error: ${line}`));\n return;\n }\n\n if (!response.ok) {\n finish(new Error(response.error ?? \"IPC call failed\"));\n } else {\n finish(null, response.result);\n }\n });\n\n socket.on(\"error\", (e: NodeJS.ErrnoException) => {\n if (e.code === \"ENOENT\" || e.code === \"ECONNREFUSED\") {\n finish(\n new Error(\n \"PAI daemon not running. Start it with: pai daemon serve\"\n )\n );\n } else {\n finish(e);\n }\n });\n\n socket.on(\"end\", () => {\n if (!done) {\n finish(new Error(\"IPC connection closed before response\"));\n }\n });\n\n timer = setTimeout(() => {\n finish(new Error(`IPC call timed out after ${timeoutMs}ms`));\n }, timeoutMs);\n });\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;AA4BA,MAAa,kBAAkB,eAAe;;AAG9C,MAAM,iBAAiB;;;;;;AAwBvB,IAAa,YAAb,MAAuB;CACrB,AAAiB;CAEjB,YAAY,YAAqB;AAC/B,OAAK,aAAa,cAAc;;;;;;;;;;;;CAalC,MAAM,KAAK,QAAgB,QAAiC,WAAsC;AAChG,SAAO,KAAK,KAAK,QAAQ,QAAQ,UAAU;;;;;CAM7C,MAAM,SAA2C;AAE/C,SADe,MAAM,KAAK,KAAK,UAAU,EAAE,CAAC;;;;;CAO9C,MAAM,eAA8B;AAClC,QAAM,KAAK,KAAK,aAAa,EAAE,CAAC;;;;;CAUlC,MAAM,wBAGH;AAED,SADe,MAAM,KAAK,KAAK,2BAA2B,EAAE,CAAC;;;;;CAO/D,MAAM,sBAAsB,OAIgB;AAE1C,SADe,MAAM,KAAK,KAAK,2BAA2B,MAAiC;;;;;CAO7F,MAAM,iBAAiB,SAIC;AAEtB,SADe,MAAM,KAAK,KAAK,qBAAqB,QAAmC;;;;;;CAYzF,MAAM,WAAW,QAAqD;AAEpE,SADe,MAAM,KAAK,KAAK,eAAe,OAA6C;;;;;;CAY7F,MAAM,iBAAiB,QAGa;EAIlC,MAAM,aADS,MAAM,KAAK,KAAK,sBAAsB,OAAkC;AAEvF,MAAI,WAAW,QAAS,QAAO;EAC/B,MAAM,OAAO,WAAW,UAAU,IAAI,QAAQ;AAE9C,MAAI;AACF,UAAO,KAAK,MAAM,KAAK;UACjB;AACN,UAAO;;;;;;;CAYX,AAAQ,KACN,QACA,QACA,YAAoB,gBACF;EAClB,MAAM,aAAa,KAAK;AAExB,SAAO,IAAI,SAAS,SAAS,WAAW;GACtC,IAAI,SAAwB;GAC5B,IAAI,OAAO;GACX,IAAI,SAAS;GACb,IAAI,QAA8C;GAElD,SAAS,OAAO,OAAqB,OAAuB;AAC1D,QAAI,KAAM;AACV,WAAO;AACP,QAAI,UAAU,MAAM;AAClB,kBAAa,MAAM;AACnB,aAAQ;;AAEV,QAAI;AACF,aAAQ,SAAS;YACX;AAGR,QAAI,MACF,QAAO,MAAM;QAEb,SAAQ,MAAM;;AAIlB,YAAS,QAAQ,kBAAkB;IACjC,MAAM,UAAsB;KAC1B,IAAI,YAAY;KAChB;KACA;KACD;AACD,WAAQ,MAAM,KAAK,UAAU,QAAQ,GAAG,KAAK;KAC7C;AAEF,UAAO,GAAG,SAAS,UAAkB;AACnC,cAAU,MAAM,UAAU;IAC1B,MAAM,KAAK,OAAO,QAAQ,KAAK;AAC/B,QAAI,OAAO,GAAI;IAEf,MAAM,OAAO,OAAO,MAAM,GAAG,GAAG;AAChC,aAAS,OAAO,MAAM,KAAK,EAAE;IAE7B,IAAI;AACJ,QAAI;AACF,gBAAW,KAAK,MAAM,KAAK;YACrB;AACN,4BAAO,IAAI,MAAM,oBAAoB,OAAO,CAAC;AAC7C;;AAGF,QAAI,CAAC,SAAS,GACZ,QAAO,IAAI,MAAM,SAAS,SAAS,kBAAkB,CAAC;QAEtD,QAAO,MAAM,SAAS,OAAO;KAE/B;AAEF,UAAO,GAAG,UAAU,MAA6B;AAC/C,QAAI,EAAE,SAAS,YAAY,EAAE,SAAS,eACpC,wBACE,IAAI,MACF,0DACD,CACF;QAED,QAAO,EAAE;KAEX;AAEF,UAAO,GAAG,aAAa;AACrB,QAAI,CAAC,KACH,wBAAO,IAAI,MAAM,wCAAwC,CAAC;KAE5D;AAEF,WAAQ,iBAAiB;AACvB,2BAAO,IAAI,MAAM,4BAA4B,UAAU,IAAI,CAAC;MAC3D,UAAU;IACb"}
1
+ {"version":3,"file":"ipc-client-EhWY8XL0.mjs","names":[],"sources":["../src/daemon/ipc-client.ts"],"sourcesContent":["/**\n * ipc-client.ts — IPC client for the PAI Daemon MCP shim\n *\n * PaiClient connects to the Unix Domain Socket served by daemon.ts\n * and forwards tool calls to the daemon. Uses a fresh socket connection per\n * call (connect → write JSON + newline → read response line → parse → destroy).\n * This keeps the client stateless and avoids connection management complexity.\n *\n * Adapted from the Coogle ipc-client pattern (which was adapted from Whazaa).\n */\n\nimport { connect, Socket } from \"node:net\";\nimport { randomUUID } from \"node:crypto\";\nimport type {\n NotificationConfig,\n NotificationMode,\n NotificationEvent,\n SendResult,\n} from \"../notifications/types.js\";\nimport type { TopicCheckParams, TopicCheckResult } from \"../topics/detector.js\";\nimport type { AutoRouteResult } from \"../session/auto-route.js\";\nimport { paiSocketPath } from \"../runtime-paths.js\";\n\n// ---------------------------------------------------------------------------\n// Protocol types\n// ---------------------------------------------------------------------------\n\n/** Default socket path */\nexport const IPC_SOCKET_PATH = paiSocketPath();\n\n/** Timeout for IPC calls (60 seconds) */\nconst IPC_TIMEOUT_MS = 60_000;\n\ninterface IpcRequest {\n id: string;\n method: string;\n params: Record<string, unknown>;\n}\n\ninterface IpcResponse {\n id: string;\n ok: boolean;\n result?: unknown;\n error?: string;\n}\n\n// ---------------------------------------------------------------------------\n// Client\n// ---------------------------------------------------------------------------\n\n/**\n * Thin IPC proxy that forwards tool calls to pai-daemon over a Unix\n * Domain Socket. Each call opens a fresh connection, sends one NDJSON request,\n * reads the response, and closes. Stateless and simple.\n */\nexport class PaiClient {\n private readonly socketPath: string;\n\n constructor(socketPath?: string) {\n this.socketPath = socketPath ?? IPC_SOCKET_PATH;\n }\n\n /**\n * Call a PAI tool by name with the given params.\n * Returns the tool result or throws on error.\n *\n * `timeoutMs` overrides the default 60s wait — for a caller on a hook's\n * critical path (e.g. the threshold-triggered handover enqueue in\n * `cli/commands/session/autosave.ts`) where the actual work happens later,\n * asynchronously, in the daemon's worker loop, and only the cheap\n * enqueue handshake itself should ever be waited on.\n */\n async call(method: string, params: Record<string, unknown>, timeoutMs?: number): Promise<unknown> {\n return this.send(method, params, timeoutMs);\n }\n\n /**\n * Check daemon status.\n */\n async status(): Promise<Record<string, unknown>> {\n const result = await this.send(\"status\", {});\n return result as Record<string, unknown>;\n }\n\n /**\n * Trigger an immediate index run.\n */\n async triggerIndex(): Promise<void> {\n await this.send(\"index_now\", {});\n }\n\n // -------------------------------------------------------------------------\n // Notification methods\n // -------------------------------------------------------------------------\n\n /**\n * Get the current notification config from the daemon.\n */\n async getNotificationConfig(): Promise<{\n config: NotificationConfig;\n activeChannels: string[];\n }> {\n const result = await this.send(\"notification_get_config\", {});\n return result as { config: NotificationConfig; activeChannels: string[] };\n }\n\n /**\n * Patch the notification config on the daemon (and persist to disk).\n */\n async setNotificationConfig(patch: {\n mode?: NotificationMode;\n channels?: Partial<NotificationConfig[\"channels\"]>;\n routing?: Partial<NotificationConfig[\"routing\"]>;\n }): Promise<{ config: NotificationConfig }> {\n const result = await this.send(\"notification_set_config\", patch as Record<string, unknown>);\n return result as { config: NotificationConfig };\n }\n\n /**\n * Send a notification via the daemon (routes to configured channels).\n */\n async sendNotification(payload: {\n event: NotificationEvent;\n message: string;\n title?: string;\n }): Promise<SendResult> {\n const result = await this.send(\"notification_send\", payload as Record<string, unknown>);\n return result as SendResult;\n }\n\n // -------------------------------------------------------------------------\n // Topic detection methods\n // -------------------------------------------------------------------------\n\n /**\n * Check whether the provided context text has drifted to a different project\n * than the session's current routing.\n */\n async topicCheck(params: TopicCheckParams): Promise<TopicCheckResult> {\n const result = await this.send(\"topic_check\", params as unknown as Record<string, unknown>);\n return result as TopicCheckResult;\n }\n\n // -------------------------------------------------------------------------\n // Session routing methods\n // -------------------------------------------------------------------------\n\n /**\n * Automatically detect which project a session belongs to.\n * Tries path match, PAI.md marker walk, then topic detection (if context given).\n */\n async sessionAutoRoute(params: {\n cwd?: string;\n context?: string;\n }): Promise<AutoRouteResult | null> {\n // session_auto_route returns a ToolResult (content array). Extract the text\n // and parse JSON from it.\n const result = await this.send(\"session_auto_route\", params as Record<string, unknown>);\n const toolResult = result as { content?: Array<{ text: string }>; isError?: boolean };\n if (toolResult.isError) return null;\n const text = toolResult.content?.[0]?.text ?? \"\";\n // Text is either JSON (on match) or a human-readable \"no match\" message\n try {\n return JSON.parse(text) as AutoRouteResult;\n } catch {\n return null;\n }\n }\n\n // -------------------------------------------------------------------------\n // Internal transport\n // -------------------------------------------------------------------------\n\n /**\n * Send a single IPC request and wait for the response.\n * Opens a new socket connection per call — simple and reliable.\n */\n private send(\n method: string,\n params: Record<string, unknown>,\n timeoutMs: number = IPC_TIMEOUT_MS\n ): Promise<unknown> {\n const socketPath = this.socketPath;\n\n return new Promise((resolve, reject) => {\n let socket: Socket | null = null;\n let done = false;\n let buffer = \"\";\n let timer: ReturnType<typeof setTimeout> | null = null;\n\n function finish(error: Error | null, value?: unknown): void {\n if (done) return;\n done = true;\n if (timer !== null) {\n clearTimeout(timer);\n timer = null;\n }\n try {\n socket?.destroy();\n } catch {\n // ignore\n }\n if (error) {\n reject(error);\n } else {\n resolve(value);\n }\n }\n\n socket = connect(socketPath, () => {\n const request: IpcRequest = {\n id: randomUUID(),\n method,\n params,\n };\n socket!.write(JSON.stringify(request) + \"\\n\");\n });\n\n socket.on(\"data\", (chunk: Buffer) => {\n buffer += chunk.toString();\n const nl = buffer.indexOf(\"\\n\");\n if (nl === -1) return;\n\n const line = buffer.slice(0, nl);\n buffer = buffer.slice(nl + 1);\n\n let response: IpcResponse;\n try {\n response = JSON.parse(line) as IpcResponse;\n } catch {\n finish(new Error(`IPC parse error: ${line}`));\n return;\n }\n\n if (!response.ok) {\n finish(new Error(response.error ?? \"IPC call failed\"));\n } else {\n finish(null, response.result);\n }\n });\n\n socket.on(\"error\", (e: NodeJS.ErrnoException) => {\n if (e.code === \"ENOENT\" || e.code === \"ECONNREFUSED\") {\n finish(\n new Error(\n \"PAI daemon not running. Start it with: pai daemon serve\"\n )\n );\n } else {\n finish(e);\n }\n });\n\n socket.on(\"end\", () => {\n if (!done) {\n finish(new Error(\"IPC connection closed before response\"));\n }\n });\n\n timer = setTimeout(() => {\n finish(new Error(`IPC call timed out after ${timeoutMs}ms`));\n }, timeoutMs);\n });\n }\n}\n"],"mappings":";;;;;;;;;;;;;;;;AA4BA,MAAa,kBAAkB,eAAe;;AAG9C,MAAM,iBAAiB;;;;;;AAwBvB,IAAa,YAAb,MAAuB;CACrB,AAAiB;CAEjB,YAAY,YAAqB;AAC/B,OAAK,aAAa,cAAc;;;;;;;;;;;;CAalC,MAAM,KAAK,QAAgB,QAAiC,WAAsC;AAChG,SAAO,KAAK,KAAK,QAAQ,QAAQ,UAAU;;;;;CAM7C,MAAM,SAA2C;AAE/C,SADe,MAAM,KAAK,KAAK,UAAU,EAAE,CAAC;;;;;CAO9C,MAAM,eAA8B;AAClC,QAAM,KAAK,KAAK,aAAa,EAAE,CAAC;;;;;CAUlC,MAAM,wBAGH;AAED,SADe,MAAM,KAAK,KAAK,2BAA2B,EAAE,CAAC;;;;;CAO/D,MAAM,sBAAsB,OAIgB;AAE1C,SADe,MAAM,KAAK,KAAK,2BAA2B,MAAiC;;;;;CAO7F,MAAM,iBAAiB,SAIC;AAEtB,SADe,MAAM,KAAK,KAAK,qBAAqB,QAAmC;;;;;;CAYzF,MAAM,WAAW,QAAqD;AAEpE,SADe,MAAM,KAAK,KAAK,eAAe,OAA6C;;;;;;CAY7F,MAAM,iBAAiB,QAGa;EAIlC,MAAM,aADS,MAAM,KAAK,KAAK,sBAAsB,OAAkC;AAEvF,MAAI,WAAW,QAAS,QAAO;EAC/B,MAAM,OAAO,WAAW,UAAU,IAAI,QAAQ;AAE9C,MAAI;AACF,UAAO,KAAK,MAAM,KAAK;UACjB;AACN,UAAO;;;;;;;CAYX,AAAQ,KACN,QACA,QACA,YAAoB,gBACF;EAClB,MAAM,aAAa,KAAK;AAExB,SAAO,IAAI,SAAS,SAAS,WAAW;GACtC,IAAI,SAAwB;GAC5B,IAAI,OAAO;GACX,IAAI,SAAS;GACb,IAAI,QAA8C;GAElD,SAAS,OAAO,OAAqB,OAAuB;AAC1D,QAAI,KAAM;AACV,WAAO;AACP,QAAI,UAAU,MAAM;AAClB,kBAAa,MAAM;AACnB,aAAQ;;AAEV,QAAI;AACF,aAAQ,SAAS;YACX;AAGR,QAAI,MACF,QAAO,MAAM;QAEb,SAAQ,MAAM;;AAIlB,YAAS,QAAQ,kBAAkB;IACjC,MAAM,UAAsB;KAC1B,IAAI,YAAY;KAChB;KACA;KACD;AACD,WAAQ,MAAM,KAAK,UAAU,QAAQ,GAAG,KAAK;KAC7C;AAEF,UAAO,GAAG,SAAS,UAAkB;AACnC,cAAU,MAAM,UAAU;IAC1B,MAAM,KAAK,OAAO,QAAQ,KAAK;AAC/B,QAAI,OAAO,GAAI;IAEf,MAAM,OAAO,OAAO,MAAM,GAAG,GAAG;AAChC,aAAS,OAAO,MAAM,KAAK,EAAE;IAE7B,IAAI;AACJ,QAAI;AACF,gBAAW,KAAK,MAAM,KAAK;YACrB;AACN,4BAAO,IAAI,MAAM,oBAAoB,OAAO,CAAC;AAC7C;;AAGF,QAAI,CAAC,SAAS,GACZ,QAAO,IAAI,MAAM,SAAS,SAAS,kBAAkB,CAAC;QAEtD,QAAO,MAAM,SAAS,OAAO;KAE/B;AAEF,UAAO,GAAG,UAAU,MAA6B;AAC/C,QAAI,EAAE,SAAS,YAAY,EAAE,SAAS,eACpC,wBACE,IAAI,MACF,0DACD,CACF;QAED,QAAO,EAAE;KAEX;AAEF,UAAO,GAAG,aAAa;AACrB,QAAI,CAAC,KACH,wBAAO,IAAI,MAAM,wCAAwC,CAAC;KAE5D;AAEF,WAAQ,iBAAiB;AACvB,2BAAO,IAAI,MAAM,4BAA4B,UAAU,IAAI,CAAC;MAC3D,UAAU;IACb"}
@@ -26,4 +26,4 @@ function entityContentId(name, tenantId = "default") {
26
26
 
27
27
  //#endregion
28
28
  export { entityContentId as t };
29
- //# sourceMappingURL=kg-entity-Cj8Q6YmU.mjs.map
29
+ //# sourceMappingURL=kg-entity-1uqCnk4u.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"kg-entity-Cj8Q6YmU.mjs","names":[],"sources":["../src/memory/kg-entity.ts"],"sourcesContent":["/**\n * kg-entity.ts — Entity content-addressing with multi-tenant support.\n *\n * Provides UUID5-style deterministic content hashes for KG entities and edges,\n * ensuring that the same entity name always maps to the same ID within a tenant.\n * This enables idempotent upserts and stable foreign keys for kg_triples.\n *\n * Multi-tenant support: each tenant namespace gets its own entity ID space.\n * The default tenant is \"default\" for single-user deployments.\n */\n\nimport { createHash } from \"node:crypto\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface KgEntity {\n entity_id: string;\n tenant_id: string;\n name: string;\n type: string;\n description?: string;\n first_seen?: number;\n last_seen?: number;\n mention_count: number;\n feedback_weight: number;\n}\n\nexport interface KgEntityUpsertParams {\n name: string;\n type?: string;\n description?: string;\n tenantId?: string;\n}\n\n// ---------------------------------------------------------------------------\n// Content addressing\n// ---------------------------------------------------------------------------\n\n/**\n * Generate a deterministic entity ID (UUID5-style) for a given name and tenant.\n *\n * The ID is a hex digest derived from \"tenant_id:name\" so the same entity\n * always receives the same ID within a tenant namespace.\n *\n * @param name Entity name (case-preserved)\n * @param tenantId Tenant namespace (default: \"default\")\n */\nexport function entityContentId(name: string, tenantId = \"default\"): string {\n return createHash(\"sha256\")\n .update(`entity:${tenantId}:${name}`)\n .digest(\"hex\")\n .slice(0, 32); // 128-bit hex string — UUID5-compatible length\n}\n\n/**\n * Generate a deterministic edge ID for a (source, relation, target) triple\n * within a tenant namespace.\n *\n * @param source Source entity name\n * @param relation Relation/predicate verb phrase\n * @param target Target entity name\n * @param tenantId Tenant namespace (default: \"default\")\n */\nexport function edgeContentId(\n source: string,\n relation: string,\n target: string,\n tenantId = \"default\"\n): string {\n return createHash(\"sha256\")\n .update(`edge:${tenantId}:${source}:${relation}:${target}`)\n .digest(\"hex\")\n .slice(0, 32);\n}\n\n// SQLite's kg_entities CRUD (upsertKgEntity/findKgEntity/listKgEntities/\n// updateEntityFeedbackWeight) lives in storage/sqlite/kg-entity.ts —\n// PostgresBackend implements the same operations inline against its own\n// pool, so there is no cross-backend function to share here.\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAiDA,SAAgB,gBAAgB,MAAc,WAAW,WAAmB;AAC1E,QAAO,WAAW,SAAS,CACxB,OAAO,UAAU,SAAS,GAAG,OAAO,CACpC,OAAO,MAAM,CACb,MAAM,GAAG,GAAG"}
1
+ {"version":3,"file":"kg-entity-1uqCnk4u.mjs","names":[],"sources":["../src/memory/kg-entity.ts"],"sourcesContent":["/**\n * kg-entity.ts — Entity content-addressing with multi-tenant support.\n *\n * Provides UUID5-style deterministic content hashes for KG entities and edges,\n * ensuring that the same entity name always maps to the same ID within a tenant.\n * This enables idempotent upserts and stable foreign keys for kg_triples.\n *\n * Multi-tenant support: each tenant namespace gets its own entity ID space.\n * The default tenant is \"default\" for single-user deployments.\n */\n\nimport { createHash } from \"node:crypto\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface KgEntity {\n entity_id: string;\n tenant_id: string;\n name: string;\n type: string;\n description?: string;\n first_seen?: number;\n last_seen?: number;\n mention_count: number;\n feedback_weight: number;\n}\n\nexport interface KgEntityUpsertParams {\n name: string;\n type?: string;\n description?: string;\n tenantId?: string;\n}\n\n// ---------------------------------------------------------------------------\n// Content addressing\n// ---------------------------------------------------------------------------\n\n/**\n * Generate a deterministic entity ID (UUID5-style) for a given name and tenant.\n *\n * The ID is a hex digest derived from \"tenant_id:name\" so the same entity\n * always receives the same ID within a tenant namespace.\n *\n * @param name Entity name (case-preserved)\n * @param tenantId Tenant namespace (default: \"default\")\n */\nexport function entityContentId(name: string, tenantId = \"default\"): string {\n return createHash(\"sha256\")\n .update(`entity:${tenantId}:${name}`)\n .digest(\"hex\")\n .slice(0, 32); // 128-bit hex string — UUID5-compatible length\n}\n\n/**\n * Generate a deterministic edge ID for a (source, relation, target) triple\n * within a tenant namespace.\n *\n * @param source Source entity name\n * @param relation Relation/predicate verb phrase\n * @param target Target entity name\n * @param tenantId Tenant namespace (default: \"default\")\n */\nexport function edgeContentId(\n source: string,\n relation: string,\n target: string,\n tenantId = \"default\"\n): string {\n return createHash(\"sha256\")\n .update(`edge:${tenantId}:${source}:${relation}:${target}`)\n .digest(\"hex\")\n .slice(0, 32);\n}\n\n// SQLite's kg_entities CRUD (upsertKgEntity/findKgEntity/listKgEntities/\n// updateEntityFeedbackWeight) lives in storage/sqlite/kg-entity.ts —\n// PostgresBackend implements the same operations inline against its own\n// pool, so there is no cross-backend function to share here.\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;AAiDA,SAAgB,gBAAgB,MAAc,WAAW,WAAmB;AAC1E,QAAO,WAAW,SAAS,CACxB,OAAO,UAAU,SAAS,GAAG,OAAO,CACpC,OAAO,MAAM,CACb,MAAM,GAAG,GAAG"}
@@ -1,6 +1,6 @@
1
- import { n as TITLE_STOP_WORDS } from "./stop-words-ClCwO8mZ.mjs";
2
- import "./embeddings-DfrYNIng.mjs";
3
- import { t as zettelThemes } from "./themes-B2v7RNnA.mjs";
1
+ import { n as TITLE_STOP_WORDS } from "./stop-words-BdQuaE9K.mjs";
2
+ import "./embeddings-DK9XfQic.mjs";
3
+ import { t as zettelThemes } from "./themes-D1FQFthd.mjs";
4
4
  import { mkdirSync, writeFileSync } from "node:fs";
5
5
  import { dirname, join } from "node:path";
6
6
 
@@ -188,4 +188,4 @@ function handleIdeaMaterialize(params, vaultPath) {
188
188
 
189
189
  //#endregion
190
190
  export { handleGraphLatentIdeas, handleIdeaMaterialize };
191
- //# sourceMappingURL=latent-ideas-BRy6HNwg.mjs.map
191
+ //# sourceMappingURL=latent-ideas-CSuKfiq3.mjs.map