@tekmidian/pai 0.66.1 → 0.66.2

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 (158) hide show
  1. package/dist/{aibroker-client-B8c42Lh8.mjs → aibroker-client-CHEEvJZW.mjs} +2 -2
  2. package/dist/{aibroker-client-C5Fw7DNz.mjs → aibroker-client-Dfv7j1Od.mjs} +3 -3
  3. package/dist/{aibroker-client-C5Fw7DNz.mjs.map → aibroker-client-Dfv7j1Od.mjs.map} +1 -1
  4. package/dist/{auto-route-o0BOsXn7.mjs → auto-route-BnizyALK.mjs} +65 -4
  5. package/dist/auto-route-BnizyALK.mjs.map +1 -0
  6. package/dist/auto-route-D6fW1Q8z.mjs +3 -0
  7. package/dist/{chain-CZfKIEq9.mjs → chain-D5876UQX.mjs} +3 -4
  8. package/dist/{chain-CZfKIEq9.mjs.map → chain-D5876UQX.mjs.map} +1 -1
  9. package/dist/cli/index.mjs +18 -23
  10. package/dist/cli/index.mjs.map +1 -1
  11. package/dist/cli/program.mjs +17 -22
  12. package/dist/{config-BbLFD7Uf.mjs → config-C-LiGlop.mjs} +2 -2
  13. package/dist/{config-BbLFD7Uf.mjs.map → config-C-LiGlop.mjs.map} +1 -1
  14. package/dist/{config-YinjgXEJ.mjs → config-DKIA7wgF.mjs} +1 -1
  15. package/dist/{context-handover-cache-pkHzmL3c.mjs → context-handover-cache-9PGvXRIw.mjs} +984 -10
  16. package/dist/context-handover-cache-9PGvXRIw.mjs.map +1 -0
  17. package/dist/daemon/index.mjs +17 -19
  18. package/dist/daemon/index.mjs.map +1 -1
  19. package/dist/daemon-BUgNvMUh.mjs +5029 -0
  20. package/dist/daemon-BUgNvMUh.mjs.map +1 -0
  21. package/dist/daemon-DL0eglMJ.mjs +18 -0
  22. package/dist/daemon-mcp/index.mjs +12 -9
  23. package/dist/daemon-mcp/index.mjs.map +1 -1
  24. package/dist/{embeddings-CcscYWwk.mjs → embeddings-CEBGrzwu.mjs} +1 -1
  25. package/dist/{embeddings-Bx3q0QOY.mjs → embeddings-DOLZnT1X.mjs} +1 -1
  26. package/dist/{embeddings-Bx3q0QOY.mjs.map → embeddings-DOLZnT1X.mjs.map} +1 -1
  27. package/dist/{env-JNEIrQWg.mjs → env-DiolswKQ.mjs} +1 -1
  28. package/dist/{env-JNEIrQWg.mjs.map → env-DiolswKQ.mjs.map} +1 -1
  29. package/dist/factory-DrBg24UT.mjs +8 -0
  30. package/dist/factory-Ypf8r0fK.mjs +2892 -0
  31. package/dist/factory-Ypf8r0fK.mjs.map +1 -0
  32. package/dist/{fallback-CIiq2Raw.mjs → fallback-D51R4shY.mjs} +23 -11
  33. package/dist/fallback-D51R4shY.mjs.map +1 -0
  34. package/dist/{chunker-BH4i-F2b.mjs → helpers-CZsi_49C.mjs} +221 -2
  35. package/dist/helpers-CZsi_49C.mjs.map +1 -0
  36. package/dist/hooks/worker-status-line.mjs.map +2 -2
  37. package/dist/index.mjs +6 -7
  38. package/dist/{ipc-client-D16Xw6Uo.mjs → ipc-client-BLYX51nG.mjs} +2 -2
  39. package/dist/{ipc-client-D16Xw6Uo.mjs.map → ipc-client-BLYX51nG.mjs.map} +1 -1
  40. package/dist/main-resolver-BYgP86C1.mjs +7 -0
  41. package/dist/{main-resolver-DPPwHNtn.mjs → main-resolver-kVkMJZUD.mjs} +6 -6
  42. package/dist/{main-resolver-DPPwHNtn.mjs.map → main-resolver-kVkMJZUD.mjs.map} +1 -1
  43. package/dist/{migrate-BD7D8EEh.mjs → migrate-Bq0esMOa.mjs} +2 -2
  44. package/dist/{migrate-BD7D8EEh.mjs.map → migrate-Bq0esMOa.mjs.map} +1 -1
  45. package/dist/{pai-marker-D1MMswkz.mjs → pai-marker-DXVpFsYz.mjs} +1 -1
  46. package/dist/{pai-marker-D1MMswkz.mjs.map → pai-marker-DXVpFsYz.mjs.map} +1 -1
  47. package/dist/{planner-Dm5Acyo1.mjs → planner-D5yt0EfA.mjs} +10 -7
  48. package/dist/{planner-Dm5Acyo1.mjs.map → planner-D5yt0EfA.mjs.map} +1 -1
  49. package/dist/postgres-BmLr0MUm.mjs +5 -0
  50. package/dist/{postgres-Ceqsa64C.mjs → postgres-D3xc2RB4.mjs} +364 -9
  51. package/dist/postgres-D3xc2RB4.mjs.map +1 -0
  52. package/dist/{program-JJ0TwSHI.mjs → program-DIXRtAFy.mjs} +77 -70
  53. package/dist/program-DIXRtAFy.mjs.map +1 -0
  54. package/dist/{query-feedback-BIaZTTFO.mjs → query-feedback-B7FYE4JR.mjs} +1 -1
  55. package/dist/{query-feedback-BIaZTTFO.mjs.map → query-feedback-B7FYE4JR.mjs.map} +1 -1
  56. package/dist/{reranker-DKv80KO5.mjs → reranker-3lnggwgq.mjs} +1 -1
  57. package/dist/{reranker-DKv80KO5.mjs.map → reranker-3lnggwgq.mjs.map} +1 -1
  58. package/dist/{reranker-CFiEzHQu.mjs → reranker-CZ2mP4cf.mjs} +1 -1
  59. package/dist/router-BK-hFeQ6.mjs +3 -0
  60. package/dist/{router-a5G7q8_3.mjs → router-BaTbc9VX.mjs} +2 -2
  61. package/dist/{router-a5G7q8_3.mjs.map → router-BaTbc9VX.mjs.map} +1 -1
  62. package/dist/{run-BCpbxqA5.mjs → run-CLYTOm0x.mjs} +599 -22
  63. package/dist/run-CLYTOm0x.mjs.map +1 -0
  64. package/dist/{run-env-DRcp7A8K.mjs → run-env-Bnmf6bRI.mjs} +2 -2
  65. package/dist/{run-env-DRcp7A8K.mjs.map → run-env-Bnmf6bRI.mjs.map} +1 -1
  66. package/dist/{runtime-paths-CHTg3ywb.mjs → runtime-paths-D3FKAm69.mjs} +1 -1
  67. package/dist/{runtime-paths-CHTg3ywb.mjs.map → runtime-paths-D3FKAm69.mjs.map} +1 -1
  68. package/dist/{search-BOSphCJ1.mjs → search-CNAGTiJP.mjs} +2 -2
  69. package/dist/{search-BOSphCJ1.mjs.map → search-CNAGTiJP.mjs.map} +1 -1
  70. package/dist/{sources-CijVso3n.mjs → sources-CM2g-CLT.mjs} +2 -2
  71. package/dist/{sources-CijVso3n.mjs.map → sources-CM2g-CLT.mjs.map} +1 -1
  72. package/dist/{stop-words-BdQuaE9K.mjs → stop-words-DtxaTWU_.mjs} +1 -1
  73. package/dist/{stop-words-BdQuaE9K.mjs.map → stop-words-DtxaTWU_.mjs.map} +1 -1
  74. package/dist/{utils-C9HsYDpO.mjs → utils-DddRwMsG.mjs} +2 -2
  75. package/dist/{utils-C9HsYDpO.mjs.map → utils-DddRwMsG.mjs.map} +1 -1
  76. package/dist/{zettelkasten-CtGHQWPU.mjs → zettelkasten-BhZMvmoK.mjs} +149 -7
  77. package/dist/zettelkasten-BhZMvmoK.mjs.map +1 -0
  78. package/dist/zettelkasten-BwV1pluL.mjs +5 -0
  79. package/docs/commands/worker.md +2 -0
  80. package/package.json +1 -1
  81. package/dist/async-C2Bm_Lal.mjs +0 -300
  82. package/dist/async-C2Bm_Lal.mjs.map +0 -1
  83. package/dist/auto-route-o0BOsXn7.mjs.map +0 -1
  84. package/dist/chunker-BH4i-F2b.mjs.map +0 -1
  85. package/dist/clusters-BCtD3fbe.mjs +0 -169
  86. package/dist/clusters-BCtD3fbe.mjs.map +0 -1
  87. package/dist/context-handover-cache-pkHzmL3c.mjs.map +0 -1
  88. package/dist/daemon-DXlaVCkw.mjs +0 -1741
  89. package/dist/daemon-DXlaVCkw.mjs.map +0 -1
  90. package/dist/daemon-DyORmxZu.mjs +0 -19
  91. package/dist/detector-BEPFsINR.mjs +0 -3
  92. package/dist/detector-BRTtAWrM.mjs +0 -65
  93. package/dist/detector-BRTtAWrM.mjs.map +0 -1
  94. package/dist/factory-CNpPQQ-5.mjs +0 -249
  95. package/dist/factory-CNpPQQ-5.mjs.map +0 -1
  96. package/dist/factory-DY7x73mE.mjs +0 -3
  97. package/dist/fallback-CIiq2Raw.mjs.map +0 -1
  98. package/dist/federation-db-BTyoufBh.mjs +0 -139
  99. package/dist/federation-db-BTyoufBh.mjs.map +0 -1
  100. package/dist/federation-db-HfIFc7FG.mjs +0 -3
  101. package/dist/helpers-BRCJg0G3.mjs +0 -223
  102. package/dist/helpers-BRCJg0G3.mjs.map +0 -1
  103. package/dist/indexer-backend-DFF2FrYx.mjs +0 -5
  104. package/dist/indexer-backend-isSLg6yE.mjs +0 -1
  105. package/dist/kg-entity-DCOcsVFD.mjs +0 -29
  106. package/dist/kg-entity-DCOcsVFD.mjs.map +0 -1
  107. package/dist/latent-ideas-wwAXquGe.mjs +0 -191
  108. package/dist/latent-ideas-wwAXquGe.mjs.map +0 -1
  109. package/dist/link-boost-CnI7UVMJ.mjs +0 -34
  110. package/dist/link-boost-CnI7UVMJ.mjs.map +0 -1
  111. package/dist/main-resolver-BKz_OuEh.mjs +0 -7
  112. package/dist/merge-2gqRPFu2.mjs +0 -3
  113. package/dist/merge-DgU9OgZy.mjs +0 -6
  114. package/dist/merge-DgU9OgZy.mjs.map +0 -1
  115. package/dist/module-paths-DdRzbkUI.mjs +0 -44
  116. package/dist/module-paths-DdRzbkUI.mjs.map +0 -1
  117. package/dist/neighborhood-2FsmzoxG.mjs +0 -114
  118. package/dist/neighborhood-2FsmzoxG.mjs.map +0 -1
  119. package/dist/note-context-De63k8na.mjs +0 -106
  120. package/dist/note-context-De63k8na.mjs.map +0 -1
  121. package/dist/postgres-Ceqsa64C.mjs.map +0 -1
  122. package/dist/program-JJ0TwSHI.mjs.map +0 -1
  123. package/dist/query-feedback-B_iigYj-.mjs +0 -3
  124. package/dist/registry-db-C7voqML9.mjs +0 -213
  125. package/dist/registry-db-C7voqML9.mjs.map +0 -1
  126. package/dist/registry-db-JHPhA8vF.mjs +0 -3
  127. package/dist/registry-postgres-nOfyBSIW.mjs +0 -795
  128. package/dist/registry-postgres-nOfyBSIW.mjs.map +0 -1
  129. package/dist/registry-sqlite-DrCW1aRK.mjs +0 -593
  130. package/dist/registry-sqlite-DrCW1aRK.mjs.map +0 -1
  131. package/dist/router-CXUGsv85.mjs +0 -3
  132. package/dist/run-BCpbxqA5.mjs.map +0 -1
  133. package/dist/search-Bf3Kub3F.mjs +0 -3
  134. package/dist/server-DgmAHyFK.mjs +0 -403
  135. package/dist/server-DgmAHyFK.mjs.map +0 -1
  136. package/dist/session-keepalive-DxjTHGcK.mjs +0 -582
  137. package/dist/session-keepalive-DxjTHGcK.mjs.map +0 -1
  138. package/dist/sqlite-CqsTy6xo.mjs +0 -936
  139. package/dist/sqlite-CqsTy6xo.mjs.map +0 -1
  140. package/dist/state-DW8zdweW.mjs +0 -3
  141. package/dist/state-HyjqTihC.mjs +0 -76
  142. package/dist/state-HyjqTihC.mjs.map +0 -1
  143. package/dist/themes-CTaOj3e1.mjs +0 -148
  144. package/dist/themes-CTaOj3e1.mjs.map +0 -1
  145. package/dist/tools-BcOKPBtg.mjs +0 -1489
  146. package/dist/tools-BcOKPBtg.mjs.map +0 -1
  147. package/dist/tools-C-_l0o8Z.mjs +0 -6
  148. package/dist/trace-CtV0RO6n.mjs +0 -137
  149. package/dist/trace-CtV0RO6n.mjs.map +0 -1
  150. package/dist/utils-BrX9FLeK.mjs +0 -3
  151. package/dist/vault-indexer-BG1sEVyM.mjs +0 -537
  152. package/dist/vault-indexer-BG1sEVyM.mjs.map +0 -1
  153. package/dist/wakeup-D8n33hYV.mjs +0 -335
  154. package/dist/wakeup-D8n33hYV.mjs.map +0 -1
  155. package/dist/work-queue-worker-4tLa5gpQ.mjs +0 -567
  156. package/dist/work-queue-worker-4tLa5gpQ.mjs.map +0 -1
  157. package/dist/work-queue-worker-rFZvTO3p.mjs +0 -11
  158. package/dist/zettelkasten-CtGHQWPU.mjs.map +0 -1
@@ -0,0 +1 @@
1
+ {"version":3,"file":"postgres-D3xc2RB4.mjs","names":["vault.upsertVaultFile","vault.deleteVaultFile","vault.getVaultFile","vault.getVaultFileByInode","vault.getAllVaultFiles","vault.getRecentVaultFiles","vault.countVaultFiles","vault.countVaultFilesWithPrefix","vault.countVaultFilesAfter","vault.getVaultFilesByPaths","vault.getVaultFilesByPathsAfter","vault.getAllVaultFilePaths","vault.getVaultFilePathsWithPrefix","vault.getVaultFilePathsAfter","vault.upsertVaultAliases","vault.deleteVaultAliases","vault.getVaultAlias","vault.replaceLinksForSources","vault.getLinksFromSource","vault.getLinksToTarget","vault.getVaultLinkGraph","vault.getDeadLinks","vault.getDeadLinksWithLineNumbers","vault.getDeadLinksWithPrefix","vault.getDeadLinksAfter","vault.countVaultLinksWithPrefix","vault.countVaultLinksAfter","vault.getVaultLinksFromPaths","vault.getVaultLinkEdges","vault.getVaultLinkEdgesWithPrefix","vault.getVaultLinkEdgesAfter","vault.upsertVaultHealth","vault.getVaultHealth","vault.getOrphans","vault.getOrphansWithPrefix","vault.getOrphansAfter","vault.getLowConnectivity","vault.getLowConnectivityWithPrefix","vault.getLowConnectivityAfter","vault.upsertNameIndex","vault.replaceNameIndex","vault.resolveVaultName","vault.searchVaultNameIndex","kgTriples.addKgTriple","kgTriples.queryKgTriples","kgTriples.invalidateKgTriple","kgTriples.getKgContradictions","kgTriples.getKgStats","observations.storeObservation","observations.storeObservationWithProject","observations.queryObservations","observations.queryRecentObservations","observations.querySessionObservations","observations.getObservationStats","observations.getObservationTypesForPaths","observations.storeSessionSummary","observations.queryRecentSummaries","observations.recordSkillInvocation","observations.querySkillTelemetry"],"sources":["../src/module-paths.ts","../src/storage/postgres/helpers.ts","../src/storage/postgres/search.ts","../src/storage/postgres/vault.ts","../src/memory/kg-entity.ts","../src/memory/indexer/async.ts","../src/storage/postgres/tunnels.ts","../src/storage/postgres/sources.ts","../src/storage/postgres/kg-triples.ts","../src/storage/postgres/observations.ts","../src/storage/postgres/backend.ts"],"sourcesContent":["/**\n * module-paths.ts — locate a file relative to the package root from a running module.\n *\n * tsdown bundles shared code into chunks emitted directly under dist/ (e.g.\n * dist/program-*.mjs) rather than always alongside the entry that needs them\n * (dist/cli/index.mjs, dist/daemon/index.mjs, ...). A fixed \"../daemon/index.mjs\"\n * relative to import.meta.url assumes one specific chunk depth and silently\n * resolves to the wrong file — or a file under the source tree instead of\n * dist — whenever the bundler's chunking changes. Walking up from the running\n * module's own directory until `dir/rel` exists is depth-independent, and works\n * whether `rel` lives under dist/ (built entries) or is a sibling of dist/ at\n * the package root (files excluded from the bundle, like docker/migrate-sqlite.ts).\n */\n\nimport { existsSync } from \"node:fs\";\nimport { dirname, join } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\n\n/**\n * Resolve `rel` by walking up from the directory containing `moduleUrl`,\n * returning the first `dir/rel` that exists on disk.\n *\n * Throws rather than returning a guessed path, so a caller writing a service\n * file or spawning a process fails loudly instead of pointing at nothing.\n */\nexport function resolveFromModule(moduleUrl: string, rel: string, maxLevels = 8): string {\n let dir = dirname(fileURLToPath(moduleUrl));\n for (let i = 0; i < maxLevels; i++) {\n const candidate = join(dir, rel);\n if (existsSync(candidate)) return candidate;\n const parent = dirname(dir);\n if (parent === dir) break;\n dir = parent;\n }\n throw new Error(`cannot locate \"${rel}\" above ${dirname(fileURLToPath(moduleUrl))} — is the project built? (bun run build)`);\n}\n\n/** Package root (the directory holding package.json) for a running module, at any bundle depth. */\nexport function packageRoot(moduleUrl: string): string {\n return dirname(resolveFromModule(moduleUrl, \"package.json\"));\n}\n","/**\n * Internal helper utilities for the Postgres storage backend.\n */\n\nimport { STOP_WORDS } from \"../../utils/stop-words.js\";\n\n/**\n * Convert a Buffer of Float32 LE bytes (as stored in SQLite) to number[].\n */\nexport function bufferToVector(buf: Buffer): number[] {\n const floats: number[] = [];\n for (let i = 0; i < buf.length; i += 4) {\n floats.push(buf.readFloatLE(i));\n }\n return floats;\n}\n\n/**\n * Convert a free-text query to a Postgres tsquery string.\n *\n * Uses OR (|) semantics so that a chunk matching ANY query term is returned,\n * ranked by ts_rank (which scores higher when more terms match). AND (&)\n * semantics are too strict for multi-word queries because all terms rarely\n * co-occur in a single chunk.\n *\n * Example: \"Synchrotech interview follow-up Gilles\"\n * → \"synchrotech | interview | follow | gilles\"\n */\nexport function buildPgTsQuery(query: string): string {\n const tokens = query\n .toLowerCase()\n .split(/[\\s\\p{P}]+/u)\n .filter(Boolean)\n .filter((t) => t.length >= 2)\n .filter((t) => !STOP_WORDS.has(t))\n // Sanitize: strip tsquery special characters to prevent syntax errors\n .map((t) => t.replace(/'/g, \"''\").replace(/[&|!():]/g, \"\"))\n .filter(Boolean);\n\n if (tokens.length === 0) {\n const raw = query.replace(/[^a-z0-9]/gi, \" \").trim().split(/\\s+/).filter(Boolean).join(\" | \");\n return raw || \"\";\n }\n\n return tokens.join(\" | \");\n}\n","/**\n * Keyword and semantic search implementations for the Postgres backend.\n * Functions take a `pool` parameter so they can be called from PostgresBackend methods.\n */\n\nimport type { Pool } from \"pg\";\nimport type { SearchResult, SearchOptions } from \"../../memory/search.js\";\nimport { buildPgTsQuery } from \"./helpers.js\";\n\n/**\n * A failed search must not look like a search that found nothing.\n *\n * Both functions below used to catch, log to stderr, and `return []`. The log goes\n * to the daemon's own stderr, which no caller ever sees, so an unreachable backend\n * and a genuine miss were BYTE-IDENTICAL at the call site — `memory_search`\n * reported \"No results found for query: …\" either way.\n *\n * That cost a real wrong answer on 2026-08-04. `pai-pgvector` exited at 13:39Z and\n * the index lived inside it; for the next two hours every memory_search returned\n * cleanly empty. A sibling session searched for an open DMARC point, got nothing,\n * and told the owner nothing was recorded. The search had run against a dead\n * backend and was worthless — and nothing in the reply could have revealed that.\n *\n * So: throw. The MCP shim already turns a thrown error into a visible message, and\n * an error the user can act on beats an empty result they cannot distinguish from\n * the truth. This deliberately makes transient failures noisier — that is the\n * trade, and it is the right way round.\n */\nfunction searchFailure(kind: \"keyword\" | \"semantic\", cause: unknown): Error {\n const detail = cause instanceof Error ? cause.message : String(cause);\n return new Error(\n `Postgres ${kind} search failed — the memory index is unreachable, ` +\n `so this is NOT an empty result set. Check the backend is up ` +\n `(docker start pai-pgvector) and see pai daemon status. Cause: ${detail}`\n );\n}\n\n/**\n * Full-text keyword search using Postgres tsvector/tsquery with 'simple' dictionary.\n */\nexport async function searchKeyword(\n pool: Pool,\n query: string,\n opts?: SearchOptions\n): Promise<SearchResult[]> {\n const maxResults = opts?.maxResults ?? 10;\n\n const tsQuery = buildPgTsQuery(query);\n if (!tsQuery) return [];\n\n const conditions: string[] = [\"fts_vector @@ to_tsquery('simple', $1)\"];\n const params: (string | number)[] = [tsQuery];\n let paramIdx = 2;\n\n if (opts?.projectIds && opts.projectIds.length > 0) {\n const placeholders = opts.projectIds.map(() => `$${paramIdx++}`).join(\", \");\n conditions.push(`project_id IN (${placeholders})`);\n params.push(...opts.projectIds);\n }\n\n if (opts?.sources && opts.sources.length > 0) {\n const placeholders = opts.sources.map(() => `$${paramIdx++}`).join(\", \");\n conditions.push(`source IN (${placeholders})`);\n params.push(...opts.sources);\n }\n\n if (opts?.tiers && opts.tiers.length > 0) {\n const placeholders = opts.tiers.map(() => `$${paramIdx++}`).join(\", \");\n conditions.push(`tier IN (${placeholders})`);\n params.push(...opts.tiers);\n }\n\n params.push(maxResults);\n const limitParam = `$${paramIdx}`;\n\n const sql = `\n SELECT\n project_id,\n path,\n start_line,\n end_line,\n text AS snippet,\n tier,\n source,\n ts_rank(fts_vector, to_tsquery('simple', $1)) AS rank_score\n FROM pai_chunks\n WHERE ${conditions.join(\" AND \")}\n ORDER BY rank_score DESC\n LIMIT ${limitParam}\n `;\n\n try {\n const result = await pool.query<{\n project_id: number;\n path: string;\n start_line: number;\n end_line: number;\n snippet: string;\n tier: string;\n source: string;\n rank_score: number;\n }>(sql, params);\n\n return result.rows.map((row) => ({\n projectId: row.project_id,\n path: row.path,\n startLine: row.start_line,\n endLine: row.end_line,\n snippet: row.snippet,\n score: row.rank_score,\n tier: row.tier,\n source: row.source,\n }));\n } catch (e) {\n process.stderr.write(`[pai-postgres] searchKeyword error: ${e}\\n`);\n throw searchFailure(\"keyword\", e);\n }\n}\n\n/**\n * Semantic vector similarity search using pgvector cosine distance (<=>).\n */\nexport async function searchSemantic(\n pool: Pool,\n queryEmbedding: Float32Array,\n opts?: SearchOptions\n): Promise<SearchResult[]> {\n const maxResults = opts?.maxResults ?? 10;\n\n const conditions: string[] = [\"embedding IS NOT NULL\"];\n const params: (string | number)[] = [];\n let paramIdx = 1;\n\n const vecStr = \"[\" + Array.from(queryEmbedding).join(\",\") + \"]\";\n params.push(vecStr);\n const vecParam = `$${paramIdx++}`;\n\n if (opts?.projectIds && opts.projectIds.length > 0) {\n const placeholders = opts.projectIds.map(() => `$${paramIdx++}`).join(\", \");\n conditions.push(`project_id IN (${placeholders})`);\n params.push(...opts.projectIds);\n }\n\n if (opts?.sources && opts.sources.length > 0) {\n const placeholders = opts.sources.map(() => `$${paramIdx++}`).join(\", \");\n conditions.push(`source IN (${placeholders})`);\n params.push(...opts.sources);\n }\n\n if (opts?.tiers && opts.tiers.length > 0) {\n const placeholders = opts.tiers.map(() => `$${paramIdx++}`).join(\", \");\n conditions.push(`tier IN (${placeholders})`);\n params.push(...opts.tiers);\n }\n\n params.push(maxResults);\n const limitParam = `$${paramIdx}`;\n\n // <=> is cosine distance; 1 - distance = cosine similarity\n const sql = `\n SELECT\n project_id,\n path,\n start_line,\n end_line,\n text AS snippet,\n tier,\n source,\n 1 - (embedding <=> ${vecParam}::vector) AS cosine_similarity\n FROM pai_chunks\n WHERE ${conditions.join(\" AND \")}\n ORDER BY embedding <=> ${vecParam}::vector\n LIMIT ${limitParam}\n `;\n\n try {\n const result = await pool.query<{\n project_id: number;\n path: string;\n start_line: number;\n end_line: number;\n snippet: string;\n tier: string;\n source: string;\n cosine_similarity: number;\n }>(sql, params);\n\n const minScore = opts?.minScore ?? -Infinity;\n\n return result.rows\n .map((row) => ({\n projectId: row.project_id,\n path: row.path,\n startLine: row.start_line,\n endLine: row.end_line,\n snippet: row.snippet,\n score: row.cosine_similarity,\n tier: row.tier,\n source: row.source,\n }))\n .filter((r) => r.score >= minScore);\n } catch (e) {\n process.stderr.write(`[pai-postgres] searchSemantic error: ${e}\\n`);\n throw searchFailure(\"semantic\", e);\n }\n}\n","/**\n * Vault storage operations for the Postgres backend.\n * All functions take a `pool` parameter — called from PostgresBackend methods.\n */\n\nimport type { Pool } from \"pg\";\nimport type {\n VaultFileRow, VaultAliasRow, VaultLinkRow, VaultHealthRow, VaultNameEntry,\n} from \"../interface.js\";\n\n// ---------------------------------------------------------------------------\n// Vault files\n// ---------------------------------------------------------------------------\n\nexport async function upsertVaultFile(pool: Pool, file: VaultFileRow): Promise<void> {\n await pool.query(\n `INSERT INTO vault_files (vault_path, inode, device, hash, title, indexed_at)\n VALUES ($1, $2, $3, $4, $5, $6)\n ON CONFLICT (vault_path) DO UPDATE SET\n inode = EXCLUDED.inode, device = EXCLUDED.device,\n hash = EXCLUDED.hash, title = EXCLUDED.title,\n indexed_at = EXCLUDED.indexed_at`,\n [file.vaultPath, file.inode, file.device, file.hash, file.title, file.indexedAt]\n );\n}\n\nexport async function deleteVaultFile(pool: Pool, vaultPath: string): Promise<void> {\n const client = await pool.connect();\n try {\n await client.query(\"BEGIN\");\n await client.query(\"DELETE FROM vault_links WHERE source_path = $1\", [vaultPath]);\n await client.query(\"DELETE FROM vault_health WHERE vault_path = $1\", [vaultPath]);\n await client.query(\"DELETE FROM vault_name_index WHERE vault_path = $1\", [vaultPath]);\n await client.query(\"DELETE FROM vault_aliases WHERE vault_path = $1 OR canonical_path = $1\", [vaultPath]);\n await client.query(\"DELETE FROM vault_files WHERE vault_path = $1\", [vaultPath]);\n await client.query(\"COMMIT\");\n } catch (e) {\n await client.query(\"ROLLBACK\");\n throw e;\n } finally {\n client.release();\n }\n}\n\ntype VaultFileDbRow = { vault_path: string; inode: string; device: string; hash: string; title: string | null; indexed_at: string };\n\nfunction mapVaultFileRow(row: VaultFileDbRow): VaultFileRow {\n return {\n vaultPath: row.vault_path,\n inode: Number(row.inode),\n device: Number(row.device),\n hash: row.hash,\n title: row.title,\n indexedAt: Number(row.indexed_at),\n };\n}\n\nexport async function getVaultFile(pool: Pool, vaultPath: string): Promise<VaultFileRow | null> {\n const r = await pool.query<VaultFileDbRow>(\n \"SELECT vault_path, inode, device, hash, title, indexed_at FROM vault_files WHERE vault_path = $1\",\n [vaultPath]\n );\n return r.rows.length === 0 ? null : mapVaultFileRow(r.rows[0]);\n}\n\nexport async function getVaultFileByInode(pool: Pool, inode: number, device: number): Promise<VaultFileRow | null> {\n const r = await pool.query<VaultFileDbRow>(\n \"SELECT vault_path, inode, device, hash, title, indexed_at FROM vault_files WHERE inode = $1 AND device = $2 LIMIT 1\",\n [inode, device]\n );\n return r.rows.length === 0 ? null : mapVaultFileRow(r.rows[0]);\n}\n\nexport async function getAllVaultFiles(pool: Pool): Promise<VaultFileRow[]> {\n const r = await pool.query<VaultFileDbRow>(\"SELECT vault_path, inode, device, hash, title, indexed_at FROM vault_files\");\n return r.rows.map(mapVaultFileRow);\n}\n\nexport async function getRecentVaultFiles(pool: Pool, sinceMs: number): Promise<VaultFileRow[]> {\n const r = await pool.query<VaultFileDbRow>(\n \"SELECT vault_path, inode, device, hash, title, indexed_at FROM vault_files WHERE indexed_at > $1\",\n [sinceMs]\n );\n return r.rows.map(mapVaultFileRow);\n}\n\nexport async function countVaultFiles(pool: Pool): Promise<number> {\n const r = await pool.query<{ n: string }>(\"SELECT COUNT(*)::text AS n FROM vault_files\");\n return parseInt(r.rows[0]?.n ?? \"0\", 10);\n}\n\nexport async function countVaultFilesWithPrefix(pool: Pool, prefix: string): Promise<number> {\n const r = await pool.query<{ n: string }>(\"SELECT COUNT(*) AS n FROM vault_files WHERE vault_path LIKE $1\", [`${prefix}%`]);\n return Number(r.rows[0]?.n ?? 0);\n}\n\nexport async function countVaultFilesAfter(pool: Pool, sinceMs: number): Promise<number> {\n const r = await pool.query<{ n: string }>(\"SELECT COUNT(*) AS n FROM vault_files WHERE indexed_at > $1\", [sinceMs]);\n return Number(r.rows[0]?.n ?? 0);\n}\n\nexport async function getVaultFilesByPaths(pool: Pool, paths: string[]): Promise<VaultFileRow[]> {\n if (paths.length === 0) return [];\n const placeholders = paths.map((_, i) => `$${i + 1}`).join(\", \");\n const r = await pool.query<VaultFileDbRow>(\n `SELECT vault_path, inode, device, hash, title, indexed_at FROM vault_files WHERE vault_path IN (${placeholders})`,\n paths\n );\n return r.rows.map(mapVaultFileRow);\n}\n\nexport async function getVaultFilesByPathsAfter(pool: Pool, paths: string[], sinceMs: number): Promise<VaultFileRow[]> {\n if (paths.length === 0) return [];\n const placeholders = paths.map((_, i) => `$${i + 1}`).join(\", \");\n const r = await pool.query<VaultFileDbRow>(\n `SELECT vault_path, inode, device, hash, title, indexed_at FROM vault_files WHERE vault_path IN (${placeholders}) AND indexed_at >= $${paths.length + 1} ORDER BY indexed_at ASC`,\n [...paths, sinceMs]\n );\n return r.rows.map(mapVaultFileRow);\n}\n\nexport async function getAllVaultFilePaths(pool: Pool): Promise<string[]> {\n const r = await pool.query<{ vault_path: string }>(\"SELECT vault_path FROM vault_files\");\n return r.rows.map(row => row.vault_path);\n}\n\nexport async function getVaultFilePathsWithPrefix(pool: Pool, prefix: string): Promise<string[]> {\n const r = await pool.query<{ vault_path: string }>(\n \"SELECT vault_path FROM vault_files WHERE vault_path LIKE $1\",\n [`${prefix}%`]\n );\n return r.rows.map(row => row.vault_path);\n}\n\nexport async function getVaultFilePathsAfter(pool: Pool, sinceMs: number): Promise<string[]> {\n const r = await pool.query<{ vault_path: string }>(\n \"SELECT vault_path FROM vault_files WHERE indexed_at > $1\",\n [sinceMs]\n );\n return r.rows.map(row => row.vault_path);\n}\n\n// ---------------------------------------------------------------------------\n// Vault aliases\n// ---------------------------------------------------------------------------\n\nexport async function upsertVaultAliases(pool: Pool, aliases: VaultAliasRow[]): Promise<void> {\n if (aliases.length === 0) return;\n const client = await pool.connect();\n try {\n await client.query(\"BEGIN\");\n for (const a of aliases) {\n await client.query(\n `INSERT INTO vault_aliases (vault_path, canonical_path, inode, device)\n VALUES ($1, $2, $3, $4)\n ON CONFLICT (vault_path) DO UPDATE SET\n canonical_path = EXCLUDED.canonical_path,\n inode = EXCLUDED.inode, device = EXCLUDED.device`,\n [a.vaultPath, a.canonicalPath, a.inode, a.device]\n );\n }\n await client.query(\"COMMIT\");\n } catch (e) {\n await client.query(\"ROLLBACK\");\n throw e;\n } finally {\n client.release();\n }\n}\n\nexport async function deleteVaultAliases(pool: Pool, canonicalPath: string): Promise<void> {\n await pool.query(\"DELETE FROM vault_aliases WHERE canonical_path = $1\", [canonicalPath]);\n}\n\nexport async function getVaultAlias(pool: Pool, vaultPath: string): Promise<{ canonicalPath: string } | null> {\n const r = await pool.query<{ canonical_path: string }>(\n \"SELECT canonical_path FROM vault_aliases WHERE vault_path = $1\",\n [vaultPath]\n );\n return r.rows.length > 0 ? { canonicalPath: r.rows[0].canonical_path } : null;\n}\n\n// ---------------------------------------------------------------------------\n// Vault links\n// ---------------------------------------------------------------------------\n\ntype VaultLinkDbRow = { source_path: string; target_raw: string; target_path: string | null; link_type: string; line_number: number; confidence?: string };\n\nfunction mapVaultLinkRow(row: VaultLinkDbRow): VaultLinkRow {\n return {\n sourcePath: row.source_path,\n targetRaw: row.target_raw,\n targetPath: row.target_path,\n linkType: row.link_type,\n lineNumber: row.line_number,\n confidence: (row.confidence as VaultLinkRow[\"confidence\"]) ?? \"EXTRACTED\",\n };\n}\n\nexport async function replaceLinksForSources(pool: Pool, sourcePaths: string[], links: VaultLinkRow[]): Promise<void> {\n const client = await pool.connect();\n try {\n await client.query(\"BEGIN\");\n if (sourcePaths.length > 0) {\n await client.query(\n \"DELETE FROM vault_links WHERE source_path = ANY($1::text[])\",\n [sourcePaths]\n );\n }\n // Collapse duplicates on the conflict key before batching. One line can\n // legitimately carry the same target twice (\"see [[X]] and [[X]] again\"),\n // which yields two rows with the same (source_path, target_raw,\n // line_number). Postgres refuses a multi-row INSERT that would touch one\n // row twice — \"ON CONFLICT DO UPDATE command cannot affect row a second\n // time\" — and the whole vault index pass rolls back on it. Last occurrence\n // wins, which is what DO UPDATE would have left behind anyway.\n const deduped = new Map<string, VaultLinkRow>();\n for (const l of links) {\n deduped.set(`${l.sourcePath}\\u0000${l.targetRaw}\\u0000${l.lineNumber}`, l);\n }\n const uniqueLinks = [...deduped.values()];\n\n for (let i = 0; i < uniqueLinks.length; i += 500) {\n const batch = uniqueLinks.slice(i, i + 500);\n const values: string[] = [];\n const params: (string | number | null)[] = [];\n let idx = 1;\n for (const l of batch) {\n values.push(`($${idx++}, $${idx++}, $${idx++}, $${idx++}, $${idx++}, $${idx++})`);\n params.push(l.sourcePath, l.targetRaw, l.targetPath, l.linkType, l.lineNumber, l.confidence ?? \"EXTRACTED\");\n }\n await client.query(\n `INSERT INTO vault_links (source_path, target_raw, target_path, link_type, line_number, confidence)\n VALUES ${values.join(\", \")}\n ON CONFLICT (source_path, target_raw, line_number) DO UPDATE SET\n target_path = EXCLUDED.target_path, link_type = EXCLUDED.link_type,\n confidence = EXCLUDED.confidence`,\n params\n );\n }\n await client.query(\"COMMIT\");\n } catch (e) {\n await client.query(\"ROLLBACK\");\n throw e;\n } finally {\n client.release();\n }\n}\n\nexport async function getLinksFromSource(pool: Pool, sourcePath: string): Promise<VaultLinkRow[]> {\n const r = await pool.query<VaultLinkDbRow>(\n \"SELECT source_path, target_raw, target_path, link_type, line_number, confidence FROM vault_links WHERE source_path = $1\",\n [sourcePath]\n );\n return r.rows.map(mapVaultLinkRow);\n}\n\nexport async function getLinksToTarget(pool: Pool, targetPath: string): Promise<VaultLinkRow[]> {\n const r = await pool.query<VaultLinkDbRow>(\n \"SELECT source_path, target_raw, target_path, link_type, line_number, confidence FROM vault_links WHERE target_path = $1\",\n [targetPath]\n );\n return r.rows.map(mapVaultLinkRow);\n}\n\nexport async function getVaultLinkGraph(pool: Pool): Promise<Array<{ source_path: string; target_path: string }>> {\n const r = await pool.query<{ source_path: string; target_path: string }>(\n \"SELECT source_path, target_path FROM vault_links WHERE target_path IS NOT NULL\"\n );\n return r.rows;\n}\n\nexport async function getDeadLinks(pool: Pool): Promise<Array<{ sourcePath: string; targetRaw: string }>> {\n const r = await pool.query<{ source_path: string; target_raw: string }>(\n \"SELECT source_path, target_raw FROM vault_links WHERE target_path IS NULL\"\n );\n return r.rows.map(row => ({ sourcePath: row.source_path, targetRaw: row.target_raw }));\n}\n\nexport async function getDeadLinksWithLineNumbers(pool: Pool): Promise<Array<{ sourcePath: string; targetRaw: string; lineNumber: number }>> {\n const r = await pool.query<{ source_path: string; target_raw: string; line_number: number }>(\n \"SELECT source_path, target_raw, line_number FROM vault_links WHERE target_path IS NULL\"\n );\n return r.rows.map(row => ({ sourcePath: row.source_path, targetRaw: row.target_raw, lineNumber: row.line_number }));\n}\n\nexport async function getDeadLinksWithPrefix(pool: Pool, prefix: string): Promise<Array<{ sourcePath: string; targetRaw: string; lineNumber: number }>> {\n const r = await pool.query<{ source_path: string; target_raw: string; line_number: number }>(\n \"SELECT source_path, target_raw, line_number FROM vault_links WHERE target_path IS NULL AND source_path LIKE $1\",\n [`${prefix}%`]\n );\n return r.rows.map(row => ({ sourcePath: row.source_path, targetRaw: row.target_raw, lineNumber: row.line_number }));\n}\n\nexport async function getDeadLinksAfter(pool: Pool, sinceMs: number): Promise<Array<{ sourcePath: string; targetRaw: string; lineNumber: number }>> {\n const r = await pool.query<{ source_path: string; target_raw: string; line_number: number }>(\n \"SELECT source_path, target_raw, line_number FROM vault_links WHERE target_path IS NULL AND source_path IN (SELECT vault_path FROM vault_files WHERE indexed_at > $1)\",\n [sinceMs]\n );\n return r.rows.map(row => ({ sourcePath: row.source_path, targetRaw: row.target_raw, lineNumber: row.line_number }));\n}\n\nexport async function countVaultLinksWithPrefix(pool: Pool, prefix: string): Promise<number> {\n const r = await pool.query<{ n: string }>(\"SELECT COUNT(*) AS n FROM vault_links WHERE source_path LIKE $1\", [`${prefix}%`]);\n return Number(r.rows[0]?.n ?? 0);\n}\n\nexport async function countVaultLinksAfter(pool: Pool, sinceMs: number): Promise<number> {\n const r = await pool.query<{ n: string }>(\n \"SELECT COUNT(*) AS n FROM vault_links WHERE source_path IN (SELECT vault_path FROM vault_files WHERE indexed_at > $1)\",\n [sinceMs]\n );\n return Number(r.rows[0]?.n ?? 0);\n}\n\nexport async function getVaultLinksFromPaths(pool: Pool, sourcePaths: string[]): Promise<VaultLinkRow[]> {\n if (sourcePaths.length === 0) return [];\n const placeholders = sourcePaths.map((_, i) => `$${i + 1}`).join(\", \");\n const r = await pool.query<VaultLinkDbRow>(\n `SELECT source_path, target_raw, target_path, link_type, line_number, confidence FROM vault_links WHERE source_path IN (${placeholders}) AND target_path IS NOT NULL`,\n sourcePaths\n );\n return r.rows.map(mapVaultLinkRow);\n}\n\nexport async function getVaultLinkEdges(pool: Pool): Promise<Array<{ source: string; target: string }>> {\n const r = await pool.query<{ source: string; target: string }>(\n \"SELECT DISTINCT source_path AS source, target_path AS target FROM vault_links WHERE target_path IS NOT NULL\"\n );\n return r.rows;\n}\n\nexport async function getVaultLinkEdgesWithPrefix(pool: Pool, prefix: string): Promise<Array<{ source: string; target: string }>> {\n const r = await pool.query<{ source: string; target: string }>(\n \"SELECT DISTINCT source_path AS source, target_path AS target FROM vault_links WHERE target_path IS NOT NULL AND source_path LIKE $1\",\n [`${prefix}%`]\n );\n return r.rows;\n}\n\nexport async function getVaultLinkEdgesAfter(pool: Pool, sinceMs: number): Promise<Array<{ source: string; target: string }>> {\n const r = await pool.query<{ source: string; target: string }>(\n \"SELECT DISTINCT source_path AS source, target_path AS target FROM vault_links WHERE target_path IS NOT NULL AND source_path IN (SELECT vault_path FROM vault_files WHERE indexed_at > $1)\",\n [sinceMs]\n );\n return r.rows;\n}\n\n// ---------------------------------------------------------------------------\n// Vault health\n// ---------------------------------------------------------------------------\n\ntype VaultHealthDbRow = { vault_path: string; inbound_count: number; outbound_count: number; dead_link_count: number; is_orphan: number; computed_at: string };\n\nfunction mapVaultHealthRow(row: VaultHealthDbRow): VaultHealthRow {\n return {\n vaultPath: row.vault_path,\n inboundCount: row.inbound_count,\n outboundCount: row.outbound_count,\n deadLinkCount: row.dead_link_count,\n isOrphan: row.is_orphan === 1,\n computedAt: Number(row.computed_at),\n };\n}\n\nexport async function upsertVaultHealth(pool: Pool, rows: VaultHealthRow[]): Promise<void> {\n if (rows.length === 0) return;\n const client = await pool.connect();\n try {\n await client.query(\"BEGIN\");\n for (const h of rows) {\n await client.query(\n `INSERT INTO vault_health (vault_path, inbound_count, outbound_count, dead_link_count, is_orphan, computed_at)\n VALUES ($1, $2, $3, $4, $5, $6)\n ON CONFLICT (vault_path) DO UPDATE SET\n inbound_count = EXCLUDED.inbound_count,\n outbound_count = EXCLUDED.outbound_count,\n dead_link_count = EXCLUDED.dead_link_count,\n is_orphan = EXCLUDED.is_orphan,\n computed_at = EXCLUDED.computed_at`,\n [h.vaultPath, h.inboundCount, h.outboundCount, h.deadLinkCount, h.isOrphan ? 1 : 0, h.computedAt]\n );\n }\n await client.query(\"COMMIT\");\n } catch (e) {\n await client.query(\"ROLLBACK\");\n throw e;\n } finally {\n client.release();\n }\n}\n\nexport async function getVaultHealth(pool: Pool, vaultPath: string): Promise<VaultHealthRow | null> {\n const r = await pool.query<VaultHealthDbRow>(\n \"SELECT vault_path, inbound_count, outbound_count, dead_link_count, is_orphan, computed_at FROM vault_health WHERE vault_path = $1\",\n [vaultPath]\n );\n return r.rows.length === 0 ? null : mapVaultHealthRow(r.rows[0]);\n}\n\nexport async function getOrphans(pool: Pool): Promise<VaultHealthRow[]> {\n const r = await pool.query<VaultHealthDbRow>(\n \"SELECT vault_path, inbound_count, outbound_count, dead_link_count, is_orphan, computed_at FROM vault_health WHERE is_orphan = 1\"\n );\n return r.rows.map(row => ({ ...mapVaultHealthRow(row), isOrphan: true }));\n}\n\nexport async function getOrphansWithPrefix(pool: Pool, prefix: string): Promise<string[]> {\n const r = await pool.query<{ vault_path: string }>(\n \"SELECT vault_path FROM vault_health WHERE is_orphan = 1 AND vault_path LIKE $1\",\n [`${prefix}%`]\n );\n return r.rows.map(row => row.vault_path);\n}\n\nexport async function getOrphansAfter(pool: Pool, sinceMs: number): Promise<string[]> {\n const r = await pool.query<{ vault_path: string }>(\n \"SELECT vh.vault_path FROM vault_health vh JOIN vault_files vf ON vh.vault_path = vf.vault_path WHERE vh.is_orphan = 1 AND vf.indexed_at > $1\",\n [sinceMs]\n );\n return r.rows.map(row => row.vault_path);\n}\n\nexport async function getLowConnectivity(pool: Pool): Promise<string[]> {\n const r = await pool.query<{ vault_path: string }>(\n \"SELECT vault_path FROM vault_health WHERE inbound_count + outbound_count <= 1\"\n );\n return r.rows.map(row => row.vault_path);\n}\n\nexport async function getLowConnectivityWithPrefix(pool: Pool, prefix: string): Promise<string[]> {\n const r = await pool.query<{ vault_path: string }>(\n \"SELECT vault_path FROM vault_health WHERE inbound_count + outbound_count <= 1 AND vault_path LIKE $1\",\n [`${prefix}%`]\n );\n return r.rows.map(row => row.vault_path);\n}\n\nexport async function getLowConnectivityAfter(pool: Pool, sinceMs: number): Promise<string[]> {\n const r = await pool.query<{ vault_path: string }>(\n \"SELECT vh.vault_path FROM vault_health vh JOIN vault_files vf ON vh.vault_path = vf.vault_path WHERE vh.inbound_count + vh.outbound_count <= 1 AND vf.indexed_at > $1\",\n [sinceMs]\n );\n return r.rows.map(row => row.vault_path);\n}\n\n// ---------------------------------------------------------------------------\n// Vault name index\n// ---------------------------------------------------------------------------\n\nexport async function upsertNameIndex(pool: Pool, entries: VaultNameEntry[]): Promise<void> {\n if (entries.length === 0) return;\n const client = await pool.connect();\n try {\n await client.query(\"BEGIN\");\n for (const e of entries) {\n await client.query(\n `INSERT INTO vault_name_index (name, vault_path)\n VALUES ($1, $2) ON CONFLICT (name, vault_path) DO NOTHING`,\n [e.name, e.vaultPath]\n );\n }\n await client.query(\"COMMIT\");\n } catch (e_) {\n await client.query(\"ROLLBACK\");\n throw e_;\n } finally {\n client.release();\n }\n}\n\nexport async function replaceNameIndex(pool: Pool, entries: VaultNameEntry[]): Promise<void> {\n const client = await pool.connect();\n try {\n await client.query(\"BEGIN\");\n await client.query(\"DELETE FROM vault_name_index\");\n for (let i = 0; i < entries.length; i += 500) {\n const batch = entries.slice(i, i + 500);\n const values: string[] = [];\n const params: string[] = [];\n let idx = 1;\n for (const e of batch) {\n values.push(`($${idx++}, $${idx++})`);\n params.push(e.name, e.vaultPath);\n }\n await client.query(\n `INSERT INTO vault_name_index (name, vault_path) VALUES ${values.join(\", \")}`,\n params\n );\n }\n await client.query(\"COMMIT\");\n } catch (e) {\n await client.query(\"ROLLBACK\");\n throw e;\n } finally {\n client.release();\n }\n}\n\nexport async function resolveVaultName(pool: Pool, name: string): Promise<string[]> {\n const r = await pool.query<{ vault_path: string }>(\n \"SELECT vault_path FROM vault_name_index WHERE name = $1\",\n [name]\n );\n return r.rows.map(row => row.vault_path);\n}\n\nexport async function searchVaultNameIndex(pool: Pool, query: string, limit = 100): Promise<string[]> {\n const r = await pool.query<{ vault_path: string }>(\n \"SELECT DISTINCT vault_path FROM vault_name_index WHERE lower(name) LIKE lower($1) LIMIT $2\",\n [`%${query}%`, limit]\n );\n return r.rows.map(row => row.vault_path);\n}\n","/**\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","/**\n * Backend-aware async indexer for PAI federation memory.\n *\n * Provides the same functionality as sync.ts but writes through the\n * StorageBackend interface instead of directly to the SQLite driver.\n * Used when the daemon is configured with the Postgres backend.\n *\n * The SQLite path still uses sync.ts directly (which is faster for SQLite\n * due to synchronous transactions).\n */\n\nimport { readFileSync, statSync, existsSync } from \"node:fs\";\nimport { join, relative, basename } from \"node:path\";\nimport type { StorageBackend, ChunkRow } from \"../../storage/interface.js\";\nimport type { RegistryBackend } from \"../../storage/registry-interface.js\";\n// Static on purpose: a lazy import() of a dist chunk first hit after a rebuild\n// throws \"Cannot find module\" (prune-dist.mjs deletes superseded chunks).\nimport { generateEmbeddings, serializeEmbedding } from \"../embeddings.js\";\nimport { chunkMarkdown, fileContentHash } from \"../chunker.js\";\nimport {\n sha256File,\n chunkId,\n detectTier,\n walkMdFiles,\n walkContentFiles,\n isPathTooBroadForContentScan,\n parseSessionTitleChunk,\n yieldToEventLoop,\n INDEX_YIELD_EVERY,\n} from \"./helpers.js\";\nimport type { IndexResult } from \"./types.js\";\n\nexport type { IndexResult };\n\n// ---------------------------------------------------------------------------\n// Single-file indexing via StorageBackend\n// ---------------------------------------------------------------------------\n\n/**\n * Index a single file through the StorageBackend interface.\n * Returns true if the file was re-indexed (changed or new), false if skipped.\n */\nexport async function indexFileWithBackend(\n backend: StorageBackend,\n projectId: number,\n rootPath: string,\n relativePath: string,\n source: string,\n tier: string,\n): Promise<boolean> {\n const absPath = join(rootPath, relativePath);\n\n let content: string;\n let stat: ReturnType<typeof statSync>;\n try {\n content = readFileSync(absPath, \"utf8\");\n stat = statSync(absPath);\n } catch {\n return false;\n }\n\n const hash = fileContentHash(content);\n const mtime = Math.floor(stat.mtimeMs);\n const size = stat.size;\n\n // Change detection\n const existingHash = await backend.getFileHash(projectId, relativePath);\n if (existingHash === hash) return false;\n\n // Delete old chunks\n await backend.deleteChunksForFile(projectId, relativePath);\n\n // Chunk the content\n const rawChunks = chunkMarkdown(content);\n const updatedAt = Date.now();\n\n const chunks: ChunkRow[] = rawChunks.map((c, i) => ({\n id: chunkId(projectId, relativePath, i, c.startLine, c.endLine),\n projectId,\n source,\n tier,\n path: relativePath,\n startLine: c.startLine,\n endLine: c.endLine,\n hash: c.hash,\n text: c.text,\n updatedAt,\n embedding: null,\n }));\n\n // Insert chunks + update file record\n await backend.insertChunks(chunks);\n await backend.upsertFile({ projectId, path: relativePath, source, tier, hash, mtime, size });\n\n return true;\n}\n\n// ---------------------------------------------------------------------------\n// Project-level indexing via StorageBackend\n// ---------------------------------------------------------------------------\n\nexport async function indexProjectWithBackend(\n backend: StorageBackend,\n projectId: number,\n rootPath: string,\n claudeNotesDir?: string | null,\n): Promise<IndexResult> {\n const result: IndexResult = { filesProcessed: 0, chunksCreated: 0, filesSkipped: 0 };\n\n const filesToIndex: Array<{ absPath: string; rootBase: string; source: string; tier: string }> = [];\n\n const rootMemoryMd = join(rootPath, \"MEMORY.md\");\n if (existsSync(rootMemoryMd)) {\n filesToIndex.push({ absPath: rootMemoryMd, rootBase: rootPath, source: \"memory\", tier: \"evergreen\" });\n }\n\n const memoryDir = join(rootPath, \"memory\");\n for (const absPath of walkMdFiles(memoryDir)) {\n const relPath = relative(rootPath, absPath);\n const tier = detectTier(relPath);\n filesToIndex.push({ absPath, rootBase: rootPath, source: \"memory\", tier });\n }\n\n const notesDir = join(rootPath, \"Notes\");\n for (const absPath of walkMdFiles(notesDir)) {\n filesToIndex.push({ absPath, rootBase: rootPath, source: \"notes\", tier: \"session\" });\n }\n\n // Synthetic session-title chunks for Notes files\n {\n const updatedAt = Date.now();\n for (const absPath of walkMdFiles(notesDir)) {\n const fileName = basename(absPath);\n const text = parseSessionTitleChunk(fileName);\n if (!text) continue;\n const relPath = relative(rootPath, absPath);\n const syntheticPath = `${relPath}::title`;\n const id = chunkId(projectId, syntheticPath, 0, 0, 0);\n const hash = sha256File(text);\n const titleChunk: ChunkRow = {\n id, projectId, source: \"notes\", tier: \"session\",\n path: syntheticPath, startLine: 0, endLine: 0,\n hash, text, updatedAt, embedding: null,\n };\n try {\n await backend.insertChunks([titleChunk]);\n } catch {\n // Skip title chunks that cause backend errors\n }\n }\n }\n\n if (!isPathTooBroadForContentScan(rootPath)) {\n for (const absPath of walkContentFiles(rootPath)) {\n filesToIndex.push({ absPath, rootBase: rootPath, source: \"content\", tier: \"topic\" });\n }\n }\n\n if (claudeNotesDir && claudeNotesDir !== notesDir) {\n for (const absPath of walkMdFiles(claudeNotesDir)) {\n filesToIndex.push({ absPath, rootBase: claudeNotesDir, source: \"notes\", tier: \"session\" });\n }\n\n // Synthetic title chunks for claude notes dir\n {\n const updatedAt = Date.now();\n for (const absPath of walkMdFiles(claudeNotesDir)) {\n const fileName = basename(absPath);\n const text = parseSessionTitleChunk(fileName);\n if (!text) continue;\n const relPath = relative(claudeNotesDir, absPath);\n const syntheticPath = `${relPath}::title`;\n const id = chunkId(projectId, syntheticPath, 0, 0, 0);\n const hash = sha256File(text);\n const titleChunk: ChunkRow = {\n id, projectId, source: \"notes\", tier: \"session\",\n path: syntheticPath, startLine: 0, endLine: 0,\n hash, text, updatedAt, embedding: null,\n };\n try {\n await backend.insertChunks([titleChunk]);\n } catch {\n // Skip title chunks that cause backend errors\n }\n }\n }\n\n if (claudeNotesDir.endsWith(\"/Notes\")) {\n const claudeProjectDir = claudeNotesDir.slice(0, -\"/Notes\".length);\n const claudeMemoryMd = join(claudeProjectDir, \"MEMORY.md\");\n if (existsSync(claudeMemoryMd)) {\n filesToIndex.push({ absPath: claudeMemoryMd, rootBase: claudeProjectDir, source: \"memory\", tier: \"evergreen\" });\n }\n const claudeMemoryDir = join(claudeProjectDir, \"memory\");\n for (const absPath of walkMdFiles(claudeMemoryDir)) {\n const relPath = relative(claudeProjectDir, absPath);\n const tier = detectTier(relPath);\n filesToIndex.push({ absPath, rootBase: claudeProjectDir, source: \"memory\", tier });\n }\n }\n }\n\n await yieldToEventLoop();\n\n let filesSinceYield = 0;\n\n for (const { absPath, rootBase, source, tier } of filesToIndex) {\n if (filesSinceYield >= INDEX_YIELD_EVERY) {\n await yieldToEventLoop();\n filesSinceYield = 0;\n }\n filesSinceYield++;\n\n const relPath = relative(rootBase, absPath);\n try {\n const changed = await indexFileWithBackend(backend, projectId, rootBase, relPath, source, tier);\n\n if (changed) {\n const ids = await backend.getChunkIds(projectId, relPath);\n result.filesProcessed++;\n result.chunksCreated += ids.length;\n } else {\n result.filesSkipped++;\n }\n } catch {\n // Skip files that cause backend errors (e.g. null bytes in Postgres)\n result.filesSkipped++;\n }\n }\n\n // Prune stale paths\n const livePaths = new Set<string>();\n for (const { absPath, rootBase } of filesToIndex) {\n livePaths.add(relative(rootBase, absPath));\n }\n\n const dbChunkPaths = await backend.getDistinctChunkPaths(projectId);\n\n const stalePaths: string[] = [];\n for (const p of dbChunkPaths) {\n const basePath = p.endsWith(\"::title\") ? p.slice(0, -\"::title\".length) : p;\n if (!livePaths.has(basePath)) {\n stalePaths.push(p);\n }\n }\n\n if (stalePaths.length > 0) {\n await backend.deletePaths(projectId, stalePaths);\n }\n\n return result;\n}\n\n// ---------------------------------------------------------------------------\n// Embedding generation via StorageBackend\n// ---------------------------------------------------------------------------\n\nconst EMBED_BATCH_SIZE = 50;\nconst EMBED_YIELD_EVERY = 1;\n\n/** Default ceiling on one pass. See EmbedPassOptions. */\nconst DEFAULT_MAX_CHUNKS_PER_PASS = 5_000;\n/** Default wall-clock ceiling on one pass, in milliseconds. */\nconst DEFAULT_MAX_MILLIS_PER_PASS = 120_000;\n\nexport interface EmbedPassOptions {\n /**\n * Stop the pass after this many chunks. Unbounded passes are the reason a\n * six-figure backlog starves the indexer: the daemon serialises indexing and\n * embedding against each other, so a pass that runs for hours means nothing\n * new gets indexed for hours. Bounding the pass costs nothing — every\n * embedding is written as it is produced, so the next pass resumes exactly\n * where this one stopped.\n */\n maxChunks?: number;\n /** Stop the pass after this much wall-clock time, whichever bound hits first. */\n maxMillis?: number;\n}\n\n/**\n * Generate and store embeddings for unembedded chunks via the StorageBackend.\n *\n * The pass is deliberately bounded (see EmbedPassOptions) and resumable: it\n * takes a slice of the backlog, embeds it, and returns so the scheduler can run\n * an index pass before coming back. Draining the whole backlog in one call\n * starves indexing for as long as the call runs.\n *\n * The optional `shouldStop` callback is checked between every batch. When it\n * returns true the embed loop exits early so the caller (e.g. the daemon\n * shutdown handler) can close the pool without racing against active queries.\n *\n * Returns the number of newly embedded chunks.\n */\nexport async function embedChunksWithBackend(\n backend: StorageBackend,\n shouldStop?: () => boolean,\n projectNames?: Map<number, string>,\n options?: EmbedPassOptions,\n): Promise<number> {\n\n const maxChunks = options?.maxChunks ?? DEFAULT_MAX_CHUNKS_PER_PASS;\n const maxMillis = options?.maxMillis ?? DEFAULT_MAX_MILLIS_PER_PASS;\n const deadline = Date.now() + maxMillis;\n\n const rows = await backend.getUnembeddedChunkIds(undefined, maxChunks);\n if (rows.length === 0) return 0;\n\n const total = rows.length;\n let embedded = 0;\n\n // Build a summary of what needs embedding: count chunks per project_id\n const projectChunkCounts = new Map<number, { count: number; samplePath: string }>();\n for (const row of rows) {\n const entry = projectChunkCounts.get(row.project_id);\n if (entry) {\n entry.count++;\n } else {\n projectChunkCounts.set(row.project_id, { count: 1, samplePath: row.path });\n }\n }\n const pName = (pid: number) => projectNames?.get(pid) ?? `project ${pid}`;\n const projectSummary = Array.from(projectChunkCounts.entries())\n .map(([pid, { count, samplePath }]) => ` ${pName(pid)}: ${count} chunks (e.g. ${samplePath})`)\n .join(\"\\n\");\n process.stderr.write(\n `[pai-daemon] Embed pass: ${total} unembedded chunks across ${projectChunkCounts.size} project(s)\\n${projectSummary}\\n`\n );\n\n // Track current project for transition logging\n let currentProjectId = -1;\n let projectEmbedded = 0;\n\n for (let i = 0; i < rows.length; i += EMBED_BATCH_SIZE) {\n // Check cancellation between every batch before touching the pool again\n if (shouldStop?.()) {\n process.stderr.write(\n `[pai-daemon] Embed pass cancelled after ${embedded}/${total} chunks (shutdown requested)\\n`\n );\n break;\n }\n\n // Yield the daemon back to the indexer rather than run past the deadline.\n // The remaining rows are simply picked up by the next scheduled pass.\n if (Date.now() >= deadline) {\n process.stderr.write(\n `[pai-daemon] Embed pass yielding after ${embedded}/${total} chunks (${maxMillis}ms budget spent); resuming next pass\\n`\n );\n break;\n }\n\n const batch = rows.slice(i, i + EMBED_BATCH_SIZE);\n\n // Keep IPC responsive: the forward pass below is synchronous inside the\n // model, so yield before entering it rather than between chunks.\n await yieldToEventLoop();\n\n const vecs = await generateEmbeddings(batch.map((r) => r.text));\n // Issue the writes together rather than one round-trip at a time. Measured\n // on this machine the model embeds 40-67 chunks/s in isolation while the\n // daemon managed ~5/s: the difference was one sequential UPDATE per chunk,\n // so the pass spent most of its budget waiting on the network, not working.\n // Concurrency is bounded by the batch size, which the pool handles; the\n // SQLite backend is synchronous and simply ignores the difference.\n await Promise.all(\n batch.map((row, j) => backend.updateEmbedding(row.id, serializeEmbedding(vecs[j])))\n );\n\n // Attribute the batch to projects only after it is durably stored, and walk\n // it in order so a batch straddling a project boundary credits each side\n // correctly. Rows arrive ordered by project, so a boundary is a real\n // transition, not the per-chunk flapping this logging used to produce.\n for (const { project_id, path } of batch) {\n if (project_id !== currentProjectId) {\n if (currentProjectId !== -1) {\n process.stderr.write(\n `[pai-daemon] Finished ${pName(currentProjectId)}: ${projectEmbedded} chunks embedded\\n`\n );\n }\n const info = projectChunkCounts.get(project_id);\n process.stderr.write(\n `[pai-daemon] Embedding ${pName(project_id)} (${info?.count ?? \"?\"} chunks, starting at ${path})\\n`\n );\n currentProjectId = project_id;\n projectEmbedded = 0;\n }\n projectEmbedded++;\n }\n\n embedded += batch.length;\n\n // Log progress with current file path for context\n const lastChunk = batch[batch.length - 1];\n process.stderr.write(\n `[pai-daemon] Embedded ${embedded}/${total} chunks (${pName(lastChunk.project_id)}: ${lastChunk.path})\\n`\n );\n }\n\n // Log final project completion\n if (currentProjectId !== -1) {\n process.stderr.write(\n `[pai-daemon] Finished ${pName(currentProjectId)}: ${projectEmbedded} chunks embedded\\n`\n );\n }\n\n return embedded;\n}\n\n// ---------------------------------------------------------------------------\n// Global indexing via StorageBackend\n// ---------------------------------------------------------------------------\n\nexport async function indexAllWithBackend(\n backend: StorageBackend,\n registry: RegistryBackend,\n): Promise<{ projects: number; result: IndexResult }> {\n const projects = await registry.listProjects({ status: \"active\" });\n\n const totals: IndexResult = { filesProcessed: 0, chunksCreated: 0, filesSkipped: 0 };\n\n for (const project of projects) {\n await yieldToEventLoop();\n const r = await indexProjectWithBackend(backend, project.id, project.root_path, project.claude_notes_dir);\n totals.filesProcessed += r.filesProcessed;\n totals.chunksCreated += r.chunksCreated;\n totals.filesSkipped += r.filesSkipped;\n }\n\n return { projects: projects.length, result: totals };\n}\n","/**\n * Postgres implementation of cross-project concept \"tunnel\" detection —\n * moved from memory/tunnels.ts alongside its SQLite counterpart\n * (storage/sqlite/tunnels.ts) so raw SQL against the federation database\n * lives entirely under src/storage/.\n *\n * Uses ts_stat() + plainto_tsquery to efficiently find terms that appear\n * across multiple projects.\n */\n\nimport { STOP_WORDS } from \"../../utils/stop-words.js\";\nimport type { PgPoolLike } from \"../pg-types.js\";\nimport type { RegistryBackend } from \"../registry-interface.js\";\nimport type { Tunnel, FindTunnelsOptions, FindTunnelsResult } from \"../../memory/tunnels.js\";\n\ninterface ProjectSlugMap {\n [id: number]: string;\n}\n\nasync function findTunnelsForSlugMap(\n pool: PgPoolLike,\n slugMap: ProjectSlugMap,\n opts: Required<FindTunnelsOptions>,\n): Promise<FindTunnelsResult> {\n const projectIds = Object.keys(slugMap).map(Number);\n if (projectIds.length < 2) {\n return { tunnels: [], projects_analyzed: projectIds.length, total_concepts_evaluated: 0 };\n }\n\n // Step 1 — extract top terms from the corpus using ts_stat over all chunks.\n const termResult = await pool.query<{ word: string; ndoc: string; nentry: string }>(\n `SELECT word, ndoc, nentry\n FROM ts_stat(\n 'SELECT to_tsvector(''simple'', text) FROM memory_chunks WHERE project_id = ANY($1)'\n )\n WHERE length(word) >= 3\n AND nentry >= $2\n ORDER BY ndoc DESC\n LIMIT 500`,\n [projectIds, opts.min_occurrences]\n );\n\n let candidateTerms = termResult.rows\n .map((r) => r.word)\n .filter((t) => !STOP_WORDS.has(t));\n\n if (candidateTerms.length === 0) {\n return { tunnels: [], projects_analyzed: projectIds.length, total_concepts_evaluated: 0 };\n }\n\n candidateTerms = candidateTerms.slice(0, 200);\n\n // Step 2 — for each candidate, count distinct projects via a single batched query.\n const valuesClause = candidateTerms\n .map((t, i) => `($${i + 2}::text)`)\n .join(\", \");\n\n const batchResult = await pool.query<{\n concept: string;\n project_id: string;\n cnt: string;\n first_seen: string;\n last_seen: string;\n }>(\n `SELECT v.concept, c.project_id::text, COUNT(*) AS cnt,\n MIN(c.updated_at) AS first_seen,\n MAX(c.updated_at) AS last_seen\n FROM (VALUES ${valuesClause}) AS v(concept)\n JOIN memory_chunks c\n ON to_tsvector('simple', c.text) @@ plainto_tsquery('simple', v.concept)\n AND c.project_id = ANY($1)\n GROUP BY v.concept, c.project_id`,\n [projectIds, ...candidateTerms]\n );\n\n const byConceptMap = new Map<\n string,\n { projects: Set<number>; occurrences: number; firstSeen: number; lastSeen: number }\n >();\n\n for (const row of batchResult.rows) {\n const existing = byConceptMap.get(row.concept) ?? {\n projects: new Set<number>(),\n occurrences: 0,\n firstSeen: Infinity,\n lastSeen: -Infinity,\n };\n existing.projects.add(parseInt(row.project_id, 10));\n existing.occurrences += parseInt(row.cnt, 10);\n const fs = parseInt(row.first_seen, 10);\n const ls = parseInt(row.last_seen, 10);\n if (fs < existing.firstSeen) existing.firstSeen = fs;\n if (ls > existing.lastSeen) existing.lastSeen = ls;\n byConceptMap.set(row.concept, existing);\n }\n\n const tunnels: Tunnel[] = [];\n for (const [concept, data] of byConceptMap) {\n if (data.projects.size < opts.min_projects) continue;\n if (data.occurrences < opts.min_occurrences) continue;\n\n const projects = [...data.projects]\n .map((id) => slugMap[id] ?? String(id))\n .filter(Boolean);\n\n tunnels.push({\n concept,\n projects,\n occurrences: data.occurrences,\n first_seen: data.firstSeen === Infinity ? 0 : data.firstSeen,\n last_seen: data.lastSeen === -Infinity ? 0 : data.lastSeen,\n });\n }\n\n tunnels.sort((a, b) => {\n const byProjects = b.projects.length - a.projects.length;\n if (byProjects !== 0) return byProjects;\n return b.occurrences - a.occurrences;\n });\n\n return {\n tunnels: tunnels.slice(0, opts.limit),\n projects_analyzed: projectIds.length,\n total_concepts_evaluated: candidateTerms.length,\n };\n}\n\n/** Find cross-project concept tunnels against a Postgres federation database. */\nexport async function findTunnelsPostgres(\n pool: PgPoolLike,\n registry: RegistryBackend,\n options?: FindTunnelsOptions,\n): Promise<FindTunnelsResult> {\n const opts: Required<FindTunnelsOptions> = {\n min_projects: options?.min_projects ?? 2,\n min_occurrences: options?.min_occurrences ?? 3,\n limit: options?.limit ?? 20,\n };\n\n const projectRows = await registry.listProjects({ excludeArchived: true });\n const slugMap: ProjectSlugMap = {};\n for (const { id, slug } of projectRows) {\n slugMap[id] = slug;\n }\n\n return findTunnelsForSlugMap(pool, slugMap, opts);\n}\n","/**\n * Postgres implementation of the `pai memory sources` aggregate report —\n * moved from cli/commands/memory/sources.ts alongside its SQLite\n * counterpart (storage/sqlite/sources.ts).\n */\n\nimport type { Pool } from \"pg\";\nimport type { MemorySourcesReport } from \"../interface.js\";\n\nconst num = (v: unknown): number => Number(v ?? 0);\n\nexport async function getMemorySourcesReportPostgres(pool: Pool): Promise<MemorySourcesReport> {\n const compRes = await pool.query(\n `SELECT source, tier, COUNT(*) AS chunks, COUNT(embedding) AS embedded\n FROM pai_chunks GROUP BY source, tier ORDER BY COUNT(*) DESC`\n );\n const composition = (compRes.rows as Array<{ source: string; tier: string; chunks: unknown; embedded: unknown }>)\n .map((r) => ({ source: r.source, tier: r.tier, chunks: num(r.chunks), embedded: num(r.embedded) }));\n\n const pathsRes = await pool.query(\n `SELECT path, COUNT(*) AS chunks FROM pai_chunks GROUP BY path`\n );\n const paths = (pathsRes.rows as Array<{ path: string; chunks: unknown }>)\n .map((r) => ({ path: r.path, chunks: num(r.chunks) }));\n\n const churnRes = await pool.query(\n `SELECT to_char(to_timestamp(updated_at/1000),'YYYY-MM-DD') AS day,\n COUNT(*) AS chunks, COUNT(embedding) AS embedded\n FROM pai_chunks GROUP BY day ORDER BY day DESC LIMIT 10`\n );\n const churn = (churnRes.rows as Array<{ day: string; chunks: unknown; embedded: unknown }>)\n .map((r) => ({ day: r.day, chunks: num(r.chunks), embedded: num(r.embedded) }));\n\n return { composition, paths, churn };\n}\n","/**\n * Postgres implementation of the temporal knowledge graph (kg_triples).\n * Delegated from PostgresBackend — see src/storage/interface.ts for the\n * StorageBackend contract these functions satisfy.\n */\n\nimport type { Pool } from \"pg\";\nimport type { KgAddParams, KgQueryParams, KgTriple, KgContradiction } from \"../../memory/kg.js\";\nimport type { KgStats } from \"../interface.js\";\n\nfunction rowToTriple(row: Record<string, unknown>): KgTriple {\n return {\n id: row.id as number,\n subject: row.subject as string,\n predicate: row.predicate as string,\n object: row.object as string,\n project_id: row.project_id as number | undefined,\n source_session: row.source_session as string | undefined,\n valid_from: new Date(row.valid_from as string),\n valid_to: row.valid_to ? new Date(row.valid_to as string) : undefined,\n confidence: row.confidence as \"EXTRACTED\" | \"INFERRED\" | \"AMBIGUOUS\",\n created_at: new Date(row.created_at as string),\n };\n}\n\nexport async function addKgTriple(pool: Pool, params: KgAddParams): Promise<KgTriple> {\n const confidence = params.confidence ?? \"EXTRACTED\";\n const result = await pool.query<Record<string, unknown>>(\n `INSERT INTO kg_triples\n (subject, predicate, object, project_id, source_session, confidence)\n VALUES ($1, $2, $3, $4, $5, $6)\n RETURNING *`,\n [\n params.subject,\n params.predicate,\n params.object,\n params.project_id ?? null,\n params.source_session ?? null,\n confidence,\n ]\n );\n return rowToTriple(result.rows[0]);\n}\n\n/**\n * Query triples by subject, predicate, object, and/or project.\n * Supports point-in-time queries via as_of.\n * By default only returns currently-valid triples (valid_to IS NULL).\n */\nexport async function queryKgTriples(pool: Pool, params: KgQueryParams): Promise<KgTriple[]> {\n const conditions: string[] = [];\n const values: unknown[] = [];\n let idx = 1;\n\n if (params.subject !== undefined) {\n conditions.push(`subject = $${idx++}`);\n values.push(params.subject);\n }\n if (params.predicate !== undefined) {\n conditions.push(`predicate = $${idx++}`);\n values.push(params.predicate);\n }\n if (params.object !== undefined) {\n conditions.push(`object = $${idx++}`);\n values.push(params.object);\n }\n if (params.project_id !== undefined) {\n conditions.push(`project_id = $${idx++}`);\n values.push(params.project_id);\n }\n\n if (params.as_of !== undefined) {\n // Valid at the given timestamp: started before or at as_of, and not yet ended\n conditions.push(`valid_from <= $${idx++}`);\n values.push(params.as_of);\n conditions.push(`(valid_to IS NULL OR valid_to > $${idx++})`);\n values.push(params.as_of);\n } else if (!params.include_invalidated) {\n // Default: only currently-valid (no valid_to set)\n conditions.push(`valid_to IS NULL`);\n }\n\n const where = conditions.length > 0 ? `WHERE ${conditions.join(\" AND \")}` : \"\";\n const result = await pool.query<Record<string, unknown>>(\n `SELECT * FROM kg_triples ${where} ORDER BY valid_from DESC`,\n values\n );\n return result.rows.map(rowToTriple);\n}\n\n/** Invalidate a triple by setting valid_to = NOW(). Does not delete the row — preserves history. */\nexport async function invalidateKgTriple(pool: Pool, tripleId: number): Promise<void> {\n await pool.query(\n `UPDATE kg_triples SET valid_to = NOW() WHERE id = $1 AND valid_to IS NULL`,\n [tripleId]\n );\n}\n\n/**\n * Find contradictions: cases where the same (subject, predicate) pair has\n * multiple currently-valid objects.\n */\nexport async function getKgContradictions(pool: Pool, subject: string): Promise<KgContradiction[]> {\n const result = await pool.query<{ subject: string; predicate: string; objects: string[] }>(\n `SELECT subject, predicate, array_agg(object ORDER BY object) AS objects\n FROM kg_triples\n WHERE subject = $1\n AND valid_to IS NULL\n GROUP BY subject, predicate\n HAVING COUNT(*) > 1`,\n [subject]\n );\n return result.rows.map((row) => ({\n subject: row.subject,\n predicate: row.predicate,\n objects: row.objects,\n }));\n}\n\nexport async function getKgStats(pool: Pool): Promise<KgStats> {\n const totals = await pool.query<{\n total: string;\n valid: string;\n invalidated: string;\n subjects: string;\n predicates: string;\n }>(\n `SELECT\n COUNT(*)::text AS total,\n COUNT(*) FILTER (WHERE valid_to IS NULL)::text AS valid,\n COUNT(*) FILTER (WHERE valid_to IS NOT NULL)::text AS invalidated,\n COUNT(DISTINCT subject)::text AS subjects,\n COUNT(DISTINCT predicate)::text AS predicates\n FROM kg_triples`\n );\n\n const contradictions = await pool.query<{ count: string }>(\n `SELECT COUNT(*)::text AS count FROM (\n SELECT subject, predicate\n FROM kg_triples\n WHERE valid_to IS NULL\n GROUP BY subject, predicate\n HAVING COUNT(*) > 1\n ) c`\n );\n\n const row = totals.rows[0];\n return {\n total: parseInt(row?.total ?? \"0\", 10),\n valid: parseInt(row?.valid ?? \"0\", 10),\n invalidated: parseInt(row?.invalidated ?? \"0\", 10),\n subjects: parseInt(row?.subjects ?? \"0\", 10),\n predicates: parseInt(row?.predicates ?? \"0\", 10),\n contradictions: parseInt(contradictions.rows[0]?.count ?? \"0\", 10),\n };\n}\n","/**\n * Postgres implementation of PAI observations, session summaries, and skill\n * telemetry (pai_observations / pai_session_summaries / pai_skill_telemetry).\n * Delegated from PostgresBackend — see src/storage/interface.ts for the\n * StorageBackend contract these functions satisfy.\n *\n * Content-hash deduplication: observations with the same hash created within\n * a 30-second window are silently dropped to prevent duplicate entries from\n * rapid repeated tool calls.\n */\n\nimport type { Pool } from \"pg\";\nimport { sha256 } from \"../../utils/hash.js\";\nimport type { RegistryBackend } from \"../registry-interface.js\";\nimport type {\n ObservationRow, ObservationWithCwd, StoreObservationInput, QueryObservationsOptions,\n ObservationStats, SessionSummaryRow, StoreSessionSummaryInput,\n SkillTelemetryRow, RecordSkillInvocationInput, QuerySkillTelemetryOptions,\n} from \"../interface.js\";\n\n// ---------------------------------------------------------------------------\n// Schema initialisation\n// ---------------------------------------------------------------------------\n\n/**\n * Inlined schema DDL — avoids runtime file reads that break in bundled code\n * (the bundler puts this in a shared chunk whose __dirname differs from src/).\n */\nconst SCHEMA_SQL = `\nCREATE TABLE IF NOT EXISTS pai_observations (\n id SERIAL PRIMARY KEY,\n session_id TEXT NOT NULL,\n project_id INTEGER,\n project_slug TEXT,\n type TEXT NOT NULL CHECK (type IN ('decision', 'bugfix', 'feature', 'refactor', 'discovery', 'change')),\n title TEXT NOT NULL,\n narrative TEXT,\n tool_name TEXT,\n tool_input_summary TEXT,\n files_read JSONB DEFAULT '[]'::jsonb,\n files_modified JSONB DEFAULT '[]'::jsonb,\n concepts JSONB DEFAULT '[]'::jsonb,\n content_hash TEXT,\n created_at TIMESTAMPTZ DEFAULT NOW()\n);\n\nCREATE INDEX IF NOT EXISTS idx_obs_project ON pai_observations(project_id);\nCREATE INDEX IF NOT EXISTS idx_obs_session ON pai_observations(session_id);\nCREATE INDEX IF NOT EXISTS idx_obs_type ON pai_observations(type);\nCREATE INDEX IF NOT EXISTS idx_obs_created ON pai_observations(created_at DESC);\nCREATE INDEX IF NOT EXISTS idx_obs_hash ON pai_observations(content_hash);\n\nCREATE TABLE IF NOT EXISTS pai_session_summaries (\n id SERIAL PRIMARY KEY,\n session_id TEXT NOT NULL UNIQUE,\n project_id INTEGER,\n project_slug TEXT,\n request TEXT,\n investigated TEXT,\n learned TEXT,\n completed TEXT,\n next_steps TEXT,\n observation_count INTEGER DEFAULT 0,\n created_at TIMESTAMPTZ DEFAULT NOW()\n);\n\nCREATE INDEX IF NOT EXISTS idx_ss_project ON pai_session_summaries(project_id);\nCREATE INDEX IF NOT EXISTS idx_ss_session ON pai_session_summaries(session_id);\n`;\n\nlet _tablesEnsured = false;\n\n/**\n * Run schema DDL idempotently against the given pool.\n * Uses a module-level flag so subsequent calls are no-ops within the same\n * process lifetime (the SQL itself uses IF NOT EXISTS so it is safe to re-run).\n */\nasync function ensureObservationTables(pool: Pool): Promise<void> {\n if (_tablesEnsured) return;\n await pool.query(SCHEMA_SQL);\n _tablesEnsured = true;\n}\n\nconst SKILL_TELEMETRY_SCHEMA_SQL = `\nCREATE TABLE IF NOT EXISTS pai_skill_telemetry (\n id SERIAL PRIMARY KEY,\n scope TEXT NOT NULL DEFAULT 'default',\n skill_name TEXT NOT NULL,\n source TEXT NOT NULL DEFAULT 'local',\n status TEXT NOT NULL DEFAULT 'active' CHECK (status IN ('trial','active','archived')),\n trigger_count INTEGER NOT NULL DEFAULT 0,\n accept_count INTEGER NOT NULL DEFAULT 0,\n first_triggered TIMESTAMPTZ DEFAULT NOW(),\n last_triggered TIMESTAMPTZ DEFAULT NOW(),\n context_projects JSONB DEFAULT '[]'::jsonb,\n hash TEXT,\n audit_status TEXT,\n last_audited TIMESTAMPTZ,\n UNIQUE(scope, skill_name, source)\n);\n\nCREATE INDEX IF NOT EXISTS idx_skilltel_scope ON pai_skill_telemetry(scope);\nCREATE INDEX IF NOT EXISTS idx_skilltel_status ON pai_skill_telemetry(scope, status);\nCREATE INDEX IF NOT EXISTS idx_skilltel_last ON pai_skill_telemetry(last_triggered DESC);\n`;\n\nlet _skillTelemetryEnsured = false;\n\nasync function ensureSkillTelemetryTable(pool: Pool): Promise<void> {\n if (_skillTelemetryEnsured) return;\n await pool.query(SKILL_TELEMETRY_SCHEMA_SQL);\n _skillTelemetryEnsured = true;\n}\n\n// ---------------------------------------------------------------------------\n// Content-hash deduplication\n// ---------------------------------------------------------------------------\n\n/** Hash = SHA256(session_id + tool_name + title).slice(0, 16) */\nfunction computeContentHash(sessionId: string, toolName: string, title: string): string {\n return sha256(sessionId + \"\\x00\" + toolName + \"\\x00\" + title).slice(0, 16);\n}\n\n// ---------------------------------------------------------------------------\n// Store observation\n// ---------------------------------------------------------------------------\n\n/**\n * Insert an observation, skipping duplicates within a 30-second window.\n * Returns the inserted row's id, or null if the insert was suppressed.\n */\nexport async function storeObservation(\n pool: Pool,\n obs: StoreObservationInput\n): Promise<number | null> {\n await ensureObservationTables(pool);\n\n const hash = computeContentHash(obs.session_id, obs.tool_name, obs.title);\n\n const dupCheck = await pool.query<{ id: number }>(\n `SELECT id FROM pai_observations\n WHERE content_hash = $1\n AND session_id = $2\n AND created_at >= NOW() - INTERVAL '30 seconds'\n LIMIT 1`,\n [hash, obs.session_id]\n );\n\n if (dupCheck.rowCount && dupCheck.rowCount > 0) {\n return null;\n }\n\n const result = await pool.query<{ id: number }>(\n `INSERT INTO pai_observations\n (session_id, project_id, project_slug, type, title, narrative,\n tool_name, tool_input_summary, files_read, files_modified, concepts, content_hash)\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9::jsonb, $10::jsonb, $11::jsonb, $12)\n RETURNING id`,\n [\n obs.session_id,\n obs.project_id ?? null,\n obs.project_slug ?? null,\n obs.type,\n obs.title,\n obs.narrative ?? null,\n obs.tool_name,\n obs.tool_input_summary ?? null,\n JSON.stringify(obs.files_read),\n JSON.stringify(obs.files_modified),\n JSON.stringify(obs.concepts),\n hash,\n ]\n );\n\n return result.rows[0]?.id ?? null;\n}\n\n/**\n * Attribute an observation to the active project covering `cwd`, then store\n * it. One shared sequence for every writer (IPC handler, cache keepalive) so\n * the lookup query exists exactly once.\n */\nexport async function storeObservationWithProject(\n pool: Pool,\n registry: RegistryBackend,\n obs: ObservationWithCwd\n): Promise<number | null> {\n let project_id: number | null = null;\n let project_slug: string | null = null;\n if (obs.cwd) {\n const row = await registry.findProjectByCwdPrefix(obs.cwd);\n if (row) {\n project_id = row.id;\n project_slug = row.slug;\n }\n }\n return storeObservation(pool, { ...obs, project_id, project_slug });\n}\n\n// ---------------------------------------------------------------------------\n// Query observations\n// ---------------------------------------------------------------------------\n\nexport async function queryObservations(\n pool: Pool,\n opts: QueryObservationsOptions = {}\n): Promise<ObservationRow[]> {\n await ensureObservationTables(pool);\n\n const conditions: string[] = [];\n const params: unknown[] = [];\n let idx = 1;\n\n if (opts.projectId !== undefined) {\n conditions.push(`project_id = $${idx++}`);\n params.push(opts.projectId);\n }\n if (opts.sessionId !== undefined) {\n conditions.push(`session_id = $${idx++}`);\n params.push(opts.sessionId);\n }\n if (opts.type !== undefined) {\n conditions.push(`type = $${idx++}`);\n params.push(opts.type);\n }\n\n const where = conditions.length > 0 ? `WHERE ${conditions.join(\" AND \")}` : \"\";\n const limit = opts.limit ?? 50;\n const offset = opts.offset ?? 0;\n\n params.push(limit, offset);\n\n const result = await pool.query<ObservationRow>(\n `SELECT id, session_id, project_id, project_slug, type, title, narrative,\n tool_name, tool_input_summary,\n files_read, files_modified, concepts,\n content_hash, created_at\n FROM pai_observations\n ${where}\n ORDER BY created_at DESC\n LIMIT $${idx++} OFFSET $${idx}`,\n params\n );\n\n return result.rows;\n}\n\nexport async function queryRecentObservations(\n pool: Pool,\n projectId: number,\n limit: number\n): Promise<ObservationRow[]> {\n await ensureObservationTables(pool);\n\n const result = await pool.query<ObservationRow>(\n `SELECT id, session_id, project_id, project_slug, type, title, narrative,\n tool_name, tool_input_summary,\n files_read, files_modified, concepts,\n content_hash, created_at\n FROM pai_observations\n WHERE project_id = $1\n ORDER BY created_at DESC\n LIMIT $2`,\n [projectId, limit]\n );\n\n return result.rows;\n}\n\nexport async function querySessionObservations(\n pool: Pool,\n sessionId: string\n): Promise<ObservationRow[]> {\n await ensureObservationTables(pool);\n\n const result = await pool.query<ObservationRow>(\n `SELECT id, session_id, project_id, project_slug, type, title, narrative,\n tool_name, tool_input_summary,\n files_read, files_modified, concepts,\n content_hash, created_at\n FROM pai_observations\n WHERE session_id = $1\n ORDER BY created_at ASC`,\n [sessionId]\n );\n\n return result.rows;\n}\n\nexport async function getObservationStats(pool: Pool): Promise<ObservationStats> {\n await ensureObservationTables(pool);\n\n const [totalRes, byTypeRes, byProjectRes, recentRes] = await Promise.all([\n pool.query<{ count: string }>(\"SELECT COUNT(*) as count FROM pai_observations\"),\n pool.query<{ type: string; count: string }>(\n \"SELECT type, COUNT(*) as count FROM pai_observations GROUP BY type ORDER BY count DESC\"\n ),\n pool.query<{ project_slug: string | null; count: string }>(\n \"SELECT project_slug, COUNT(*) as count FROM pai_observations GROUP BY project_slug ORDER BY count DESC LIMIT 15\"\n ),\n pool.query<{ created_at: string }>(\n \"SELECT created_at FROM pai_observations ORDER BY created_at DESC LIMIT 1\"\n ),\n ]);\n\n return {\n total: parseInt(totalRes.rows[0]?.count ?? \"0\", 10),\n by_type: byTypeRes.rows.map((r) => ({ type: r.type, count: parseInt(r.count, 10) })),\n by_project: byProjectRes.rows.map((r) => ({ project_slug: r.project_slug, count: parseInt(r.count, 10) })),\n most_recent: recentRes.rows[0]?.created_at ?? null,\n };\n}\n\n/**\n * Observation-type counts per vault path, for the graph_* handlers (note\n * enrichment). Falls back to an empty map when the query fails — this is\n * best-effort enrichment, never a hard dependency for graph responses.\n */\nexport async function getObservationTypesForPaths(\n pool: Pool,\n filePaths: string[],\n projectId?: number\n): Promise<Map<string, Record<string, number>>> {\n if (filePaths.length === 0) return new Map();\n\n try {\n const params: unknown[] = [filePaths];\n let projectFilter = \"\";\n if (projectId !== undefined) {\n params.push(projectId);\n projectFilter = `AND project_id = $${params.length}`;\n }\n\n const result = await pool.query<{ path: string; type: string; cnt: string }>(\n `SELECT unnested_path AS path, type, COUNT(*) AS cnt\n FROM pai_observations,\n LATERAL unnest(files_modified || files_read) AS unnested_path\n WHERE unnested_path = ANY($1::text[])\n ${projectFilter}\n GROUP BY unnested_path, type`,\n params\n );\n\n const byPath = new Map<string, Record<string, number>>();\n for (const row of result.rows) {\n const existing = byPath.get(row.path) ?? {};\n existing[row.type] = (existing[row.type] ?? 0) + parseInt(row.cnt, 10);\n byPath.set(row.path, existing);\n }\n return byPath;\n } catch {\n return new Map();\n }\n}\n\n// ---------------------------------------------------------------------------\n// Session summaries\n// ---------------------------------------------------------------------------\n\n/** Upsert a session summary. Uses ON CONFLICT on session_id so re-calling with updated content is safe. */\nexport async function storeSessionSummary(\n pool: Pool,\n summary: StoreSessionSummaryInput\n): Promise<void> {\n await ensureObservationTables(pool);\n\n await pool.query(\n `INSERT INTO pai_session_summaries\n (session_id, project_id, project_slug, request, investigated,\n learned, completed, next_steps, observation_count)\n VALUES ($1, $2, $3, $4, $5, $6, $7, $8, $9)\n ON CONFLICT (session_id) DO UPDATE SET\n project_id = EXCLUDED.project_id,\n project_slug = EXCLUDED.project_slug,\n request = EXCLUDED.request,\n investigated = EXCLUDED.investigated,\n learned = EXCLUDED.learned,\n completed = EXCLUDED.completed,\n next_steps = EXCLUDED.next_steps,\n observation_count = EXCLUDED.observation_count`,\n [\n summary.session_id,\n summary.project_id ?? null,\n summary.project_slug ?? null,\n summary.request ?? null,\n summary.investigated ?? null,\n summary.learned ?? null,\n summary.completed ?? null,\n summary.next_steps ?? null,\n summary.observation_count ?? 0,\n ]\n );\n}\n\nexport async function queryRecentSummaries(\n pool: Pool,\n projectId: number,\n limit: number\n): Promise<SessionSummaryRow[]> {\n await ensureObservationTables(pool);\n\n const result = await pool.query<SessionSummaryRow>(\n `SELECT id, session_id, project_id, project_slug,\n request, investigated, learned, completed, next_steps,\n observation_count, created_at\n FROM pai_session_summaries\n WHERE project_id = $1\n ORDER BY created_at DESC\n LIMIT $2`,\n [projectId, limit]\n );\n\n return result.rows;\n}\n\n// ---------------------------------------------------------------------------\n// Skill telemetry — self-educating skill system, Phase 1 (capture)\n// ---------------------------------------------------------------------------\n\n/**\n * Record a single skill invocation. Upserts on (scope, skill_name, source):\n * increments trigger_count, refreshes last_triggered, and dedup-merges the\n * project slug into context_projects.\n */\nexport async function recordSkillInvocation(\n pool: Pool,\n input: RecordSkillInvocationInput\n): Promise<void> {\n await ensureSkillTelemetryTable(pool);\n\n const scope = input.scope ?? \"default\";\n const source = input.source ?? \"local\";\n const proj = input.project_slug ?? null;\n const initialProjects = JSON.stringify(proj ? [proj] : []);\n\n await pool.query(\n `INSERT INTO pai_skill_telemetry\n (scope, skill_name, source, trigger_count, context_projects)\n VALUES ($1, $2, $3, 1, $4::jsonb)\n ON CONFLICT (scope, skill_name, source) DO UPDATE SET\n trigger_count = pai_skill_telemetry.trigger_count + 1,\n last_triggered = NOW(),\n context_projects = CASE\n WHEN $5::text IS NULL THEN pai_skill_telemetry.context_projects\n WHEN pai_skill_telemetry.context_projects @> to_jsonb($5::text)\n THEN pai_skill_telemetry.context_projects\n ELSE pai_skill_telemetry.context_projects || to_jsonb($5::text)\n END`,\n [scope, input.skill_name, source, initialProjects, proj]\n );\n}\n\n/** List skill telemetry rows, most-triggered first. */\nexport async function querySkillTelemetry(\n pool: Pool,\n opts: QuerySkillTelemetryOptions = {}\n): Promise<SkillTelemetryRow[]> {\n await ensureSkillTelemetryTable(pool);\n\n const conditions: string[] = [];\n const params: unknown[] = [];\n let idx = 1;\n\n conditions.push(`scope = $${idx++}`);\n params.push(opts.scope ?? \"default\");\n\n if (opts.status !== undefined) {\n conditions.push(`status = $${idx++}`);\n params.push(opts.status);\n }\n\n const where = `WHERE ${conditions.join(\" AND \")}`;\n const limit = opts.limit ?? 100;\n params.push(limit);\n\n const result = await pool.query<SkillTelemetryRow>(\n `SELECT id, scope, skill_name, source, status,\n trigger_count, accept_count,\n first_triggered, last_triggered,\n context_projects, hash, audit_status, last_audited\n FROM pai_skill_telemetry\n ${where}\n ORDER BY trigger_count DESC, last_triggered DESC\n LIMIT $${idx}`,\n params\n );\n\n return result.rows;\n}\n","/**\n * PostgresBackend — implements StorageBackend using PostgreSQL + pgvector.\n *\n * Vector similarity: pgvector's <=> cosine distance operator\n * Full-text search: PostgreSQL tsvector/tsquery (replaces SQLite FTS5)\n * Connection pooling: node-postgres Pool\n *\n * Schema is auto-initialized on first connection if tables don't exist.\n * Per-user database isolation: each macOS user gets their own database (pai_<username>).\n */\n\nimport pg from \"pg\";\nimport type { Pool, PoolClient } from \"pg\";\nimport { readFileSync } from \"node:fs\";\nimport { resolveFromModule } from \"../../module-paths.js\";\nimport type {\n StorageBackend, ChunkRow, FileRow, FederationStats,\n VaultFileRow, VaultAliasRow, VaultLinkRow, VaultHealthRow, VaultNameEntry,\n MemorySourcesReport, FindTunnelsOptions, FindTunnelsResult,\n KgStats, ObservationRow, ObservationWithCwd, StoreObservationInput, QueryObservationsOptions,\n ObservationStats, SessionSummaryRow, StoreSessionSummaryInput,\n SkillTelemetryRow, RecordSkillInvocationInput, QuerySkillTelemetryOptions,\n} from \"../interface.js\";\nimport type { SearchResult, SearchOptions } from \"../../memory/search.js\";\nimport type { PostgresConfig } from \"./config.js\";\nimport { bufferToVector } from \"./helpers.js\";\nimport { searchKeyword, searchSemantic } from \"./search.js\";\nimport * as vault from \"./vault.js\";\nimport { entityContentId, type KgEntity, type KgEntityUpsertParams } from \"../../memory/kg-entity.js\";\nimport type { KgAddParams, KgQueryParams, KgTriple, KgContradiction } from \"../../memory/kg.js\";\nimport type { IndexResult } from \"../../memory/indexer/types.js\";\nimport type { RegistryBackend } from \"../registry-interface.js\";\nimport { indexAllWithBackend } from \"../../memory/indexer/async.js\";\nimport { findTunnelsPostgres } from \"./tunnels.js\";\nimport { getMemorySourcesReportPostgres } from \"./sources.js\";\nimport * as kgTriples from \"./kg-triples.js\";\nimport * as observations from \"./observations.js\";\n\nconst { Pool: PgPool } = pg;\n\nexport class PostgresBackend implements StorageBackend {\n readonly backendType = \"postgres\" as const;\n readonly supportsPostgresFeatures = true;\n\n private pool: Pool;\n\n /**\n * Ensure the per-user database exists and has the required schema.\n * Connects to the default 'postgres' database to CREATE DATABASE if needed,\n * then connects to the target database to apply init.sql schema.\n * Safe to call multiple times (fully idempotent).\n */\n static async ensureDatabase(config: PostgresConfig): Promise<void> {\n const connStr =\n config.connectionString ??\n `postgresql://${config.user ?? \"pai\"}:${config.password ?? \"pai\"}@${config.host ?? \"localhost\"}:${config.port ?? 5432}/${config.database ?? \"pai\"}`;\n const url = new URL(connStr);\n const targetDb = url.pathname.slice(1);\n\n const adminUrl = new URL(connStr);\n adminUrl.pathname = \"/postgres\";\n const adminPool = new PgPool({\n connectionString: adminUrl.toString(),\n max: 1,\n connectionTimeoutMillis: 5000,\n });\n\n try {\n const check = await adminPool.query(\n \"SELECT 1 FROM pg_database WHERE datname = $1\",\n [targetDb]\n );\n if (check.rowCount === 0) {\n await adminPool.query(`CREATE DATABASE \"${targetDb}\"`);\n process.stderr.write(`[pai-postgres] Created database: ${targetDb}\\n`);\n }\n } finally {\n await adminPool.end();\n }\n\n const targetPool = new PgPool({\n connectionString: connStr,\n max: 1,\n connectionTimeoutMillis: 5000,\n });\n\n try {\n const tableCheck = await targetPool.query(\n \"SELECT 1 FROM information_schema.tables WHERE table_name = 'pai_chunks'\"\n );\n if (tableCheck.rowCount === 0) {\n const initSql = readFileSync(resolveFromModule(import.meta.url, \"docker/init.sql\"), \"utf-8\");\n await targetPool.query(initSql);\n process.stderr.write(`[pai-postgres] Applied schema to database: ${targetDb}\\n`);\n }\n\n // Run incremental migrations for existing databases\n await PostgresBackend.runMigrations(targetPool);\n } finally {\n await targetPool.end();\n }\n }\n\n /**\n * Run incremental migrations for existing databases.\n * Each migration is idempotent — safe to run on databases that already have the change.\n */\n private static async runMigrations(pool: Pool): Promise<void> {\n // Migration: add confidence column to vault_links if it does not exist\n const colCheck = await pool.query(\n `SELECT 1 FROM information_schema.columns\n WHERE table_name = 'vault_links' AND column_name = 'confidence'`\n );\n if (colCheck.rowCount === 0) {\n await pool.query(\n \"ALTER TABLE vault_links ADD COLUMN confidence TEXT NOT NULL DEFAULT 'EXTRACTED'\"\n );\n process.stderr.write(\"[pai-postgres] Migration: added confidence column to vault_links\\n\");\n }\n\n // Migration: create kg_triples table if it does not exist\n const kgCheck = await pool.query(\n `SELECT 1 FROM information_schema.tables WHERE table_name = 'kg_triples'`\n );\n if (kgCheck.rowCount === 0) {\n await pool.query(`\n CREATE TABLE kg_triples (\n id SERIAL PRIMARY KEY,\n subject TEXT NOT NULL,\n predicate TEXT NOT NULL,\n object TEXT NOT NULL,\n project_id INTEGER,\n source_session TEXT,\n valid_from TIMESTAMP DEFAULT CURRENT_TIMESTAMP,\n valid_to TIMESTAMP,\n confidence TEXT DEFAULT 'EXTRACTED',\n created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP\n )\n `);\n await pool.query(`CREATE INDEX idx_kg_subject ON kg_triples(subject)`);\n await pool.query(`CREATE INDEX idx_kg_predicate ON kg_triples(predicate)`);\n await pool.query(`CREATE INDEX idx_kg_object ON kg_triples(object)`);\n await pool.query(`CREATE INDEX idx_kg_valid ON kg_triples(valid_from, valid_to)`);\n process.stderr.write(\"[pai-postgres] Migration: created kg_triples table\\n\");\n }\n\n // Migration: kg_entities + registry tables (projects, sessions, tags,\n // project_tags, session_tags, aliases, links, compaction_log).\n // Every statement is IF NOT EXISTS — safe to run on every startup.\n await pool.query(`\n CREATE 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 BIGINT,\n last_seen BIGINT,\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 CREATE INDEX IF NOT EXISTS idx_kge_tenant ON kg_entities(tenant_id);\n CREATE INDEX IF NOT EXISTS idx_kge_name ON kg_entities(tenant_id, name);\n CREATE INDEX IF NOT EXISTS idx_kge_type ON kg_entities(tenant_id, type);\n\n CREATE TABLE IF NOT EXISTS projects (\n id SERIAL PRIMARY KEY,\n slug TEXT NOT NULL UNIQUE,\n display_name TEXT NOT NULL,\n root_path TEXT NOT NULL UNIQUE,\n encoded_dir TEXT NOT NULL UNIQUE,\n type TEXT NOT NULL DEFAULT 'local'\n CHECK (type IN ('local','central','obsidian-linked','external')),\n status TEXT NOT NULL DEFAULT 'active'\n CHECK (status IN ('active','archived','migrating')),\n parent_id INTEGER REFERENCES projects(id),\n obsidian_link TEXT,\n claude_notes_dir TEXT,\n session_config TEXT,\n created_at BIGINT NOT NULL,\n updated_at BIGINT NOT NULL,\n archived_at BIGINT\n );\n\n CREATE TABLE IF NOT EXISTS sessions (\n id SERIAL PRIMARY KEY,\n project_id INTEGER NOT NULL REFERENCES projects(id),\n number INTEGER NOT NULL,\n date TEXT NOT NULL,\n slug TEXT NOT NULL,\n title TEXT NOT NULL,\n filename TEXT NOT NULL,\n status TEXT NOT NULL DEFAULT 'open'\n CHECK (status IN ('open','completed','compacted')),\n claude_session_id TEXT,\n token_count INTEGER,\n created_at BIGINT NOT NULL,\n closed_at BIGINT,\n UNIQUE (project_id, number)\n );\n\n CREATE TABLE IF NOT EXISTS tags (\n id SERIAL PRIMARY KEY,\n name TEXT NOT NULL UNIQUE\n );\n\n CREATE TABLE IF NOT EXISTS project_tags (\n project_id INTEGER NOT NULL REFERENCES projects(id),\n tag_id INTEGER NOT NULL REFERENCES tags(id),\n PRIMARY KEY (project_id, tag_id)\n );\n\n CREATE TABLE IF NOT EXISTS session_tags (\n session_id INTEGER NOT NULL REFERENCES sessions(id),\n tag_id INTEGER NOT NULL REFERENCES tags(id),\n PRIMARY KEY (session_id, tag_id)\n );\n\n CREATE TABLE IF NOT EXISTS aliases (\n alias TEXT PRIMARY KEY,\n project_id INTEGER NOT NULL REFERENCES projects(id)\n );\n\n CREATE TABLE IF NOT EXISTS compaction_log (\n id SERIAL PRIMARY KEY,\n project_id INTEGER NOT NULL REFERENCES projects(id),\n session_id INTEGER REFERENCES sessions(id),\n trigger TEXT NOT NULL CHECK (trigger IN ('precompact','manual','end-session')),\n files_written TEXT NOT NULL,\n token_count INTEGER,\n created_at BIGINT NOT NULL\n );\n\n CREATE TABLE IF NOT EXISTS links (\n id SERIAL PRIMARY KEY,\n session_id INTEGER NOT NULL REFERENCES sessions(id),\n target_project_id INTEGER NOT NULL REFERENCES projects(id),\n link_type TEXT NOT NULL DEFAULT 'related'\n CHECK (link_type IN ('related','follow-up','reference')),\n created_at BIGINT NOT NULL,\n UNIQUE (session_id, target_project_id)\n );\n\n CREATE INDEX IF NOT EXISTS idx_projects_slug ON projects(slug);\n CREATE INDEX IF NOT EXISTS idx_projects_status ON projects(status);\n CREATE INDEX IF NOT EXISTS idx_projects_type ON projects(type);\n CREATE INDEX IF NOT EXISTS idx_sessions_project ON sessions(project_id);\n CREATE INDEX IF NOT EXISTS idx_sessions_date ON sessions(date);\n CREATE INDEX IF NOT EXISTS idx_sessions_status ON sessions(status);\n CREATE INDEX IF NOT EXISTS idx_sessions_claude ON sessions(claude_session_id);\n CREATE INDEX IF NOT EXISTS idx_pc_project ON project_tags(project_id);\n `);\n\n // Migration: add relevance_score/last_accessed_at to pai_chunks (feedback loop + QW2).\n const feedbackColCheck = await pool.query(\n `SELECT column_name FROM information_schema.columns\n WHERE table_name = 'pai_chunks' AND column_name IN ('relevance_score', 'last_accessed_at')`\n );\n const existingFeedbackCols = new Set(feedbackColCheck.rows.map((r: { column_name: string }) => r.column_name));\n if (!existingFeedbackCols.has(\"relevance_score\")) {\n await pool.query(\"ALTER TABLE pai_chunks ADD COLUMN relevance_score REAL\");\n process.stderr.write(\"[pai-postgres] Migration: added relevance_score column to pai_chunks\\n\");\n }\n if (!existingFeedbackCols.has(\"last_accessed_at\")) {\n await pool.query(\"ALTER TABLE pai_chunks ADD COLUMN last_accessed_at BIGINT\");\n process.stderr.write(\"[pai-postgres] Migration: added last_accessed_at column to pai_chunks\\n\");\n }\n }\n\n constructor(config: PostgresConfig) {\n const connStr =\n config.connectionString ??\n `postgresql://${config.user ?? \"pai\"}:${config.password ?? \"pai\"}@${config.host ?? \"localhost\"}:${config.port ?? 5432}/${config.database ?? \"pai\"}`;\n\n this.pool = new PgPool({\n connectionString: connStr,\n max: config.maxConnections ?? 5,\n connectionTimeoutMillis: config.connectionTimeoutMs ?? 5000,\n idleTimeoutMillis: 30_000,\n // A live pg Pool otherwise keeps a ref'd socket even while idle,\n // blocking a CLI process from reaching Node's 'beforeExit' idle state\n // until the pool's own idle timeout — this lets it go idle immediately.\n allowExitOnIdle: true,\n });\n\n this.pool.on(\"error\", (err) => {\n process.stderr.write(`[pai-postgres] Pool error: ${err.message}\\n`);\n });\n }\n\n // -------------------------------------------------------------------------\n // Lifecycle\n // -------------------------------------------------------------------------\n\n async close(): Promise<void> {\n await this.pool.end();\n }\n\n /**\n * Expose the underlying pg.Pool for callers that need direct query access\n * (e.g. the daemon's observation IPC methods).\n */\n getPool(): Pool {\n return this.pool;\n }\n\n async getStats(): Promise<FederationStats> {\n const client = await this.pool.connect();\n try {\n const filesResult = await client.query<{ n: string }>(\n \"SELECT COUNT(*)::text AS n FROM pai_files\"\n );\n const chunksResult = await client.query<{ n: string }>(\n \"SELECT COUNT(*)::text AS n FROM pai_chunks\"\n );\n return {\n files: parseInt(filesResult.rows[0]?.n ?? \"0\", 10),\n chunks: parseInt(chunksResult.rows[0]?.n ?? \"0\", 10),\n };\n } finally {\n client.release();\n }\n }\n\n async getProjectStats(projectId: number): Promise<FederationStats> {\n const filesResult = await this.pool.query<{ n: string }>(\n \"SELECT COUNT(*)::text AS n FROM pai_files WHERE project_id = $1\",\n [projectId]\n );\n const chunksResult = await this.pool.query<{ n: string }>(\n \"SELECT COUNT(*)::text AS n FROM pai_chunks WHERE project_id = $1\",\n [projectId]\n );\n return {\n files: parseInt(filesResult.rows[0]?.n ?? \"0\", 10),\n chunks: parseInt(chunksResult.rows[0]?.n ?? \"0\", 10),\n };\n }\n\n /**\n * Test the connection by running a trivial query.\n * Returns null on success, error message on failure.\n */\n async testConnection(): Promise<string | null> {\n let client: PoolClient | null = null;\n try {\n client = await this.pool.connect();\n await client.query(\"SELECT 1\");\n return null;\n } catch (e) {\n return e instanceof Error ? e.message : String(e);\n } finally {\n client?.release();\n }\n }\n\n // -------------------------------------------------------------------------\n // File tracking\n // -------------------------------------------------------------------------\n\n async getFileHash(projectId: number, path: string): Promise<string | undefined> {\n const result = await this.pool.query<{ hash: string }>(\n \"SELECT hash FROM pai_files WHERE project_id = $1 AND path = $2\",\n [projectId, path]\n );\n return result.rows[0]?.hash;\n }\n\n async upsertFile(file: FileRow): Promise<void> {\n await this.pool.query(\n `INSERT INTO pai_files (project_id, path, source, tier, hash, mtime, size)\n VALUES ($1, $2, $3, $4, $5, $6, $7)\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 [file.projectId, file.path, file.source, file.tier, file.hash, file.mtime, file.size]\n );\n }\n\n // -------------------------------------------------------------------------\n // Chunk management\n // -------------------------------------------------------------------------\n\n async getChunkIds(projectId: number, path: string): Promise<string[]> {\n const result = await this.pool.query<{ id: string }>(\n \"SELECT id FROM pai_chunks WHERE project_id = $1 AND path = $2\",\n [projectId, path]\n );\n return result.rows.map((r) => r.id);\n }\n\n async deleteChunksForFile(projectId: number, path: string): Promise<void> {\n await this.pool.query(\n \"DELETE FROM pai_chunks WHERE project_id = $1 AND path = $2\",\n [projectId, path]\n );\n }\n\n async insertChunks(chunks: ChunkRow[]): Promise<void> {\n if (chunks.length === 0) return;\n\n const client = await this.pool.connect();\n try {\n await client.query(\"BEGIN\");\n\n for (const c of chunks) {\n const safeText = c.text.replace(/\\0/g, \"\");\n\n await client.query(\n `INSERT INTO pai_chunks\n (id, project_id, source, tier, path, start_line, end_line, hash, text, updated_at, fts_vector)\n VALUES\n ($1, $2, $3, $4, $5, $6, $7, $8, $9, $10,\n to_tsvector('simple', $9))\n ON CONFLICT (id) DO UPDATE SET\n project_id = EXCLUDED.project_id,\n source = EXCLUDED.source,\n tier = EXCLUDED.tier,\n path = EXCLUDED.path,\n start_line = EXCLUDED.start_line,\n end_line = EXCLUDED.end_line,\n hash = EXCLUDED.hash,\n text = EXCLUDED.text,\n updated_at = EXCLUDED.updated_at,\n fts_vector = EXCLUDED.fts_vector`,\n [\n c.id, c.projectId, c.source, c.tier, c.path,\n c.startLine, c.endLine, c.hash, safeText, c.updatedAt,\n ]\n );\n }\n\n await client.query(\"COMMIT\");\n } catch (e) {\n await client.query(\"ROLLBACK\");\n throw e;\n } finally {\n client.release();\n }\n }\n\n async getDistinctChunkPaths(projectId: number): Promise<string[]> {\n const result = await this.pool.query<{ path: string }>(\n \"SELECT DISTINCT path FROM pai_chunks WHERE project_id = $1\",\n [projectId]\n );\n return result.rows.map((r) => r.path);\n }\n\n async deletePaths(projectId: number, paths: string[]): Promise<void> {\n if (paths.length === 0) return;\n const client = await this.pool.connect();\n try {\n await client.query(\"BEGIN\");\n for (const path of paths) {\n await client.query(\"DELETE FROM pai_chunks WHERE project_id = $1 AND path = $2\", [projectId, path]);\n await client.query(\"DELETE FROM pai_files WHERE project_id = $1 AND path = $2\", [projectId, path]);\n }\n await client.query(\"COMMIT\");\n } catch (e) {\n await client.query(\"ROLLBACK\");\n throw e;\n } finally {\n client.release();\n }\n }\n\n async getUnembeddedChunkIds(\n projectId?: number,\n limit?: number,\n after?: { projectId: number; id: string } | null,\n ): Promise<Array<{ id: string; text: string; project_id: number; path: string }>> {\n const params: Array<number | string> = [];\n let sql = \"SELECT id, text, project_id, path FROM pai_chunks WHERE embedding IS NULL\";\n if (projectId !== undefined) {\n params.push(projectId);\n sql += ` AND project_id = $${params.length}`;\n }\n if (after) {\n params.push(after.projectId, after.projectId, after.id);\n sql += ` AND (project_id > $${params.length - 2} OR (project_id = $${params.length - 1} AND id > $${params.length}))`;\n }\n sql += \" ORDER BY project_id, id\";\n if (limit !== undefined) {\n params.push(limit);\n sql += ` LIMIT $${params.length}`;\n }\n const result = await this.pool.query<{ id: string; text: string; project_id: number; path: string }>(sql, params);\n return result.rows;\n }\n\n async updateEmbedding(chunkId: string, embedding: Buffer): Promise<void> {\n const vec = bufferToVector(embedding);\n const vecStr = \"[\" + vec.join(\",\") + \"]\";\n await this.pool.query(\n \"UPDATE pai_chunks SET embedding = $1::vector WHERE id = $2\",\n [vecStr, chunkId]\n );\n }\n\n // -------------------------------------------------------------------------\n // Search\n // -------------------------------------------------------------------------\n\n async searchKeyword(query: string, opts?: SearchOptions): Promise<SearchResult[]> {\n return searchKeyword(this.pool, query, opts);\n }\n\n async searchSemantic(queryEmbedding: Float32Array, opts?: SearchOptions): Promise<SearchResult[]> {\n return searchSemantic(this.pool, queryEmbedding, opts);\n }\n\n // -------------------------------------------------------------------------\n // Vault operations — delegated to vault.ts\n // -------------------------------------------------------------------------\n\n async upsertVaultFile(file: VaultFileRow): Promise<void> { return vault.upsertVaultFile(this.pool, file); }\n async deleteVaultFile(vaultPath: string): Promise<void> { return vault.deleteVaultFile(this.pool, vaultPath); }\n async getVaultFile(vaultPath: string): Promise<VaultFileRow | null> { return vault.getVaultFile(this.pool, vaultPath); }\n async getVaultFileByInode(inode: number, device: number): Promise<VaultFileRow | null> { return vault.getVaultFileByInode(this.pool, inode, device); }\n async getAllVaultFiles(): Promise<VaultFileRow[]> { return vault.getAllVaultFiles(this.pool); }\n async getRecentVaultFiles(sinceMs: number): Promise<VaultFileRow[]> { return vault.getRecentVaultFiles(this.pool, sinceMs); }\n async countVaultFiles(): Promise<number> { return vault.countVaultFiles(this.pool); }\n async countVaultFilesWithPrefix(prefix: string): Promise<number> { return vault.countVaultFilesWithPrefix(this.pool, prefix); }\n async countVaultFilesAfter(sinceMs: number): Promise<number> { return vault.countVaultFilesAfter(this.pool, sinceMs); }\n async getVaultFilesByPaths(paths: string[]): Promise<VaultFileRow[]> { return vault.getVaultFilesByPaths(this.pool, paths); }\n async getVaultFilesByPathsAfter(paths: string[], sinceMs: number): Promise<VaultFileRow[]> { return vault.getVaultFilesByPathsAfter(this.pool, paths, sinceMs); }\n async getAllVaultFilePaths(): Promise<string[]> { return vault.getAllVaultFilePaths(this.pool); }\n async getVaultFilePathsWithPrefix(prefix: string): Promise<string[]> { return vault.getVaultFilePathsWithPrefix(this.pool, prefix); }\n async getVaultFilePathsAfter(sinceMs: number): Promise<string[]> { return vault.getVaultFilePathsAfter(this.pool, sinceMs); }\n\n async upsertVaultAliases(aliases: VaultAliasRow[]): Promise<void> { return vault.upsertVaultAliases(this.pool, aliases); }\n async deleteVaultAliases(canonicalPath: string): Promise<void> { return vault.deleteVaultAliases(this.pool, canonicalPath); }\n async getVaultAlias(vaultPath: string): Promise<{ canonicalPath: string } | null> { return vault.getVaultAlias(this.pool, vaultPath); }\n\n async replaceLinksForSources(sourcePaths: string[], links: VaultLinkRow[]): Promise<void> { return vault.replaceLinksForSources(this.pool, sourcePaths, links); }\n async getLinksFromSource(sourcePath: string): Promise<VaultLinkRow[]> { return vault.getLinksFromSource(this.pool, sourcePath); }\n async getLinksToTarget(targetPath: string): Promise<VaultLinkRow[]> { return vault.getLinksToTarget(this.pool, targetPath); }\n async getVaultLinkGraph(): Promise<Array<{ source_path: string; target_path: string }>> { return vault.getVaultLinkGraph(this.pool); }\n async getDeadLinks(): Promise<Array<{ sourcePath: string; targetRaw: string }>> { return vault.getDeadLinks(this.pool); }\n async getDeadLinksWithLineNumbers(): Promise<Array<{ sourcePath: string; targetRaw: string; lineNumber: number }>> { return vault.getDeadLinksWithLineNumbers(this.pool); }\n async getDeadLinksWithPrefix(prefix: string): Promise<Array<{ sourcePath: string; targetRaw: string; lineNumber: number }>> { return vault.getDeadLinksWithPrefix(this.pool, prefix); }\n async getDeadLinksAfter(sinceMs: number): Promise<Array<{ sourcePath: string; targetRaw: string; lineNumber: number }>> { return vault.getDeadLinksAfter(this.pool, sinceMs); }\n async countVaultLinksWithPrefix(prefix: string): Promise<number> { return vault.countVaultLinksWithPrefix(this.pool, prefix); }\n async countVaultLinksAfter(sinceMs: number): Promise<number> { return vault.countVaultLinksAfter(this.pool, sinceMs); }\n async getVaultLinksFromPaths(sourcePaths: string[]): Promise<VaultLinkRow[]> { return vault.getVaultLinksFromPaths(this.pool, sourcePaths); }\n async getVaultLinkEdges(): Promise<Array<{ source: string; target: string }>> { return vault.getVaultLinkEdges(this.pool); }\n async getVaultLinkEdgesWithPrefix(prefix: string): Promise<Array<{ source: string; target: string }>> { return vault.getVaultLinkEdgesWithPrefix(this.pool, prefix); }\n async getVaultLinkEdgesAfter(sinceMs: number): Promise<Array<{ source: string; target: string }>> { return vault.getVaultLinkEdgesAfter(this.pool, sinceMs); }\n\n async upsertVaultHealth(rows: VaultHealthRow[]): Promise<void> { return vault.upsertVaultHealth(this.pool, rows); }\n async getVaultHealth(vaultPath: string): Promise<VaultHealthRow | null> { return vault.getVaultHealth(this.pool, vaultPath); }\n async getOrphans(): Promise<VaultHealthRow[]> { return vault.getOrphans(this.pool); }\n async getOrphansWithPrefix(prefix: string): Promise<string[]> { return vault.getOrphansWithPrefix(this.pool, prefix); }\n async getOrphansAfter(sinceMs: number): Promise<string[]> { return vault.getOrphansAfter(this.pool, sinceMs); }\n async getLowConnectivity(): Promise<string[]> { return vault.getLowConnectivity(this.pool); }\n async getLowConnectivityWithPrefix(prefix: string): Promise<string[]> { return vault.getLowConnectivityWithPrefix(this.pool, prefix); }\n async getLowConnectivityAfter(sinceMs: number): Promise<string[]> { return vault.getLowConnectivityAfter(this.pool, sinceMs); }\n\n async upsertNameIndex(entries: VaultNameEntry[]): Promise<void> { return vault.upsertNameIndex(this.pool, entries); }\n async replaceNameIndex(entries: VaultNameEntry[]): Promise<void> { return vault.replaceNameIndex(this.pool, entries); }\n async resolveVaultName(name: string): Promise<string[]> { return vault.resolveVaultName(this.pool, name); }\n async searchVaultNameIndex(query: string, limit?: number): Promise<string[]> { return vault.searchVaultNameIndex(this.pool, query, limit); }\n\n // Legacy memory_chunks methods (used by graph and zettelkasten modules)\n async getChunksWithEmbeddings(projectId: number, limit: number): Promise<Array<{ path: string; text: string; embedding: Buffer }>> {\n const r = await this.pool.query<{ path: string; text: string; embedding: Buffer }>(\n `SELECT path, text, embedding FROM memory_chunks WHERE project_id = $1 AND embedding IS NOT NULL ORDER BY path, start_line LIMIT $2`,\n [projectId, limit]\n );\n return r.rows;\n }\n\n async getChunksForPath(projectId: number, path: string, limit = 20): Promise<Array<{ text: string; embedding: Buffer | null }>> {\n const r = await this.pool.query<{ text: string; embedding: Buffer | null }>(\n `SELECT text, embedding FROM memory_chunks WHERE project_id = $1 AND path = $2 AND embedding IS NOT NULL ORDER BY start_line LIMIT $3`,\n [projectId, path, limit]\n );\n return r.rows;\n }\n\n async searchChunksByText(projectId: number, query: string, limit: number): Promise<Array<{ path: string; text: string }>> {\n const r = await this.pool.query<{ path: string; text: string }>(\n `SELECT DISTINCT path, text FROM memory_chunks WHERE project_id = $1 AND lower(text) LIKE lower($2) LIMIT $3`,\n [projectId, `%${query}%`, limit]\n );\n return r.rows;\n }\n\n // -------------------------------------------------------------------------\n // Knowledge-graph entities (kg_entities)\n // -------------------------------------------------------------------------\n\n async upsertKgEntity(params: KgEntityUpsertParams): Promise<string> {\n const tenantId = params.tenantId ?? \"default\";\n const entityId = entityContentId(params.name, tenantId);\n const now = Date.now();\n\n await this.pool.query(\n `INSERT INTO kg_entities\n (entity_id, tenant_id, name, type, description, first_seen, last_seen, mention_count, feedback_weight)\n VALUES\n ($1, $2, $3, $4, $5, $6, $7, 1, 0.5)\n ON CONFLICT (entity_id) DO UPDATE SET\n last_seen = EXCLUDED.last_seen,\n mention_count = kg_entities.mention_count + 1,\n description = COALESCE(EXCLUDED.description, kg_entities.description),\n type = CASE WHEN EXCLUDED.type != 'unknown' THEN EXCLUDED.type ELSE kg_entities.type END`,\n [entityId, tenantId, params.name, params.type ?? \"unknown\", params.description ?? null, now, now]\n );\n\n return entityId;\n }\n\n async findKgEntity(name: string, tenantId = \"default\"): Promise<KgEntity | null> {\n const entityId = entityContentId(name, tenantId);\n const result = await this.pool.query(\n \"SELECT * FROM kg_entities WHERE entity_id = $1 AND tenant_id = $2\",\n [entityId, tenantId]\n );\n const row = result.rows[0];\n return row ? rowToKgEntity(row) : null;\n }\n\n async listKgEntities(tenantId = \"default\", type?: string, limit = 100): Promise<KgEntity[]> {\n const result = type\n ? await this.pool.query(\n \"SELECT * FROM kg_entities WHERE tenant_id = $1 AND type = $2 ORDER BY mention_count DESC LIMIT $3\",\n [tenantId, type, limit]\n )\n : await this.pool.query(\n \"SELECT * FROM kg_entities WHERE tenant_id = $1 ORDER BY mention_count DESC LIMIT $2\",\n [tenantId, limit]\n );\n return result.rows.map(rowToKgEntity);\n }\n\n async updateEntityFeedbackWeight(entityId: string, normalizedRating: number, alpha = 0.1): Promise<void> {\n const result = await this.pool.query<{ feedback_weight: number }>(\n \"SELECT feedback_weight FROM kg_entities WHERE entity_id = $1\",\n [entityId]\n );\n const row = result.rows[0];\n if (!row) return;\n\n const newWeight = Number(row.feedback_weight) + alpha * (normalizedRating - Number(row.feedback_weight));\n await this.pool.query(\n \"UPDATE kg_entities SET feedback_weight = $1 WHERE entity_id = $2\",\n [newWeight, entityId]\n );\n }\n\n // -------------------------------------------------------------------------\n // Chunk feedback / access tracking\n // -------------------------------------------------------------------------\n\n async getChunksForFeedback(chunkIds: string[]): Promise<Array<{ id: string; text: string; relevanceScore: number | null }>> {\n if (chunkIds.length === 0) return [];\n const result = await this.pool.query<{ id: string; text: string; relevance_score: number | null }>(\n \"SELECT id, text, relevance_score FROM pai_chunks WHERE id = ANY($1)\",\n [chunkIds]\n );\n return result.rows.map((r) => ({ id: r.id, text: r.text, relevanceScore: r.relevance_score }));\n }\n\n async updateChunkRelevanceScore(chunkId: string, score: number): Promise<void> {\n await this.pool.query(\"UPDATE pai_chunks SET relevance_score = $1 WHERE id = $2\", [score, chunkId]);\n }\n\n async touchChunksLastAccessed(chunkIds: string[]): Promise<void> {\n if (chunkIds.length === 0) return;\n try {\n await this.pool.query(\"UPDATE pai_chunks SET last_accessed_at = $1 WHERE id = ANY($2)\", [Date.now(), chunkIds]);\n } catch {\n // non-critical — never block search results\n }\n }\n\n // -------------------------------------------------------------------------\n // Global indexing\n // -------------------------------------------------------------------------\n\n async indexAll(registry: RegistryBackend): Promise<{ projects: number; result: IndexResult }> {\n return indexAllWithBackend(this, registry);\n }\n\n // -------------------------------------------------------------------------\n // Cross-project concept tunnels\n // -------------------------------------------------------------------------\n\n async findTunnels(registry: RegistryBackend, options?: FindTunnelsOptions): Promise<FindTunnelsResult> {\n return findTunnelsPostgres(this.pool, registry, options);\n }\n\n // -------------------------------------------------------------------------\n // Memory sources report\n // -------------------------------------------------------------------------\n\n async getMemorySourcesReport(): Promise<MemorySourcesReport> {\n return getMemorySourcesReportPostgres(this.pool);\n }\n\n // -------------------------------------------------------------------------\n // Temporal knowledge graph (kg_triples) — delegated to kg-triples.ts\n // -------------------------------------------------------------------------\n\n async addKgTriple(params: KgAddParams): Promise<KgTriple> { return kgTriples.addKgTriple(this.pool, params); }\n async queryKgTriples(params: KgQueryParams): Promise<KgTriple[]> { return kgTriples.queryKgTriples(this.pool, params); }\n async invalidateKgTriple(tripleId: number): Promise<void> { return kgTriples.invalidateKgTriple(this.pool, tripleId); }\n async getKgContradictions(subject: string): Promise<KgContradiction[]> { return kgTriples.getKgContradictions(this.pool, subject); }\n async getKgStats(): Promise<KgStats> { return kgTriples.getKgStats(this.pool); }\n\n // -------------------------------------------------------------------------\n // Observations / session summaries / skill telemetry — delegated to observations.ts\n // -------------------------------------------------------------------------\n\n async storeObservation(obs: StoreObservationInput): Promise<number | null> { return observations.storeObservation(this.pool, obs); }\n async storeObservationWithProject(registry: RegistryBackend, obs: ObservationWithCwd): Promise<number | null> {\n return observations.storeObservationWithProject(this.pool, registry, obs);\n }\n async queryObservations(opts?: QueryObservationsOptions): Promise<ObservationRow[]> { return observations.queryObservations(this.pool, opts); }\n async queryRecentObservations(projectId: number, limit: number): Promise<ObservationRow[]> { return observations.queryRecentObservations(this.pool, projectId, limit); }\n async querySessionObservations(sessionId: string): Promise<ObservationRow[]> { return observations.querySessionObservations(this.pool, sessionId); }\n async getObservationStats(): Promise<ObservationStats> { return observations.getObservationStats(this.pool); }\n async getObservationTypesForPaths(filePaths: string[], projectId?: number): Promise<Map<string, Record<string, number>>> {\n return observations.getObservationTypesForPaths(this.pool, filePaths, projectId);\n }\n async storeSessionSummary(summary: StoreSessionSummaryInput): Promise<void> { return observations.storeSessionSummary(this.pool, summary); }\n async queryRecentSummaries(projectId: number, limit: number): Promise<SessionSummaryRow[]> { return observations.queryRecentSummaries(this.pool, projectId, limit); }\n async recordSkillInvocation(input: RecordSkillInvocationInput): Promise<void> { return observations.recordSkillInvocation(this.pool, input); }\n async querySkillTelemetry(opts?: QuerySkillTelemetryOptions): Promise<SkillTelemetryRow[]> { return observations.querySkillTelemetry(this.pool, opts); }\n\n // -------------------------------------------------------------------------\n // File-path rename (session cleanup: moving Notes/*.md into YYYY/MM/)\n // -------------------------------------------------------------------------\n\n async countFilesWithPaths(paths: string[]): Promise<number> {\n if (paths.length === 0) return 0;\n const result = await this.pool.query<{ n: string }>(\n \"SELECT COUNT(*)::text AS n FROM pai_files WHERE path = ANY($1)\",\n [paths]\n );\n return parseInt(result.rows[0]?.n ?? \"0\", 10);\n }\n\n async renameFilePaths(moves: Array<{ oldPath: string; newPath: string }>): Promise<number> {\n if (moves.length === 0) return 0;\n const client = await this.pool.connect();\n let filesUpdated = 0;\n try {\n await client.query(\"BEGIN\");\n for (const { oldPath, newPath } of moves) {\n const r = await client.query(\"UPDATE pai_files SET path = $1 WHERE path = $2\", [newPath, oldPath]);\n filesUpdated += r.rowCount ?? 0;\n await client.query(\"UPDATE pai_chunks SET path = $1 WHERE path = $2\", [newPath, oldPath]);\n }\n await client.query(\"COMMIT\");\n } catch (e) {\n await client.query(\"ROLLBACK\");\n throw e;\n } finally {\n client.release();\n }\n return filesUpdated;\n }\n}\n\n/**\n * Postgres returns BIGINT columns (first_seen/last_seen) as strings — JS\n * numbers can't safely hold the full 64-bit range. Entity timestamps are\n * Date.now() ms and well inside Number.isSafeInteger, so a plain Number()\n * conversion here keeps the shape identical to better-sqlite3's row objects.\n */\nfunction rowToKgEntity(row: Record<string, unknown>): KgEntity {\n return {\n entity_id: row.entity_id as string,\n tenant_id: row.tenant_id as string,\n name: row.name as string,\n type: row.type as string,\n description: row.description as string | undefined,\n first_seen: row.first_seen != null ? Number(row.first_seen) : undefined,\n last_seen: row.last_seen != null ? Number(row.last_seen) : undefined,\n mention_count: Number(row.mention_count),\n feedback_weight: Number(row.feedback_weight),\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAyBA,SAAgB,kBAAkB,WAAmB,KAAa,YAAY,GAAW;CACvF,IAAI,MAAM,QAAQ,cAAc,UAAU,CAAC;AAC3C,MAAK,IAAI,IAAI,GAAG,IAAI,WAAW,KAAK;EAClC,MAAM,YAAY,KAAK,KAAK,IAAI;AAChC,MAAI,WAAW,UAAU,CAAE,QAAO;EAClC,MAAM,SAAS,QAAQ,IAAI;AAC3B,MAAI,WAAW,IAAK;AACpB,QAAM;;AAER,OAAM,IAAI,MAAM,kBAAkB,IAAI,UAAU,QAAQ,cAAc,UAAU,CAAC,CAAC,0CAA0C;;;AAI9H,SAAgB,YAAY,WAA2B;AACrD,QAAO,QAAQ,kBAAkB,WAAW,eAAe,CAAC;;;;;;;;;;;AC9B9D,SAAgB,eAAe,KAAuB;CACpD,MAAM,SAAmB,EAAE;AAC3B,MAAK,IAAI,IAAI,GAAG,IAAI,IAAI,QAAQ,KAAK,EACnC,QAAO,KAAK,IAAI,YAAY,EAAE,CAAC;AAEjC,QAAO;;;;;;;;;;;;;AAcT,SAAgB,eAAe,OAAuB;CACpD,MAAM,SAAS,MACZ,aAAa,CACb,MAAM,cAAc,CACpB,OAAO,QAAQ,CACf,QAAQ,MAAM,EAAE,UAAU,EAAE,CAC5B,QAAQ,MAAM,CAAC,WAAW,IAAI,EAAE,CAAC,CAEjC,KAAK,MAAM,EAAE,QAAQ,MAAM,KAAK,CAAC,QAAQ,aAAa,GAAG,CAAC,CAC1D,OAAO,QAAQ;AAElB,KAAI,OAAO,WAAW,EAEpB,QADY,MAAM,QAAQ,eAAe,IAAI,CAAC,MAAM,CAAC,MAAM,MAAM,CAAC,OAAO,QAAQ,CAAC,KAAK,MAAM,IAC/E;AAGhB,QAAO,OAAO,KAAK,MAAM;;;;;;;;;;;;;;;;;;;;;;;;AChB3B,SAAS,cAAc,MAA8B,OAAuB;CAC1E,MAAM,SAAS,iBAAiB,QAAQ,MAAM,UAAU,OAAO,MAAM;AACrE,wBAAO,IAAI,MACT,YAAY,KAAK,8KAEkD,SACpE;;;;;AAMH,eAAsB,cACpB,MACA,OACA,MACyB;CACzB,MAAM,aAAa,MAAM,cAAc;CAEvC,MAAM,UAAU,eAAe,MAAM;AACrC,KAAI,CAAC,QAAS,QAAO,EAAE;CAEvB,MAAM,aAAuB,CAAC,yCAAyC;CACvE,MAAM,SAA8B,CAAC,QAAQ;CAC7C,IAAI,WAAW;AAEf,KAAI,MAAM,cAAc,KAAK,WAAW,SAAS,GAAG;EAClD,MAAM,eAAe,KAAK,WAAW,UAAU,IAAI,aAAa,CAAC,KAAK,KAAK;AAC3E,aAAW,KAAK,kBAAkB,aAAa,GAAG;AAClD,SAAO,KAAK,GAAG,KAAK,WAAW;;AAGjC,KAAI,MAAM,WAAW,KAAK,QAAQ,SAAS,GAAG;EAC5C,MAAM,eAAe,KAAK,QAAQ,UAAU,IAAI,aAAa,CAAC,KAAK,KAAK;AACxE,aAAW,KAAK,cAAc,aAAa,GAAG;AAC9C,SAAO,KAAK,GAAG,KAAK,QAAQ;;AAG9B,KAAI,MAAM,SAAS,KAAK,MAAM,SAAS,GAAG;EACxC,MAAM,eAAe,KAAK,MAAM,UAAU,IAAI,aAAa,CAAC,KAAK,KAAK;AACtE,aAAW,KAAK,YAAY,aAAa,GAAG;AAC5C,SAAO,KAAK,GAAG,KAAK,MAAM;;AAG5B,QAAO,KAAK,WAAW;CACvB,MAAM,aAAa,IAAI;CAEvB,MAAM,MAAM;;;;;;;;;;;YAWF,WAAW,KAAK,QAAQ,CAAC;;YAEzB,WAAW;;AAGrB,KAAI;AAYF,UAXe,MAAM,KAAK,MASvB,KAAK,OAAO,EAED,KAAK,KAAK,SAAS;GAC/B,WAAW,IAAI;GACf,MAAM,IAAI;GACV,WAAW,IAAI;GACf,SAAS,IAAI;GACb,SAAS,IAAI;GACb,OAAO,IAAI;GACX,MAAM,IAAI;GACV,QAAQ,IAAI;GACb,EAAE;UACI,GAAG;AACV,UAAQ,OAAO,MAAM,uCAAuC,EAAE,IAAI;AAClE,QAAM,cAAc,WAAW,EAAE;;;;;;AAOrC,eAAsB,eACpB,MACA,gBACA,MACyB;CACzB,MAAM,aAAa,MAAM,cAAc;CAEvC,MAAM,aAAuB,CAAC,wBAAwB;CACtD,MAAM,SAA8B,EAAE;CACtC,IAAI,WAAW;CAEf,MAAM,SAAS,MAAM,MAAM,KAAK,eAAe,CAAC,KAAK,IAAI,GAAG;AAC5D,QAAO,KAAK,OAAO;CACnB,MAAM,WAAW,IAAI;AAErB,KAAI,MAAM,cAAc,KAAK,WAAW,SAAS,GAAG;EAClD,MAAM,eAAe,KAAK,WAAW,UAAU,IAAI,aAAa,CAAC,KAAK,KAAK;AAC3E,aAAW,KAAK,kBAAkB,aAAa,GAAG;AAClD,SAAO,KAAK,GAAG,KAAK,WAAW;;AAGjC,KAAI,MAAM,WAAW,KAAK,QAAQ,SAAS,GAAG;EAC5C,MAAM,eAAe,KAAK,QAAQ,UAAU,IAAI,aAAa,CAAC,KAAK,KAAK;AACxE,aAAW,KAAK,cAAc,aAAa,GAAG;AAC9C,SAAO,KAAK,GAAG,KAAK,QAAQ;;AAG9B,KAAI,MAAM,SAAS,KAAK,MAAM,SAAS,GAAG;EACxC,MAAM,eAAe,KAAK,MAAM,UAAU,IAAI,aAAa,CAAC,KAAK,KAAK;AACtE,aAAW,KAAK,YAAY,aAAa,GAAG;AAC5C,SAAO,KAAK,GAAG,KAAK,MAAM;;AAG5B,QAAO,KAAK,WAAW;CACvB,MAAM,aAAa,IAAI;CAGvB,MAAM,MAAM;;;;;;;;;2BASa,SAAS;;YAExB,WAAW,KAAK,QAAQ,CAAC;6BACR,SAAS;YAC1B,WAAW;;AAGrB,KAAI;EACF,MAAM,SAAS,MAAM,KAAK,MASvB,KAAK,OAAO;EAEf,MAAM,WAAW,MAAM,YAAY;AAEnC,SAAO,OAAO,KACX,KAAK,SAAS;GACb,WAAW,IAAI;GACf,MAAM,IAAI;GACV,WAAW,IAAI;GACf,SAAS,IAAI;GACb,SAAS,IAAI;GACb,OAAO,IAAI;GACX,MAAM,IAAI;GACV,QAAQ,IAAI;GACb,EAAE,CACF,QAAQ,MAAM,EAAE,SAAS,SAAS;UAC9B,GAAG;AACV,UAAQ,OAAO,MAAM,wCAAwC,EAAE,IAAI;AACnE,QAAM,cAAc,YAAY,EAAE;;;;;;AC7LtC,eAAsB,gBAAgB,MAAY,MAAmC;AACnF,OAAM,KAAK,MACT;;;;;0CAMA;EAAC,KAAK;EAAW,KAAK;EAAO,KAAK;EAAQ,KAAK;EAAM,KAAK;EAAO,KAAK;EAAU,CACjF;;AAGH,eAAsB,gBAAgB,MAAY,WAAkC;CAClF,MAAM,SAAS,MAAM,KAAK,SAAS;AACnC,KAAI;AACF,QAAM,OAAO,MAAM,QAAQ;AAC3B,QAAM,OAAO,MAAM,kDAAkD,CAAC,UAAU,CAAC;AACjF,QAAM,OAAO,MAAM,kDAAkD,CAAC,UAAU,CAAC;AACjF,QAAM,OAAO,MAAM,sDAAsD,CAAC,UAAU,CAAC;AACrF,QAAM,OAAO,MAAM,0EAA0E,CAAC,UAAU,CAAC;AACzG,QAAM,OAAO,MAAM,iDAAiD,CAAC,UAAU,CAAC;AAChF,QAAM,OAAO,MAAM,SAAS;UACrB,GAAG;AACV,QAAM,OAAO,MAAM,WAAW;AAC9B,QAAM;WACE;AACR,SAAO,SAAS;;;AAMpB,SAAS,gBAAgB,KAAmC;AAC1D,QAAO;EACL,WAAW,IAAI;EACf,OAAO,OAAO,IAAI,MAAM;EACxB,QAAQ,OAAO,IAAI,OAAO;EAC1B,MAAM,IAAI;EACV,OAAO,IAAI;EACX,WAAW,OAAO,IAAI,WAAW;EAClC;;AAGH,eAAsB,aAAa,MAAY,WAAiD;CAC9F,MAAM,IAAI,MAAM,KAAK,MACnB,oGACA,CAAC,UAAU,CACZ;AACD,QAAO,EAAE,KAAK,WAAW,IAAI,OAAO,gBAAgB,EAAE,KAAK,GAAG;;AAGhE,eAAsB,oBAAoB,MAAY,OAAe,QAA8C;CACjH,MAAM,IAAI,MAAM,KAAK,MACnB,uHACA,CAAC,OAAO,OAAO,CAChB;AACD,QAAO,EAAE,KAAK,WAAW,IAAI,OAAO,gBAAgB,EAAE,KAAK,GAAG;;AAGhE,eAAsB,iBAAiB,MAAqC;AAE1E,SADU,MAAM,KAAK,MAAsB,6EAA6E,EAC/G,KAAK,IAAI,gBAAgB;;AAGpC,eAAsB,oBAAoB,MAAY,SAA0C;AAK9F,SAJU,MAAM,KAAK,MACnB,oGACA,CAAC,QAAQ,CACV,EACQ,KAAK,IAAI,gBAAgB;;AAGpC,eAAsB,gBAAgB,MAA6B;CACjE,MAAM,IAAI,MAAM,KAAK,MAAqB,8CAA8C;AACxF,QAAO,SAAS,EAAE,KAAK,IAAI,KAAK,KAAK,GAAG;;AAG1C,eAAsB,0BAA0B,MAAY,QAAiC;CAC3F,MAAM,IAAI,MAAM,KAAK,MAAqB,kEAAkE,CAAC,GAAG,OAAO,GAAG,CAAC;AAC3H,QAAO,OAAO,EAAE,KAAK,IAAI,KAAK,EAAE;;AAGlC,eAAsB,qBAAqB,MAAY,SAAkC;CACvF,MAAM,IAAI,MAAM,KAAK,MAAqB,+DAA+D,CAAC,QAAQ,CAAC;AACnH,QAAO,OAAO,EAAE,KAAK,IAAI,KAAK,EAAE;;AAGlC,eAAsB,qBAAqB,MAAY,OAA0C;AAC/F,KAAI,MAAM,WAAW,EAAG,QAAO,EAAE;CACjC,MAAM,eAAe,MAAM,KAAK,GAAG,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK,KAAK;AAKhE,SAJU,MAAM,KAAK,MACnB,mGAAmG,aAAa,IAChH,MACD,EACQ,KAAK,IAAI,gBAAgB;;AAGpC,eAAsB,0BAA0B,MAAY,OAAiB,SAA0C;AACrH,KAAI,MAAM,WAAW,EAAG,QAAO,EAAE;CACjC,MAAM,eAAe,MAAM,KAAK,GAAG,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK,KAAK;AAKhE,SAJU,MAAM,KAAK,MACnB,mGAAmG,aAAa,uBAAuB,MAAM,SAAS,EAAE,2BACxJ,CAAC,GAAG,OAAO,QAAQ,CACpB,EACQ,KAAK,IAAI,gBAAgB;;AAGpC,eAAsB,qBAAqB,MAA+B;AAExE,SADU,MAAM,KAAK,MAA8B,qCAAqC,EAC/E,KAAK,KAAI,QAAO,IAAI,WAAW;;AAG1C,eAAsB,4BAA4B,MAAY,QAAmC;AAK/F,SAJU,MAAM,KAAK,MACnB,+DACA,CAAC,GAAG,OAAO,GAAG,CACf,EACQ,KAAK,KAAI,QAAO,IAAI,WAAW;;AAG1C,eAAsB,uBAAuB,MAAY,SAAoC;AAK3F,SAJU,MAAM,KAAK,MACnB,4DACA,CAAC,QAAQ,CACV,EACQ,KAAK,KAAI,QAAO,IAAI,WAAW;;AAO1C,eAAsB,mBAAmB,MAAY,SAAyC;AAC5F,KAAI,QAAQ,WAAW,EAAG;CAC1B,MAAM,SAAS,MAAM,KAAK,SAAS;AACnC,KAAI;AACF,QAAM,OAAO,MAAM,QAAQ;AAC3B,OAAK,MAAM,KAAK,QACd,OAAM,OAAO,MACX;;;;8DAKA;GAAC,EAAE;GAAW,EAAE;GAAe,EAAE;GAAO,EAAE;GAAO,CAClD;AAEH,QAAM,OAAO,MAAM,SAAS;UACrB,GAAG;AACV,QAAM,OAAO,MAAM,WAAW;AAC9B,QAAM;WACE;AACR,SAAO,SAAS;;;AAIpB,eAAsB,mBAAmB,MAAY,eAAsC;AACzF,OAAM,KAAK,MAAM,uDAAuD,CAAC,cAAc,CAAC;;AAG1F,eAAsB,cAAc,MAAY,WAA8D;CAC5G,MAAM,IAAI,MAAM,KAAK,MACnB,kEACA,CAAC,UAAU,CACZ;AACD,QAAO,EAAE,KAAK,SAAS,IAAI,EAAE,eAAe,EAAE,KAAK,GAAG,gBAAgB,GAAG;;AAS3E,SAAS,gBAAgB,KAAmC;AAC1D,QAAO;EACL,YAAY,IAAI;EAChB,WAAW,IAAI;EACf,YAAY,IAAI;EAChB,UAAU,IAAI;EACd,YAAY,IAAI;EAChB,YAAa,IAAI,cAA6C;EAC/D;;AAGH,eAAsB,uBAAuB,MAAY,aAAuB,OAAsC;CACpH,MAAM,SAAS,MAAM,KAAK,SAAS;AACnC,KAAI;AACF,QAAM,OAAO,MAAM,QAAQ;AAC3B,MAAI,YAAY,SAAS,EACvB,OAAM,OAAO,MACX,+DACA,CAAC,YAAY,CACd;EASH,MAAM,0BAAU,IAAI,KAA2B;AAC/C,OAAK,MAAM,KAAK,MACd,SAAQ,IAAI,GAAG,EAAE,WAAW,QAAQ,EAAE,UAAU,QAAQ,EAAE,cAAc,EAAE;EAE5E,MAAM,cAAc,CAAC,GAAG,QAAQ,QAAQ,CAAC;AAEzC,OAAK,IAAI,IAAI,GAAG,IAAI,YAAY,QAAQ,KAAK,KAAK;GAChD,MAAM,QAAQ,YAAY,MAAM,GAAG,IAAI,IAAI;GAC3C,MAAM,SAAmB,EAAE;GAC3B,MAAM,SAAqC,EAAE;GAC7C,IAAI,MAAM;AACV,QAAK,MAAM,KAAK,OAAO;AACrB,WAAO,KAAK,KAAK,MAAM,KAAK,MAAM,KAAK,MAAM,KAAK,MAAM,KAAK,MAAM,KAAK,MAAM,GAAG;AACjF,WAAO,KAAK,EAAE,YAAY,EAAE,WAAW,EAAE,YAAY,EAAE,UAAU,EAAE,YAAY,EAAE,cAAc,YAAY;;AAE7G,SAAM,OAAO,MACX;kBACU,OAAO,KAAK,KAAK,CAAC;;;8CAI5B,OACD;;AAEH,QAAM,OAAO,MAAM,SAAS;UACrB,GAAG;AACV,QAAM,OAAO,MAAM,WAAW;AAC9B,QAAM;WACE;AACR,SAAO,SAAS;;;AAIpB,eAAsB,mBAAmB,MAAY,YAA6C;AAKhG,SAJU,MAAM,KAAK,MACnB,2HACA,CAAC,WAAW,CACb,EACQ,KAAK,IAAI,gBAAgB;;AAGpC,eAAsB,iBAAiB,MAAY,YAA6C;AAK9F,SAJU,MAAM,KAAK,MACnB,2HACA,CAAC,WAAW,CACb,EACQ,KAAK,IAAI,gBAAgB;;AAGpC,eAAsB,kBAAkB,MAA0E;AAIhH,SAHU,MAAM,KAAK,MACnB,iFACD,EACQ;;AAGX,eAAsB,aAAa,MAAuE;AAIxG,SAHU,MAAM,KAAK,MACnB,4EACD,EACQ,KAAK,KAAI,SAAQ;EAAE,YAAY,IAAI;EAAa,WAAW,IAAI;EAAY,EAAE;;AAGxF,eAAsB,4BAA4B,MAA2F;AAI3I,SAHU,MAAM,KAAK,MACnB,yFACD,EACQ,KAAK,KAAI,SAAQ;EAAE,YAAY,IAAI;EAAa,WAAW,IAAI;EAAY,YAAY,IAAI;EAAa,EAAE;;AAGrH,eAAsB,uBAAuB,MAAY,QAA+F;AAKtJ,SAJU,MAAM,KAAK,MACnB,kHACA,CAAC,GAAG,OAAO,GAAG,CACf,EACQ,KAAK,KAAI,SAAQ;EAAE,YAAY,IAAI;EAAa,WAAW,IAAI;EAAY,YAAY,IAAI;EAAa,EAAE;;AAGrH,eAAsB,kBAAkB,MAAY,SAAgG;AAKlJ,SAJU,MAAM,KAAK,MACnB,wKACA,CAAC,QAAQ,CACV,EACQ,KAAK,KAAI,SAAQ;EAAE,YAAY,IAAI;EAAa,WAAW,IAAI;EAAY,YAAY,IAAI;EAAa,EAAE;;AAGrH,eAAsB,0BAA0B,MAAY,QAAiC;CAC3F,MAAM,IAAI,MAAM,KAAK,MAAqB,mEAAmE,CAAC,GAAG,OAAO,GAAG,CAAC;AAC5H,QAAO,OAAO,EAAE,KAAK,IAAI,KAAK,EAAE;;AAGlC,eAAsB,qBAAqB,MAAY,SAAkC;CACvF,MAAM,IAAI,MAAM,KAAK,MACnB,yHACA,CAAC,QAAQ,CACV;AACD,QAAO,OAAO,EAAE,KAAK,IAAI,KAAK,EAAE;;AAGlC,eAAsB,uBAAuB,MAAY,aAAgD;AACvG,KAAI,YAAY,WAAW,EAAG,QAAO,EAAE;CACvC,MAAM,eAAe,YAAY,KAAK,GAAG,MAAM,IAAI,IAAI,IAAI,CAAC,KAAK,KAAK;AAKtE,SAJU,MAAM,KAAK,MACnB,0HAA0H,aAAa,gCACvI,YACD,EACQ,KAAK,IAAI,gBAAgB;;AAGpC,eAAsB,kBAAkB,MAAgE;AAItG,SAHU,MAAM,KAAK,MACnB,8GACD,EACQ;;AAGX,eAAsB,4BAA4B,MAAY,QAAoE;AAKhI,SAJU,MAAM,KAAK,MACnB,uIACA,CAAC,GAAG,OAAO,GAAG,CACf,EACQ;;AAGX,eAAsB,uBAAuB,MAAY,SAAqE;AAK5H,SAJU,MAAM,KAAK,MACnB,6LACA,CAAC,QAAQ,CACV,EACQ;;AASX,SAAS,kBAAkB,KAAuC;AAChE,QAAO;EACL,WAAW,IAAI;EACf,cAAc,IAAI;EAClB,eAAe,IAAI;EACnB,eAAe,IAAI;EACnB,UAAU,IAAI,cAAc;EAC5B,YAAY,OAAO,IAAI,YAAY;EACpC;;AAGH,eAAsB,kBAAkB,MAAY,MAAuC;AACzF,KAAI,KAAK,WAAW,EAAG;CACvB,MAAM,SAAS,MAAM,KAAK,SAAS;AACnC,KAAI;AACF,QAAM,OAAO,MAAM,QAAQ;AAC3B,OAAK,MAAM,KAAK,KACd,OAAM,OAAO,MACX;;;;;;;gDAQA;GAAC,EAAE;GAAW,EAAE;GAAc,EAAE;GAAe,EAAE;GAAe,EAAE,WAAW,IAAI;GAAG,EAAE;GAAW,CAClG;AAEH,QAAM,OAAO,MAAM,SAAS;UACrB,GAAG;AACV,QAAM,OAAO,MAAM,WAAW;AAC9B,QAAM;WACE;AACR,SAAO,SAAS;;;AAIpB,eAAsB,eAAe,MAAY,WAAmD;CAClG,MAAM,IAAI,MAAM,KAAK,MACnB,qIACA,CAAC,UAAU,CACZ;AACD,QAAO,EAAE,KAAK,WAAW,IAAI,OAAO,kBAAkB,EAAE,KAAK,GAAG;;AAGlE,eAAsB,WAAW,MAAuC;AAItE,SAHU,MAAM,KAAK,MACnB,kIACD,EACQ,KAAK,KAAI,SAAQ;EAAE,GAAG,kBAAkB,IAAI;EAAE,UAAU;EAAM,EAAE;;AAG3E,eAAsB,qBAAqB,MAAY,QAAmC;AAKxF,SAJU,MAAM,KAAK,MACnB,kFACA,CAAC,GAAG,OAAO,GAAG,CACf,EACQ,KAAK,KAAI,QAAO,IAAI,WAAW;;AAG1C,eAAsB,gBAAgB,MAAY,SAAoC;AAKpF,SAJU,MAAM,KAAK,MACnB,gJACA,CAAC,QAAQ,CACV,EACQ,KAAK,KAAI,QAAO,IAAI,WAAW;;AAG1C,eAAsB,mBAAmB,MAA+B;AAItE,SAHU,MAAM,KAAK,MACnB,gFACD,EACQ,KAAK,KAAI,QAAO,IAAI,WAAW;;AAG1C,eAAsB,6BAA6B,MAAY,QAAmC;AAKhG,SAJU,MAAM,KAAK,MACnB,wGACA,CAAC,GAAG,OAAO,GAAG,CACf,EACQ,KAAK,KAAI,QAAO,IAAI,WAAW;;AAG1C,eAAsB,wBAAwB,MAAY,SAAoC;AAK5F,SAJU,MAAM,KAAK,MACnB,yKACA,CAAC,QAAQ,CACV,EACQ,KAAK,KAAI,QAAO,IAAI,WAAW;;AAO1C,eAAsB,gBAAgB,MAAY,SAA0C;AAC1F,KAAI,QAAQ,WAAW,EAAG;CAC1B,MAAM,SAAS,MAAM,KAAK,SAAS;AACnC,KAAI;AACF,QAAM,OAAO,MAAM,QAAQ;AAC3B,OAAK,MAAM,KAAK,QACd,OAAM,OAAO,MACX;qEAEA,CAAC,EAAE,MAAM,EAAE,UAAU,CACtB;AAEH,QAAM,OAAO,MAAM,SAAS;UACrB,IAAI;AACX,QAAM,OAAO,MAAM,WAAW;AAC9B,QAAM;WACE;AACR,SAAO,SAAS;;;AAIpB,eAAsB,iBAAiB,MAAY,SAA0C;CAC3F,MAAM,SAAS,MAAM,KAAK,SAAS;AACnC,KAAI;AACF,QAAM,OAAO,MAAM,QAAQ;AAC3B,QAAM,OAAO,MAAM,+BAA+B;AAClD,OAAK,IAAI,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK,KAAK;GAC5C,MAAM,QAAQ,QAAQ,MAAM,GAAG,IAAI,IAAI;GACvC,MAAM,SAAmB,EAAE;GAC3B,MAAM,SAAmB,EAAE;GAC3B,IAAI,MAAM;AACV,QAAK,MAAM,KAAK,OAAO;AACrB,WAAO,KAAK,KAAK,MAAM,KAAK,MAAM,GAAG;AACrC,WAAO,KAAK,EAAE,MAAM,EAAE,UAAU;;AAElC,SAAM,OAAO,MACX,0DAA0D,OAAO,KAAK,KAAK,IAC3E,OACD;;AAEH,QAAM,OAAO,MAAM,SAAS;UACrB,GAAG;AACV,QAAM,OAAO,MAAM,WAAW;AAC9B,QAAM;WACE;AACR,SAAO,SAAS;;;AAIpB,eAAsB,iBAAiB,MAAY,MAAiC;AAKlF,SAJU,MAAM,KAAK,MACnB,2DACA,CAAC,KAAK,CACP,EACQ,KAAK,KAAI,QAAO,IAAI,WAAW;;AAG1C,eAAsB,qBAAqB,MAAY,OAAe,QAAQ,KAAwB;AAKpG,SAJU,MAAM,KAAK,MACnB,8FACA,CAAC,IAAI,MAAM,IAAI,MAAM,CACtB,EACQ,KAAK,KAAI,QAAO,IAAI,WAAW;;;;;;;;;;;;;;;;;;;;;;;;AC/c1C,SAAgB,gBAAgB,MAAc,WAAW,WAAmB;AAC1E,QAAO,WAAW,SAAS,CACxB,OAAO,UAAU,SAAS,GAAG,OAAO,CACpC,OAAO,MAAM,CACb,MAAM,GAAG,GAAG;;;;;;;;;;;;;;;;;;;ACXjB,eAAsB,qBACpB,SACA,WACA,UACA,cACA,QACA,MACkB;CAClB,MAAM,UAAU,KAAK,UAAU,aAAa;CAE5C,IAAI;CACJ,IAAI;AACJ,KAAI;AACF,YAAU,aAAa,SAAS,OAAO;AACvC,SAAO,SAAS,QAAQ;SAClB;AACN,SAAO;;CAGT,MAAM,OAAO,gBAAgB,QAAQ;CACrC,MAAM,QAAQ,KAAK,MAAM,KAAK,QAAQ;CACtC,MAAM,OAAO,KAAK;AAIlB,KADqB,MAAM,QAAQ,YAAY,WAAW,aAAa,KAClD,KAAM,QAAO;AAGlC,OAAM,QAAQ,oBAAoB,WAAW,aAAa;CAG1D,MAAM,YAAY,cAAc,QAAQ;CACxC,MAAM,YAAY,KAAK,KAAK;CAE5B,MAAM,SAAqB,UAAU,KAAK,GAAG,OAAO;EAClD,IAAI,QAAQ,WAAW,cAAc,GAAG,EAAE,WAAW,EAAE,QAAQ;EAC/D;EACA;EACA;EACA,MAAM;EACN,WAAW,EAAE;EACb,SAAS,EAAE;EACX,MAAM,EAAE;EACR,MAAM,EAAE;EACR;EACA,WAAW;EACZ,EAAE;AAGH,OAAM,QAAQ,aAAa,OAAO;AAClC,OAAM,QAAQ,WAAW;EAAE;EAAW,MAAM;EAAc;EAAQ;EAAM;EAAM;EAAO;EAAM,CAAC;AAE5F,QAAO;;AAOT,eAAsB,wBACpB,SACA,WACA,UACA,gBACsB;CACtB,MAAM,SAAsB;EAAE,gBAAgB;EAAG,eAAe;EAAG,cAAc;EAAG;CAEpF,MAAM,eAA2F,EAAE;CAEnG,MAAM,eAAe,KAAK,UAAU,YAAY;AAChD,KAAI,WAAW,aAAa,CAC1B,cAAa,KAAK;EAAE,SAAS;EAAc,UAAU;EAAU,QAAQ;EAAU,MAAM;EAAa,CAAC;CAGvG,MAAM,YAAY,KAAK,UAAU,SAAS;AAC1C,MAAK,MAAM,WAAW,YAAY,UAAU,EAAE;EAE5C,MAAM,OAAO,WADG,SAAS,UAAU,QAAQ,CACX;AAChC,eAAa,KAAK;GAAE;GAAS,UAAU;GAAU,QAAQ;GAAU;GAAM,CAAC;;CAG5E,MAAM,WAAW,KAAK,UAAU,QAAQ;AACxC,MAAK,MAAM,WAAW,YAAY,SAAS,CACzC,cAAa,KAAK;EAAE;EAAS,UAAU;EAAU,QAAQ;EAAS,MAAM;EAAW,CAAC;CAItF;EACE,MAAM,YAAY,KAAK,KAAK;AAC5B,OAAK,MAAM,WAAW,YAAY,SAAS,EAAE;GAE3C,MAAM,OAAO,uBADI,SAAS,QAAQ,CACW;AAC7C,OAAI,CAAC,KAAM;GAEX,MAAM,gBAAgB,GADN,SAAS,UAAU,QAAQ,CACV;GAGjC,MAAM,aAAuB;IAC3B,IAHS,QAAQ,WAAW,eAAe,GAAG,GAAG,EAAE;IAG/C;IAAW,QAAQ;IAAS,MAAM;IACtC,MAAM;IAAe,WAAW;IAAG,SAAS;IAC5C,MAJW,WAAW,KAAK;IAIrB;IAAM;IAAW,WAAW;IACnC;AACD,OAAI;AACF,UAAM,QAAQ,aAAa,CAAC,WAAW,CAAC;WAClC;;;AAMZ,KAAI,CAAC,6BAA6B,SAAS,CACzC,MAAK,MAAM,WAAW,iBAAiB,SAAS,CAC9C,cAAa,KAAK;EAAE;EAAS,UAAU;EAAU,QAAQ;EAAW,MAAM;EAAS,CAAC;AAIxF,KAAI,kBAAkB,mBAAmB,UAAU;AACjD,OAAK,MAAM,WAAW,YAAY,eAAe,CAC/C,cAAa,KAAK;GAAE;GAAS,UAAU;GAAgB,QAAQ;GAAS,MAAM;GAAW,CAAC;EAI5F;GACE,MAAM,YAAY,KAAK,KAAK;AAC5B,QAAK,MAAM,WAAW,YAAY,eAAe,EAAE;IAEjD,MAAM,OAAO,uBADI,SAAS,QAAQ,CACW;AAC7C,QAAI,CAAC,KAAM;IAEX,MAAM,gBAAgB,GADN,SAAS,gBAAgB,QAAQ,CAChB;IAGjC,MAAM,aAAuB;KAC3B,IAHS,QAAQ,WAAW,eAAe,GAAG,GAAG,EAAE;KAG/C;KAAW,QAAQ;KAAS,MAAM;KACtC,MAAM;KAAe,WAAW;KAAG,SAAS;KAC5C,MAJW,WAAW,KAAK;KAIrB;KAAM;KAAW,WAAW;KACnC;AACD,QAAI;AACF,WAAM,QAAQ,aAAa,CAAC,WAAW,CAAC;YAClC;;;AAMZ,MAAI,eAAe,SAAS,SAAS,EAAE;GACrC,MAAM,mBAAmB,eAAe,MAAM,GAAG,GAAiB;GAClE,MAAM,iBAAiB,KAAK,kBAAkB,YAAY;AAC1D,OAAI,WAAW,eAAe,CAC5B,cAAa,KAAK;IAAE,SAAS;IAAgB,UAAU;IAAkB,QAAQ;IAAU,MAAM;IAAa,CAAC;GAEjH,MAAM,kBAAkB,KAAK,kBAAkB,SAAS;AACxD,QAAK,MAAM,WAAW,YAAY,gBAAgB,EAAE;IAElD,MAAM,OAAO,WADG,SAAS,kBAAkB,QAAQ,CACnB;AAChC,iBAAa,KAAK;KAAE;KAAS,UAAU;KAAkB,QAAQ;KAAU;KAAM,CAAC;;;;AAKxF,OAAM,kBAAkB;CAExB,IAAI,kBAAkB;AAEtB,MAAK,MAAM,EAAE,SAAS,UAAU,QAAQ,UAAU,cAAc;AAC9D,MAAI,mBAAmB,mBAAmB;AACxC,SAAM,kBAAkB;AACxB,qBAAkB;;AAEpB;EAEA,MAAM,UAAU,SAAS,UAAU,QAAQ;AAC3C,MAAI;AAGF,OAFgB,MAAM,qBAAqB,SAAS,WAAW,UAAU,SAAS,QAAQ,KAAK,EAElF;IACX,MAAM,MAAM,MAAM,QAAQ,YAAY,WAAW,QAAQ;AACzD,WAAO;AACP,WAAO,iBAAiB,IAAI;SAE5B,QAAO;UAEH;AAEN,UAAO;;;CAKX,MAAM,4BAAY,IAAI,KAAa;AACnC,MAAK,MAAM,EAAE,SAAS,cAAc,aAClC,WAAU,IAAI,SAAS,UAAU,QAAQ,CAAC;CAG5C,MAAM,eAAe,MAAM,QAAQ,sBAAsB,UAAU;CAEnE,MAAM,aAAuB,EAAE;AAC/B,MAAK,MAAM,KAAK,cAAc;EAC5B,MAAM,WAAW,EAAE,SAAS,UAAU,GAAG,EAAE,MAAM,GAAG,GAAkB,GAAG;AACzE,MAAI,CAAC,UAAU,IAAI,SAAS,CAC1B,YAAW,KAAK,EAAE;;AAItB,KAAI,WAAW,SAAS,EACtB,OAAM,QAAQ,YAAY,WAAW,WAAW;AAGlD,QAAO;;AAOT,MAAM,mBAAmB;;AAIzB,MAAM,8BAA8B;;AAEpC,MAAM,8BAA8B;;;;;;;;;;;;;;;AA8BpC,eAAsB,uBACpB,SACA,YACA,cACA,SACiB;CAEjB,MAAM,YAAY,SAAS,aAAa;CACxC,MAAM,YAAY,SAAS,aAAa;CACxC,MAAM,WAAW,KAAK,KAAK,GAAG;CAE9B,MAAM,OAAO,MAAM,QAAQ,sBAAsB,QAAW,UAAU;AACtE,KAAI,KAAK,WAAW,EAAG,QAAO;CAE9B,MAAM,QAAQ,KAAK;CACnB,IAAI,WAAW;CAGf,MAAM,qCAAqB,IAAI,KAAoD;AACnF,MAAK,MAAM,OAAO,MAAM;EACtB,MAAM,QAAQ,mBAAmB,IAAI,IAAI,WAAW;AACpD,MAAI,MACF,OAAM;MAEN,oBAAmB,IAAI,IAAI,YAAY;GAAE,OAAO;GAAG,YAAY,IAAI;GAAM,CAAC;;CAG9E,MAAM,SAAS,QAAgB,cAAc,IAAI,IAAI,IAAI,WAAW;CACpE,MAAM,iBAAiB,MAAM,KAAK,mBAAmB,SAAS,CAAC,CAC5D,KAAK,CAAC,KAAK,EAAE,OAAO,kBAAkB,KAAK,MAAM,IAAI,CAAC,IAAI,MAAM,gBAAgB,WAAW,GAAG,CAC9F,KAAK,KAAK;AACb,SAAQ,OAAO,MACb,4BAA4B,MAAM,4BAA4B,mBAAmB,KAAK,eAAe,eAAe,IACrH;CAGD,IAAI,mBAAmB;CACvB,IAAI,kBAAkB;AAEtB,MAAK,IAAI,IAAI,GAAG,IAAI,KAAK,QAAQ,KAAK,kBAAkB;AAEtD,MAAI,cAAc,EAAE;AAClB,WAAQ,OAAO,MACb,2CAA2C,SAAS,GAAG,MAAM,gCAC9D;AACD;;AAKF,MAAI,KAAK,KAAK,IAAI,UAAU;AAC1B,WAAQ,OAAO,MACb,0CAA0C,SAAS,GAAG,MAAM,WAAW,UAAU,wCAClF;AACD;;EAGF,MAAM,QAAQ,KAAK,MAAM,GAAG,IAAI,iBAAiB;AAIjD,QAAM,kBAAkB;EAExB,MAAM,OAAO,MAAM,mBAAmB,MAAM,KAAK,MAAM,EAAE,KAAK,CAAC;AAO/D,QAAM,QAAQ,IACZ,MAAM,KAAK,KAAK,MAAM,QAAQ,gBAAgB,IAAI,IAAI,mBAAmB,KAAK,GAAG,CAAC,CAAC,CACpF;AAMD,OAAK,MAAM,EAAE,YAAY,UAAU,OAAO;AACxC,OAAI,eAAe,kBAAkB;AACnC,QAAI,qBAAqB,GACvB,SAAQ,OAAO,MACb,yBAAyB,MAAM,iBAAiB,CAAC,IAAI,gBAAgB,oBACtE;IAEH,MAAM,OAAO,mBAAmB,IAAI,WAAW;AAC/C,YAAQ,OAAO,MACb,0BAA0B,MAAM,WAAW,CAAC,IAAI,MAAM,SAAS,IAAI,uBAAuB,KAAK,KAChG;AACD,uBAAmB;AACnB,sBAAkB;;AAEpB;;AAGF,cAAY,MAAM;EAGlB,MAAM,YAAY,MAAM,MAAM,SAAS;AACvC,UAAQ,OAAO,MACb,yBAAyB,SAAS,GAAG,MAAM,WAAW,MAAM,UAAU,WAAW,CAAC,IAAI,UAAU,KAAK,KACtG;;AAIH,KAAI,qBAAqB,GACvB,SAAQ,OAAO,MACb,yBAAyB,MAAM,iBAAiB,CAAC,IAAI,gBAAgB,oBACtE;AAGH,QAAO;;AAOT,eAAsB,oBACpB,SACA,UACoD;CACpD,MAAM,WAAW,MAAM,SAAS,aAAa,EAAE,QAAQ,UAAU,CAAC;CAElE,MAAM,SAAsB;EAAE,gBAAgB;EAAG,eAAe;EAAG,cAAc;EAAG;AAEpF,MAAK,MAAM,WAAW,UAAU;AAC9B,QAAM,kBAAkB;EACxB,MAAM,IAAI,MAAM,wBAAwB,SAAS,QAAQ,IAAI,QAAQ,WAAW,QAAQ,iBAAiB;AACzG,SAAO,kBAAkB,EAAE;AAC3B,SAAO,iBAAiB,EAAE;AAC1B,SAAO,gBAAgB,EAAE;;AAG3B,QAAO;EAAE,UAAU,SAAS;EAAQ,QAAQ;EAAQ;;;;;;;;;;;;;;ACxZtD,eAAe,sBACb,MACA,SACA,MAC4B;CAC5B,MAAM,aAAa,OAAO,KAAK,QAAQ,CAAC,IAAI,OAAO;AACnD,KAAI,WAAW,SAAS,EACtB,QAAO;EAAE,SAAS,EAAE;EAAE,mBAAmB,WAAW;EAAQ,0BAA0B;EAAG;CAgB3F,IAAI,kBAZe,MAAM,KAAK,MAC5B;;;;;;;iBAQA,CAAC,YAAY,KAAK,gBAAgB,CACnC,EAE+B,KAC7B,KAAK,MAAM,EAAE,KAAK,CAClB,QAAQ,MAAM,CAAC,WAAW,IAAI,EAAE,CAAC;AAEpC,KAAI,eAAe,WAAW,EAC5B,QAAO;EAAE,SAAS,EAAE;EAAE,mBAAmB,WAAW;EAAQ,0BAA0B;EAAG;AAG3F,kBAAiB,eAAe,MAAM,GAAG,IAAI;CAG7C,MAAM,eAAe,eAClB,KAAK,GAAG,MAAM,KAAK,IAAI,EAAE,SAAS,CAClC,KAAK,KAAK;CAEb,MAAM,cAAc,MAAM,KAAK,MAO7B;;;oBAGgB,aAAa;;;;wCAK7B,CAAC,YAAY,GAAG,eAAe,CAChC;CAED,MAAM,+BAAe,IAAI,KAGtB;AAEH,MAAK,MAAM,OAAO,YAAY,MAAM;EAClC,MAAM,WAAW,aAAa,IAAI,IAAI,QAAQ,IAAI;GAChD,0BAAU,IAAI,KAAa;GAC3B,aAAa;GACb,WAAW;GACX,UAAU;GACX;AACD,WAAS,SAAS,IAAI,SAAS,IAAI,YAAY,GAAG,CAAC;AACnD,WAAS,eAAe,SAAS,IAAI,KAAK,GAAG;EAC7C,MAAM,KAAK,SAAS,IAAI,YAAY,GAAG;EACvC,MAAM,KAAK,SAAS,IAAI,WAAW,GAAG;AACtC,MAAI,KAAK,SAAS,UAAW,UAAS,YAAY;AAClD,MAAI,KAAK,SAAS,SAAU,UAAS,WAAW;AAChD,eAAa,IAAI,IAAI,SAAS,SAAS;;CAGzC,MAAM,UAAoB,EAAE;AAC5B,MAAK,MAAM,CAAC,SAAS,SAAS,cAAc;AAC1C,MAAI,KAAK,SAAS,OAAO,KAAK,aAAc;AAC5C,MAAI,KAAK,cAAc,KAAK,gBAAiB;EAE7C,MAAM,WAAW,CAAC,GAAG,KAAK,SAAS,CAChC,KAAK,OAAO,QAAQ,OAAO,OAAO,GAAG,CAAC,CACtC,OAAO,QAAQ;AAElB,UAAQ,KAAK;GACX;GACA;GACA,aAAa,KAAK;GAClB,YAAY,KAAK,cAAc,WAAW,IAAI,KAAK;GACnD,WAAW,KAAK,aAAa,YAAY,IAAI,KAAK;GACnD,CAAC;;AAGJ,SAAQ,MAAM,GAAG,MAAM;EACrB,MAAM,aAAa,EAAE,SAAS,SAAS,EAAE,SAAS;AAClD,MAAI,eAAe,EAAG,QAAO;AAC7B,SAAO,EAAE,cAAc,EAAE;GACzB;AAEF,QAAO;EACL,SAAS,QAAQ,MAAM,GAAG,KAAK,MAAM;EACrC,mBAAmB,WAAW;EAC9B,0BAA0B,eAAe;EAC1C;;;AAIH,eAAsB,oBACpB,MACA,UACA,SAC4B;CAC5B,MAAM,OAAqC;EACzC,cAAc,SAAS,gBAAgB;EACvC,iBAAiB,SAAS,mBAAmB;EAC7C,OAAO,SAAS,SAAS;EAC1B;CAED,MAAM,cAAc,MAAM,SAAS,aAAa,EAAE,iBAAiB,MAAM,CAAC;CAC1E,MAAM,UAA0B,EAAE;AAClC,MAAK,MAAM,EAAE,IAAI,UAAU,YACzB,SAAQ,MAAM;AAGhB,QAAO,sBAAsB,MAAM,SAAS,KAAK;;;;;ACxInD,MAAM,OAAO,MAAuB,OAAO,KAAK,EAAE;AAElD,eAAsB,+BAA+B,MAA0C;AAsB7F,QAAO;EAAE,cArBO,MAAM,KAAK,MACzB;qEAED,EAC4B,KAC1B,KAAK,OAAO;GAAE,QAAQ,EAAE;GAAQ,MAAM,EAAE;GAAM,QAAQ,IAAI,EAAE,OAAO;GAAE,UAAU,IAAI,EAAE,SAAS;GAAE,EAAE;EAgB/E,QAdL,MAAM,KAAK,MAC1B,gEACD,EACuB,KACrB,KAAK,OAAO;GAAE,MAAM,EAAE;GAAM,QAAQ,IAAI,EAAE,OAAO;GAAE,EAAE;EAU3B,QARZ,MAAM,KAAK,MAC1B;;gEAGD,EACuB,KACrB,KAAK,OAAO;GAAE,KAAK,EAAE;GAAK,QAAQ,IAAI,EAAE,OAAO;GAAE,UAAU,IAAI,EAAE,SAAS;GAAE,EAAE;EAE7C;;;;;ACvBtC,SAAS,YAAY,KAAwC;AAC3D,QAAO;EACL,IAAI,IAAI;EACR,SAAS,IAAI;EACb,WAAW,IAAI;EACf,QAAQ,IAAI;EACZ,YAAY,IAAI;EAChB,gBAAgB,IAAI;EACpB,YAAY,IAAI,KAAK,IAAI,WAAqB;EAC9C,UAAU,IAAI,WAAW,IAAI,KAAK,IAAI,SAAmB,GAAG;EAC5D,YAAY,IAAI;EAChB,YAAY,IAAI,KAAK,IAAI,WAAqB;EAC/C;;AAGH,eAAsB,YAAY,MAAY,QAAwC;CACpF,MAAM,aAAa,OAAO,cAAc;AAexC,QAAO,aAdQ,MAAM,KAAK,MACxB;;;mBAIA;EACE,OAAO;EACP,OAAO;EACP,OAAO;EACP,OAAO,cAAc;EACrB,OAAO,kBAAkB;EACzB;EACD,CACF,EACyB,KAAK,GAAG;;;;;;;AAQpC,eAAsB,eAAe,MAAY,QAA4C;CAC3F,MAAM,aAAuB,EAAE;CAC/B,MAAM,SAAoB,EAAE;CAC5B,IAAI,MAAM;AAEV,KAAI,OAAO,YAAY,QAAW;AAChC,aAAW,KAAK,cAAc,QAAQ;AACtC,SAAO,KAAK,OAAO,QAAQ;;AAE7B,KAAI,OAAO,cAAc,QAAW;AAClC,aAAW,KAAK,gBAAgB,QAAQ;AACxC,SAAO,KAAK,OAAO,UAAU;;AAE/B,KAAI,OAAO,WAAW,QAAW;AAC/B,aAAW,KAAK,aAAa,QAAQ;AACrC,SAAO,KAAK,OAAO,OAAO;;AAE5B,KAAI,OAAO,eAAe,QAAW;AACnC,aAAW,KAAK,iBAAiB,QAAQ;AACzC,SAAO,KAAK,OAAO,WAAW;;AAGhC,KAAI,OAAO,UAAU,QAAW;AAE9B,aAAW,KAAK,kBAAkB,QAAQ;AAC1C,SAAO,KAAK,OAAO,MAAM;AACzB,aAAW,KAAK,oCAAoC,MAAM,GAAG;AAC7D,SAAO,KAAK,OAAO,MAAM;YAChB,CAAC,OAAO,oBAEjB,YAAW,KAAK,mBAAmB;CAGrC,MAAM,QAAQ,WAAW,SAAS,IAAI,SAAS,WAAW,KAAK,QAAQ,KAAK;AAK5E,SAJe,MAAM,KAAK,MACxB,4BAA4B,MAAM,4BAClC,OACD,EACa,KAAK,IAAI,YAAY;;;AAIrC,eAAsB,mBAAmB,MAAY,UAAiC;AACpF,OAAM,KAAK,MACT,6EACA,CAAC,SAAS,CACX;;;;;;AAOH,eAAsB,oBAAoB,MAAY,SAA6C;AAUjG,SATe,MAAM,KAAK,MACxB;;;;;2BAMA,CAAC,QAAQ,CACV,EACa,KAAK,KAAK,SAAS;EAC/B,SAAS,IAAI;EACb,WAAW,IAAI;EACf,SAAS,IAAI;EACd,EAAE;;AAGL,eAAsB,WAAW,MAA8B;CAC7D,MAAM,SAAS,MAAM,KAAK,MAOxB;;;;;;sBAOD;CAED,MAAM,iBAAiB,MAAM,KAAK,MAChC;;;;;;UAOD;CAED,MAAM,MAAM,OAAO,KAAK;AACxB,QAAO;EACL,OAAO,SAAS,KAAK,SAAS,KAAK,GAAG;EACtC,OAAO,SAAS,KAAK,SAAS,KAAK,GAAG;EACtC,aAAa,SAAS,KAAK,eAAe,KAAK,GAAG;EAClD,UAAU,SAAS,KAAK,YAAY,KAAK,GAAG;EAC5C,YAAY,SAAS,KAAK,cAAc,KAAK,GAAG;EAChD,gBAAgB,SAAS,eAAe,KAAK,IAAI,SAAS,KAAK,GAAG;EACnE;;;;;;;;;AC9HH,MAAM,aAAa;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AA0CnB,IAAI,iBAAiB;;;;;;AAOrB,eAAe,wBAAwB,MAA2B;AAChE,KAAI,eAAgB;AACpB,OAAM,KAAK,MAAM,WAAW;AAC5B,kBAAiB;;AAGnB,MAAM,6BAA6B;;;;;;;;;;;;;;;;;;;;;;AAuBnC,IAAI,yBAAyB;AAE7B,eAAe,0BAA0B,MAA2B;AAClE,KAAI,uBAAwB;AAC5B,OAAM,KAAK,MAAM,2BAA2B;AAC5C,0BAAyB;;;AAQ3B,SAAS,mBAAmB,WAAmB,UAAkB,OAAuB;AACtF,QAAO,OAAO,YAAY,OAAS,WAAW,OAAS,MAAM,CAAC,MAAM,GAAG,GAAG;;;;;;AAW5E,eAAsB,iBACpB,MACA,KACwB;AACxB,OAAM,wBAAwB,KAAK;CAEnC,MAAM,OAAO,mBAAmB,IAAI,YAAY,IAAI,WAAW,IAAI,MAAM;CAEzE,MAAM,WAAW,MAAM,KAAK,MAC1B;;;;eAKA,CAAC,MAAM,IAAI,WAAW,CACvB;AAED,KAAI,SAAS,YAAY,SAAS,WAAW,EAC3C,QAAO;AAyBT,SAtBe,MAAM,KAAK,MACxB;;;;oBAKA;EACE,IAAI;EACJ,IAAI,cAAc;EAClB,IAAI,gBAAgB;EACpB,IAAI;EACJ,IAAI;EACJ,IAAI,aAAa;EACjB,IAAI;EACJ,IAAI,sBAAsB;EAC1B,KAAK,UAAU,IAAI,WAAW;EAC9B,KAAK,UAAU,IAAI,eAAe;EAClC,KAAK,UAAU,IAAI,SAAS;EAC5B;EACD,CACF,EAEa,KAAK,IAAI,MAAM;;;;;;;AAQ/B,eAAsB,4BACpB,MACA,UACA,KACwB;CACxB,IAAI,aAA4B;CAChC,IAAI,eAA8B;AAClC,KAAI,IAAI,KAAK;EACX,MAAM,MAAM,MAAM,SAAS,uBAAuB,IAAI,IAAI;AAC1D,MAAI,KAAK;AACP,gBAAa,IAAI;AACjB,kBAAe,IAAI;;;AAGvB,QAAO,iBAAiB,MAAM;EAAE,GAAG;EAAK;EAAY;EAAc,CAAC;;AAOrE,eAAsB,kBACpB,MACA,OAAiC,EAAE,EACR;AAC3B,OAAM,wBAAwB,KAAK;CAEnC,MAAM,aAAuB,EAAE;CAC/B,MAAM,SAAoB,EAAE;CAC5B,IAAI,MAAM;AAEV,KAAI,KAAK,cAAc,QAAW;AAChC,aAAW,KAAK,iBAAiB,QAAQ;AACzC,SAAO,KAAK,KAAK,UAAU;;AAE7B,KAAI,KAAK,cAAc,QAAW;AAChC,aAAW,KAAK,iBAAiB,QAAQ;AACzC,SAAO,KAAK,KAAK,UAAU;;AAE7B,KAAI,KAAK,SAAS,QAAW;AAC3B,aAAW,KAAK,WAAW,QAAQ;AACnC,SAAO,KAAK,KAAK,KAAK;;CAGxB,MAAM,QAAQ,WAAW,SAAS,IAAI,SAAS,WAAW,KAAK,QAAQ,KAAK;CAC5E,MAAM,QAAQ,KAAK,SAAS;CAC5B,MAAM,SAAS,KAAK,UAAU;AAE9B,QAAO,KAAK,OAAO,OAAO;AAc1B,SAZe,MAAM,KAAK,MACxB;;;;;OAKG,MAAM;;cAEC,MAAM,WAAW,OAC3B,OACD,EAEa;;AAGhB,eAAsB,wBACpB,MACA,WACA,OAC2B;AAC3B,OAAM,wBAAwB,KAAK;AAcnC,SAZe,MAAM,KAAK,MACxB;;;;;;;gBAQA,CAAC,WAAW,MAAM,CACnB,EAEa;;AAGhB,eAAsB,yBACpB,MACA,WAC2B;AAC3B,OAAM,wBAAwB,KAAK;AAanC,SAXe,MAAM,KAAK,MACxB;;;;;;+BAOA,CAAC,UAAU,CACZ,EAEa;;AAGhB,eAAsB,oBAAoB,MAAuC;AAC/E,OAAM,wBAAwB,KAAK;CAEnC,MAAM,CAAC,UAAU,WAAW,cAAc,aAAa,MAAM,QAAQ,IAAI;EACvE,KAAK,MAAyB,iDAAiD;EAC/E,KAAK,MACH,yFACD;EACD,KAAK,MACH,kHACD;EACD,KAAK,MACH,2EACD;EACF,CAAC;AAEF,QAAO;EACL,OAAO,SAAS,SAAS,KAAK,IAAI,SAAS,KAAK,GAAG;EACnD,SAAS,UAAU,KAAK,KAAK,OAAO;GAAE,MAAM,EAAE;GAAM,OAAO,SAAS,EAAE,OAAO,GAAG;GAAE,EAAE;EACpF,YAAY,aAAa,KAAK,KAAK,OAAO;GAAE,cAAc,EAAE;GAAc,OAAO,SAAS,EAAE,OAAO,GAAG;GAAE,EAAE;EAC1G,aAAa,UAAU,KAAK,IAAI,cAAc;EAC/C;;;;;;;AAQH,eAAsB,4BACpB,MACA,WACA,WAC8C;AAC9C,KAAI,UAAU,WAAW,EAAG,wBAAO,IAAI,KAAK;AAE5C,KAAI;EACF,MAAM,SAAoB,CAAC,UAAU;EACrC,IAAI,gBAAgB;AACpB,MAAI,cAAc,QAAW;AAC3B,UAAO,KAAK,UAAU;AACtB,mBAAgB,qBAAqB,OAAO;;EAG9C,MAAM,SAAS,MAAM,KAAK,MACxB;;;;WAIK,cAAc;sCAEnB,OACD;EAED,MAAM,yBAAS,IAAI,KAAqC;AACxD,OAAK,MAAM,OAAO,OAAO,MAAM;GAC7B,MAAM,WAAW,OAAO,IAAI,IAAI,KAAK,IAAI,EAAE;AAC3C,YAAS,IAAI,SAAS,SAAS,IAAI,SAAS,KAAK,SAAS,IAAI,KAAK,GAAG;AACtE,UAAO,IAAI,IAAI,MAAM,SAAS;;AAEhC,SAAO;SACD;AACN,yBAAO,IAAI,KAAK;;;;AASpB,eAAsB,oBACpB,MACA,SACe;AACf,OAAM,wBAAwB,KAAK;AAEnC,OAAM,KAAK,MACT;;;;;;;;;;;;wDAaA;EACE,QAAQ;EACR,QAAQ,cAAc;EACtB,QAAQ,gBAAgB;EACxB,QAAQ,WAAW;EACnB,QAAQ,gBAAgB;EACxB,QAAQ,WAAW;EACnB,QAAQ,aAAa;EACrB,QAAQ,cAAc;EACtB,QAAQ,qBAAqB;EAC9B,CACF;;AAGH,eAAsB,qBACpB,MACA,WACA,OAC8B;AAC9B,OAAM,wBAAwB,KAAK;AAanC,SAXe,MAAM,KAAK,MACxB;;;;;;gBAOA,CAAC,WAAW,MAAM,CACnB,EAEa;;;;;;;AAYhB,eAAsB,sBACpB,MACA,OACe;AACf,OAAM,0BAA0B,KAAK;CAErC,MAAM,QAAQ,MAAM,SAAS;CAC7B,MAAM,SAAS,MAAM,UAAU;CAC/B,MAAM,OAAO,MAAM,gBAAgB;CACnC,MAAM,kBAAkB,KAAK,UAAU,OAAO,CAAC,KAAK,GAAG,EAAE,CAAC;AAE1D,OAAM,KAAK,MACT;;;;;;;;;;;aAYA;EAAC;EAAO,MAAM;EAAY;EAAQ;EAAiB;EAAK,CACzD;;;AAIH,eAAsB,oBACpB,MACA,OAAmC,EAAE,EACP;AAC9B,OAAM,0BAA0B,KAAK;CAErC,MAAM,aAAuB,EAAE;CAC/B,MAAM,SAAoB,EAAE;CAC5B,IAAI,MAAM;AAEV,YAAW,KAAK,YAAY,QAAQ;AACpC,QAAO,KAAK,KAAK,SAAS,UAAU;AAEpC,KAAI,KAAK,WAAW,QAAW;AAC7B,aAAW,KAAK,aAAa,QAAQ;AACrC,SAAO,KAAK,KAAK,OAAO;;CAG1B,MAAM,QAAQ,SAAS,WAAW,KAAK,QAAQ;CAC/C,MAAM,QAAQ,KAAK,SAAS;AAC5B,QAAO,KAAK,MAAM;AAclB,SAZe,MAAM,KAAK,MACxB;;;;;OAKG,MAAM;;cAEC,OACV,OACD,EAEa;;;;;;;;;;;;;;;ACjchB,MAAM,EAAE,MAAM,WAAW;AAEzB,IAAa,kBAAb,MAAa,gBAA0C;CACrD,AAAS,cAAc;CACvB,AAAS,2BAA2B;CAEpC,AAAQ;;;;;;;CAQR,aAAa,eAAe,QAAuC;EACjE,MAAM,UACJ,OAAO,oBACP,gBAAgB,OAAO,QAAQ,MAAM,GAAG,OAAO,YAAY,MAAM,GAAG,OAAO,QAAQ,YAAY,GAAG,OAAO,QAAQ,KAAK,GAAG,OAAO,YAAY;EAE9I,MAAM,WADM,IAAI,IAAI,QAAQ,CACP,SAAS,MAAM,EAAE;EAEtC,MAAM,WAAW,IAAI,IAAI,QAAQ;AACjC,WAAS,WAAW;EACpB,MAAM,YAAY,IAAI,OAAO;GAC3B,kBAAkB,SAAS,UAAU;GACrC,KAAK;GACL,yBAAyB;GAC1B,CAAC;AAEF,MAAI;AAKF,QAJc,MAAM,UAAU,MAC5B,gDACA,CAAC,SAAS,CACX,EACS,aAAa,GAAG;AACxB,UAAM,UAAU,MAAM,oBAAoB,SAAS,GAAG;AACtD,YAAQ,OAAO,MAAM,oCAAoC,SAAS,IAAI;;YAEhE;AACR,SAAM,UAAU,KAAK;;EAGvB,MAAM,aAAa,IAAI,OAAO;GAC5B,kBAAkB;GAClB,KAAK;GACL,yBAAyB;GAC1B,CAAC;AAEF,MAAI;AAIF,QAHmB,MAAM,WAAW,MAClC,0EACD,EACc,aAAa,GAAG;IAC7B,MAAM,UAAU,aAAa,kBAAkB,OAAO,KAAK,KAAK,kBAAkB,EAAE,QAAQ;AAC5F,UAAM,WAAW,MAAM,QAAQ;AAC/B,YAAQ,OAAO,MAAM,8CAA8C,SAAS,IAAI;;AAIlF,SAAM,gBAAgB,cAAc,WAAW;YACvC;AACR,SAAM,WAAW,KAAK;;;;;;;CAQ1B,aAAqB,cAAc,MAA2B;AAM5D,OAJiB,MAAM,KAAK,MAC1B;wEAED,EACY,aAAa,GAAG;AAC3B,SAAM,KAAK,MACT,kFACD;AACD,WAAQ,OAAO,MAAM,qEAAqE;;AAO5F,OAHgB,MAAM,KAAK,MACzB,0EACD,EACW,aAAa,GAAG;AAC1B,SAAM,KAAK,MAAM;;;;;;;;;;;;;QAaf;AACF,SAAM,KAAK,MAAM,uDAAuD;AACxE,SAAM,KAAK,MAAM,yDAAyD;AAC1E,SAAM,KAAK,MAAM,sDAAsD;AACvE,SAAM,KAAK,MAAM,oEAAoE;AACrF,WAAQ,OAAO,MAAM,uDAAuD;;AAM9E,QAAM,KAAK,MAAM;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;MAuGf;EAGF,MAAM,mBAAmB,MAAM,KAAK,MAClC;mGAED;EACD,MAAM,uBAAuB,IAAI,IAAI,iBAAiB,KAAK,KAAK,MAA+B,EAAE,YAAY,CAAC;AAC9G,MAAI,CAAC,qBAAqB,IAAI,kBAAkB,EAAE;AAChD,SAAM,KAAK,MAAM,yDAAyD;AAC1E,WAAQ,OAAO,MAAM,yEAAyE;;AAEhG,MAAI,CAAC,qBAAqB,IAAI,mBAAmB,EAAE;AACjD,SAAM,KAAK,MAAM,4DAA4D;AAC7E,WAAQ,OAAO,MAAM,0EAA0E;;;CAInG,YAAY,QAAwB;AAKlC,OAAK,OAAO,IAAI,OAAO;GACrB,kBAJA,OAAO,oBACP,gBAAgB,OAAO,QAAQ,MAAM,GAAG,OAAO,YAAY,MAAM,GAAG,OAAO,QAAQ,YAAY,GAAG,OAAO,QAAQ,KAAK,GAAG,OAAO,YAAY;GAI5I,KAAK,OAAO,kBAAkB;GAC9B,yBAAyB,OAAO,uBAAuB;GACvD,mBAAmB;GAInB,iBAAiB;GAClB,CAAC;AAEF,OAAK,KAAK,GAAG,UAAU,QAAQ;AAC7B,WAAQ,OAAO,MAAM,8BAA8B,IAAI,QAAQ,IAAI;IACnE;;CAOJ,MAAM,QAAuB;AAC3B,QAAM,KAAK,KAAK,KAAK;;;;;;CAOvB,UAAgB;AACd,SAAO,KAAK;;CAGd,MAAM,WAAqC;EACzC,MAAM,SAAS,MAAM,KAAK,KAAK,SAAS;AACxC,MAAI;GACF,MAAM,cAAc,MAAM,OAAO,MAC/B,4CACD;GACD,MAAM,eAAe,MAAM,OAAO,MAChC,6CACD;AACD,UAAO;IACL,OAAO,SAAS,YAAY,KAAK,IAAI,KAAK,KAAK,GAAG;IAClD,QAAQ,SAAS,aAAa,KAAK,IAAI,KAAK,KAAK,GAAG;IACrD;YACO;AACR,UAAO,SAAS;;;CAIpB,MAAM,gBAAgB,WAA6C;EACjE,MAAM,cAAc,MAAM,KAAK,KAAK,MAClC,mEACA,CAAC,UAAU,CACZ;EACD,MAAM,eAAe,MAAM,KAAK,KAAK,MACnC,oEACA,CAAC,UAAU,CACZ;AACD,SAAO;GACL,OAAO,SAAS,YAAY,KAAK,IAAI,KAAK,KAAK,GAAG;GAClD,QAAQ,SAAS,aAAa,KAAK,IAAI,KAAK,KAAK,GAAG;GACrD;;;;;;CAOH,MAAM,iBAAyC;EAC7C,IAAI,SAA4B;AAChC,MAAI;AACF,YAAS,MAAM,KAAK,KAAK,SAAS;AAClC,SAAM,OAAO,MAAM,WAAW;AAC9B,UAAO;WACA,GAAG;AACV,UAAO,aAAa,QAAQ,EAAE,UAAU,OAAO,EAAE;YACzC;AACR,WAAQ,SAAS;;;CAQrB,MAAM,YAAY,WAAmB,MAA2C;AAK9E,UAJe,MAAM,KAAK,KAAK,MAC7B,kEACA,CAAC,WAAW,KAAK,CAClB,EACa,KAAK,IAAI;;CAGzB,MAAM,WAAW,MAA8B;AAC7C,QAAM,KAAK,KAAK,MACd;;;;;;;kCAQA;GAAC,KAAK;GAAW,KAAK;GAAM,KAAK;GAAQ,KAAK;GAAM,KAAK;GAAM,KAAK;GAAO,KAAK;GAAK,CACtF;;CAOH,MAAM,YAAY,WAAmB,MAAiC;AAKpE,UAJe,MAAM,KAAK,KAAK,MAC7B,iEACA,CAAC,WAAW,KAAK,CAClB,EACa,KAAK,KAAK,MAAM,EAAE,GAAG;;CAGrC,MAAM,oBAAoB,WAAmB,MAA6B;AACxE,QAAM,KAAK,KAAK,MACd,8DACA,CAAC,WAAW,KAAK,CAClB;;CAGH,MAAM,aAAa,QAAmC;AACpD,MAAI,OAAO,WAAW,EAAG;EAEzB,MAAM,SAAS,MAAM,KAAK,KAAK,SAAS;AACxC,MAAI;AACF,SAAM,OAAO,MAAM,QAAQ;AAE3B,QAAK,MAAM,KAAK,QAAQ;IACtB,MAAM,WAAW,EAAE,KAAK,QAAQ,OAAO,GAAG;AAE1C,UAAM,OAAO,MACX;;;;;;;;;;;;;;;gDAgBA;KACE,EAAE;KAAI,EAAE;KAAW,EAAE;KAAQ,EAAE;KAAM,EAAE;KACvC,EAAE;KAAW,EAAE;KAAS,EAAE;KAAM;KAAU,EAAE;KAC7C,CACF;;AAGH,SAAM,OAAO,MAAM,SAAS;WACrB,GAAG;AACV,SAAM,OAAO,MAAM,WAAW;AAC9B,SAAM;YACE;AACR,UAAO,SAAS;;;CAIpB,MAAM,sBAAsB,WAAsC;AAKhE,UAJe,MAAM,KAAK,KAAK,MAC7B,8DACA,CAAC,UAAU,CACZ,EACa,KAAK,KAAK,MAAM,EAAE,KAAK;;CAGvC,MAAM,YAAY,WAAmB,OAAgC;AACnE,MAAI,MAAM,WAAW,EAAG;EACxB,MAAM,SAAS,MAAM,KAAK,KAAK,SAAS;AACxC,MAAI;AACF,SAAM,OAAO,MAAM,QAAQ;AAC3B,QAAK,MAAM,QAAQ,OAAO;AACxB,UAAM,OAAO,MAAM,8DAA8D,CAAC,WAAW,KAAK,CAAC;AACnG,UAAM,OAAO,MAAM,6DAA6D,CAAC,WAAW,KAAK,CAAC;;AAEpG,SAAM,OAAO,MAAM,SAAS;WACrB,GAAG;AACV,SAAM,OAAO,MAAM,WAAW;AAC9B,SAAM;YACE;AACR,UAAO,SAAS;;;CAIpB,MAAM,sBACJ,WACA,OACA,OACgF;EAChF,MAAM,SAAiC,EAAE;EACzC,IAAI,MAAM;AACV,MAAI,cAAc,QAAW;AAC3B,UAAO,KAAK,UAAU;AACtB,UAAO,sBAAsB,OAAO;;AAEtC,MAAI,OAAO;AACT,UAAO,KAAK,MAAM,WAAW,MAAM,WAAW,MAAM,GAAG;AACvD,UAAO,uBAAuB,OAAO,SAAS,EAAE,qBAAqB,OAAO,SAAS,EAAE,aAAa,OAAO,OAAO;;AAEpH,SAAO;AACP,MAAI,UAAU,QAAW;AACvB,UAAO,KAAK,MAAM;AAClB,UAAO,WAAW,OAAO;;AAG3B,UADe,MAAM,KAAK,KAAK,MAAsE,KAAK,OAAO,EACnG;;CAGhB,MAAM,gBAAgB,SAAiB,WAAkC;EAEvE,MAAM,SAAS,MADH,eAAe,UAAU,CACZ,KAAK,IAAI,GAAG;AACrC,QAAM,KAAK,KAAK,MACd,8DACA,CAAC,QAAQ,QAAQ,CAClB;;CAOH,MAAM,cAAc,OAAe,MAA+C;AAChF,SAAO,cAAc,KAAK,MAAM,OAAO,KAAK;;CAG9C,MAAM,eAAe,gBAA8B,MAA+C;AAChG,SAAO,eAAe,KAAK,MAAM,gBAAgB,KAAK;;CAOxD,MAAM,gBAAgB,MAAmC;AAAE,SAAOA,gBAAsB,KAAK,MAAM,KAAK;;CACxG,MAAM,gBAAgB,WAAkC;AAAE,SAAOC,gBAAsB,KAAK,MAAM,UAAU;;CAC5G,MAAM,aAAa,WAAiD;AAAE,SAAOC,aAAmB,KAAK,MAAM,UAAU;;CACrH,MAAM,oBAAoB,OAAe,QAA8C;AAAE,SAAOC,oBAA0B,KAAK,MAAM,OAAO,OAAO;;CACnJ,MAAM,mBAA4C;AAAE,SAAOC,iBAAuB,KAAK,KAAK;;CAC5F,MAAM,oBAAoB,SAA0C;AAAE,SAAOC,oBAA0B,KAAK,MAAM,QAAQ;;CAC1H,MAAM,kBAAmC;AAAE,SAAOC,gBAAsB,KAAK,KAAK;;CAClF,MAAM,0BAA0B,QAAiC;AAAE,SAAOC,0BAAgC,KAAK,MAAM,OAAO;;CAC5H,MAAM,qBAAqB,SAAkC;AAAE,SAAOC,qBAA2B,KAAK,MAAM,QAAQ;;CACpH,MAAM,qBAAqB,OAA0C;AAAE,SAAOC,qBAA2B,KAAK,MAAM,MAAM;;CAC1H,MAAM,0BAA0B,OAAiB,SAA0C;AAAE,SAAOC,0BAAgC,KAAK,MAAM,OAAO,QAAQ;;CAC9J,MAAM,uBAA0C;AAAE,SAAOC,qBAA2B,KAAK,KAAK;;CAC9F,MAAM,4BAA4B,QAAmC;AAAE,SAAOC,4BAAkC,KAAK,MAAM,OAAO;;CAClI,MAAM,uBAAuB,SAAoC;AAAE,SAAOC,uBAA6B,KAAK,MAAM,QAAQ;;CAE1H,MAAM,mBAAmB,SAAyC;AAAE,SAAOC,mBAAyB,KAAK,MAAM,QAAQ;;CACvH,MAAM,mBAAmB,eAAsC;AAAE,SAAOC,mBAAyB,KAAK,MAAM,cAAc;;CAC1H,MAAM,cAAc,WAA8D;AAAE,SAAOC,cAAoB,KAAK,MAAM,UAAU;;CAEpI,MAAM,uBAAuB,aAAuB,OAAsC;AAAE,SAAOC,uBAA6B,KAAK,MAAM,aAAa,MAAM;;CAC9J,MAAM,mBAAmB,YAA6C;AAAE,SAAOC,mBAAyB,KAAK,MAAM,WAAW;;CAC9H,MAAM,iBAAiB,YAA6C;AAAE,SAAOC,iBAAuB,KAAK,MAAM,WAAW;;CAC1H,MAAM,oBAAkF;AAAE,SAAOC,kBAAwB,KAAK,KAAK;;CACnI,MAAM,eAA0E;AAAE,SAAOC,aAAmB,KAAK,KAAK;;CACtH,MAAM,8BAA6G;AAAE,SAAOC,4BAAkC,KAAK,KAAK;;CACxK,MAAM,uBAAuB,QAA+F;AAAE,SAAOC,uBAA6B,KAAK,MAAM,OAAO;;CACpL,MAAM,kBAAkB,SAAgG;AAAE,SAAOC,kBAAwB,KAAK,MAAM,QAAQ;;CAC5K,MAAM,0BAA0B,QAAiC;AAAE,SAAOC,0BAAgC,KAAK,MAAM,OAAO;;CAC5H,MAAM,qBAAqB,SAAkC;AAAE,SAAOC,qBAA2B,KAAK,MAAM,QAAQ;;CACpH,MAAM,uBAAuB,aAAgD;AAAE,SAAOC,uBAA6B,KAAK,MAAM,YAAY;;CAC1I,MAAM,oBAAwE;AAAE,SAAOC,kBAAwB,KAAK,KAAK;;CACzH,MAAM,4BAA4B,QAAoE;AAAE,SAAOC,4BAAkC,KAAK,MAAM,OAAO;;CACnK,MAAM,uBAAuB,SAAqE;AAAE,SAAOC,uBAA6B,KAAK,MAAM,QAAQ;;CAE3J,MAAM,kBAAkB,MAAuC;AAAE,SAAOC,kBAAwB,KAAK,MAAM,KAAK;;CAChH,MAAM,eAAe,WAAmD;AAAE,SAAOC,eAAqB,KAAK,MAAM,UAAU;;CAC3H,MAAM,aAAwC;AAAE,SAAOC,WAAiB,KAAK,KAAK;;CAClF,MAAM,qBAAqB,QAAmC;AAAE,SAAOC,qBAA2B,KAAK,MAAM,OAAO;;CACpH,MAAM,gBAAgB,SAAoC;AAAE,SAAOC,gBAAsB,KAAK,MAAM,QAAQ;;CAC5G,MAAM,qBAAwC;AAAE,SAAOC,mBAAyB,KAAK,KAAK;;CAC1F,MAAM,6BAA6B,QAAmC;AAAE,SAAOC,6BAAmC,KAAK,MAAM,OAAO;;CACpI,MAAM,wBAAwB,SAAoC;AAAE,SAAOC,wBAA8B,KAAK,MAAM,QAAQ;;CAE5H,MAAM,gBAAgB,SAA0C;AAAE,SAAOC,gBAAsB,KAAK,MAAM,QAAQ;;CAClH,MAAM,iBAAiB,SAA0C;AAAE,SAAOC,iBAAuB,KAAK,MAAM,QAAQ;;CACpH,MAAM,iBAAiB,MAAiC;AAAE,SAAOC,iBAAuB,KAAK,MAAM,KAAK;;CACxG,MAAM,qBAAqB,OAAe,OAAmC;AAAE,SAAOC,qBAA2B,KAAK,MAAM,OAAO,MAAM;;CAGzI,MAAM,wBAAwB,WAAmB,OAAkF;AAKjI,UAJU,MAAM,KAAK,KAAK,MACxB,sIACA,CAAC,WAAW,MAAM,CACnB,EACQ;;CAGX,MAAM,iBAAiB,WAAmB,MAAc,QAAQ,IAAgE;AAK9H,UAJU,MAAM,KAAK,KAAK,MACxB,wIACA;GAAC;GAAW;GAAM;GAAM,CACzB,EACQ;;CAGX,MAAM,mBAAmB,WAAmB,OAAe,OAA+D;AAKxH,UAJU,MAAM,KAAK,KAAK,MACxB,+GACA;GAAC;GAAW,IAAI,MAAM;GAAI;GAAM,CACjC,EACQ;;CAOX,MAAM,eAAe,QAA+C;EAClE,MAAM,WAAW,OAAO,YAAY;EACpC,MAAM,WAAW,gBAAgB,OAAO,MAAM,SAAS;EACvD,MAAM,MAAM,KAAK,KAAK;AAEtB,QAAM,KAAK,KAAK,MACd;;;;;;;;6GASA;GAAC;GAAU;GAAU,OAAO;GAAM,OAAO,QAAQ;GAAW,OAAO,eAAe;GAAM;GAAK;GAAI,CAClG;AAED,SAAO;;CAGT,MAAM,aAAa,MAAc,WAAW,WAAqC;EAC/E,MAAM,WAAW,gBAAgB,MAAM,SAAS;EAKhD,MAAM,OAJS,MAAM,KAAK,KAAK,MAC7B,qEACA,CAAC,UAAU,SAAS,CACrB,EACkB,KAAK;AACxB,SAAO,MAAM,cAAc,IAAI,GAAG;;CAGpC,MAAM,eAAe,WAAW,WAAW,MAAe,QAAQ,KAA0B;AAU1F,UATe,OACX,MAAM,KAAK,KAAK,MACd,qGACA;GAAC;GAAU;GAAM;GAAM,CACxB,GACD,MAAM,KAAK,KAAK,MACd,uFACA,CAAC,UAAU,MAAM,CAClB,EACS,KAAK,IAAI,cAAc;;CAGvC,MAAM,2BAA2B,UAAkB,kBAA0B,QAAQ,IAAoB;EAKvG,MAAM,OAJS,MAAM,KAAK,KAAK,MAC7B,gEACA,CAAC,SAAS,CACX,EACkB,KAAK;AACxB,MAAI,CAAC,IAAK;EAEV,MAAM,YAAY,OAAO,IAAI,gBAAgB,GAAG,SAAS,mBAAmB,OAAO,IAAI,gBAAgB;AACvG,QAAM,KAAK,KAAK,MACd,oEACA,CAAC,WAAW,SAAS,CACtB;;CAOH,MAAM,qBAAqB,UAAiG;AAC1H,MAAI,SAAS,WAAW,EAAG,QAAO,EAAE;AAKpC,UAJe,MAAM,KAAK,KAAK,MAC7B,uEACA,CAAC,SAAS,CACX,EACa,KAAK,KAAK,OAAO;GAAE,IAAI,EAAE;GAAI,MAAM,EAAE;GAAM,gBAAgB,EAAE;GAAiB,EAAE;;CAGhG,MAAM,0BAA0B,SAAiB,OAA8B;AAC7E,QAAM,KAAK,KAAK,MAAM,4DAA4D,CAAC,OAAO,QAAQ,CAAC;;CAGrG,MAAM,wBAAwB,UAAmC;AAC/D,MAAI,SAAS,WAAW,EAAG;AAC3B,MAAI;AACF,SAAM,KAAK,KAAK,MAAM,kEAAkE,CAAC,KAAK,KAAK,EAAE,SAAS,CAAC;UACzG;;CASV,MAAM,SAAS,UAA+E;AAC5F,SAAO,oBAAoB,MAAM,SAAS;;CAO5C,MAAM,YAAY,UAA2B,SAA0D;AACrG,SAAO,oBAAoB,KAAK,MAAM,UAAU,QAAQ;;CAO1D,MAAM,yBAAuD;AAC3D,SAAO,+BAA+B,KAAK,KAAK;;CAOlD,MAAM,YAAY,QAAwC;AAAE,SAAOC,YAAsB,KAAK,MAAM,OAAO;;CAC3G,MAAM,eAAe,QAA4C;AAAE,SAAOC,eAAyB,KAAK,MAAM,OAAO;;CACrH,MAAM,mBAAmB,UAAiC;AAAE,SAAOC,mBAA6B,KAAK,MAAM,SAAS;;CACpH,MAAM,oBAAoB,SAA6C;AAAE,SAAOC,oBAA8B,KAAK,MAAM,QAAQ;;CACjI,MAAM,aAA+B;AAAE,SAAOC,WAAqB,KAAK,KAAK;;CAM7E,MAAM,iBAAiB,KAAoD;AAAE,SAAOC,iBAA8B,KAAK,MAAM,IAAI;;CACjI,MAAM,4BAA4B,UAA2B,KAAiD;AAC5G,SAAOC,4BAAyC,KAAK,MAAM,UAAU,IAAI;;CAE3E,MAAM,kBAAkB,MAA4D;AAAE,SAAOC,kBAA+B,KAAK,MAAM,KAAK;;CAC5I,MAAM,wBAAwB,WAAmB,OAA0C;AAAE,SAAOC,wBAAqC,KAAK,MAAM,WAAW,MAAM;;CACrK,MAAM,yBAAyB,WAA8C;AAAE,SAAOC,yBAAsC,KAAK,MAAM,UAAU;;CACjJ,MAAM,sBAAiD;AAAE,SAAOC,oBAAiC,KAAK,KAAK;;CAC3G,MAAM,4BAA4B,WAAqB,WAAkE;AACvH,SAAOC,4BAAyC,KAAK,MAAM,WAAW,UAAU;;CAElF,MAAM,oBAAoB,SAAkD;AAAE,SAAOC,oBAAiC,KAAK,MAAM,QAAQ;;CACzI,MAAM,qBAAqB,WAAmB,OAA6C;AAAE,SAAOC,qBAAkC,KAAK,MAAM,WAAW,MAAM;;CAClK,MAAM,sBAAsB,OAAkD;AAAE,SAAOC,sBAAmC,KAAK,MAAM,MAAM;;CAC3I,MAAM,oBAAoB,MAAiE;AAAE,SAAOC,oBAAiC,KAAK,MAAM,KAAK;;CAMrJ,MAAM,oBAAoB,OAAkC;AAC1D,MAAI,MAAM,WAAW,EAAG,QAAO;EAC/B,MAAM,SAAS,MAAM,KAAK,KAAK,MAC7B,kEACA,CAAC,MAAM,CACR;AACD,SAAO,SAAS,OAAO,KAAK,IAAI,KAAK,KAAK,GAAG;;CAG/C,MAAM,gBAAgB,OAAqE;AACzF,MAAI,MAAM,WAAW,EAAG,QAAO;EAC/B,MAAM,SAAS,MAAM,KAAK,KAAK,SAAS;EACxC,IAAI,eAAe;AACnB,MAAI;AACF,SAAM,OAAO,MAAM,QAAQ;AAC3B,QAAK,MAAM,EAAE,SAAS,aAAa,OAAO;IACxC,MAAM,IAAI,MAAM,OAAO,MAAM,kDAAkD,CAAC,SAAS,QAAQ,CAAC;AAClG,oBAAgB,EAAE,YAAY;AAC9B,UAAM,OAAO,MAAM,mDAAmD,CAAC,SAAS,QAAQ,CAAC;;AAE3F,SAAM,OAAO,MAAM,SAAS;WACrB,GAAG;AACV,SAAM,OAAO,MAAM,WAAW;AAC9B,SAAM;YACE;AACR,UAAO,SAAS;;AAElB,SAAO;;;;;;;;;AAUX,SAAS,cAAc,KAAwC;AAC7D,QAAO;EACL,WAAW,IAAI;EACf,WAAW,IAAI;EACf,MAAM,IAAI;EACV,MAAM,IAAI;EACV,aAAa,IAAI;EACjB,YAAY,IAAI,cAAc,OAAO,OAAO,IAAI,WAAW,GAAG;EAC9D,WAAW,IAAI,aAAa,OAAO,OAAO,IAAI,UAAU,GAAG;EAC3D,eAAe,OAAO,IAAI,cAAc;EACxC,iBAAiB,OAAO,IAAI,gBAAgB;EAC7C"}