@tekmidian/pai 0.35.0 → 0.35.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 (37) hide show
  1. package/dist/.metadata_never_index +0 -0
  2. package/dist/{auto-route-lDk1q_2h.mjs → auto-route-DVM3U2ZY.mjs} +1 -1
  3. package/dist/{auto-route-lDk1q_2h.mjs.map → auto-route-DVM3U2ZY.mjs.map} +1 -1
  4. package/dist/cli/index.mjs +2 -2
  5. package/dist/cli/program.mjs +2 -2
  6. package/dist/config-BSkVcvfq.mjs.map +1 -1
  7. package/dist/daemon/index.mjs +3 -3
  8. package/dist/{daemon-D6qwIKjj.mjs → daemon-B8L2f5vy.mjs} +41 -14
  9. package/dist/daemon-B8L2f5vy.mjs.map +1 -0
  10. package/dist/{factory-WCByhnB6.mjs → factory-vPTPoBR7.mjs} +3 -3
  11. package/dist/{factory-WCByhnB6.mjs.map → factory-vPTPoBR7.mjs.map} +1 -1
  12. package/dist/hooks/whisper-reinject.mjs +59 -0
  13. package/dist/hooks/whisper-reinject.mjs.map +7 -0
  14. package/dist/hooks/whisper-rules.mjs +24 -9
  15. package/dist/hooks/whisper-rules.mjs.map +2 -2
  16. package/dist/link-boost-QFLrJwD6.mjs +34 -0
  17. package/dist/link-boost-QFLrJwD6.mjs.map +1 -0
  18. package/dist/{pick-D7-XuJqF.mjs → pick-DatgS3Nk.mjs} +14 -14
  19. package/dist/{pick-D7-XuJqF.mjs.map → pick-DatgS3Nk.mjs.map} +1 -1
  20. package/dist/{postgres-CYQuLAfD.mjs → postgres-CI3FjGq0.mjs} +6 -3
  21. package/dist/postgres-CI3FjGq0.mjs.map +1 -0
  22. package/dist/{query-feedback-ytPNVJWt.mjs → query-feedback-D4U56Hz6.mjs} +1 -1
  23. package/dist/{query-feedback-ytPNVJWt.mjs.map → query-feedback-D4U56Hz6.mjs.map} +1 -1
  24. package/dist/{sqlite-R0pIbTmj.mjs → sqlite-C6FHnMkn.mjs} +1 -1
  25. package/dist/{sqlite-R0pIbTmj.mjs.map → sqlite-C6FHnMkn.mjs.map} +1 -1
  26. package/dist/{tools-B5t3lZ7v.mjs → tools-C1lCHerL.mjs} +27 -12
  27. package/dist/tools-C1lCHerL.mjs.map +1 -0
  28. package/dist/{vault-indexer-D04KuFhI.mjs → vault-indexer-CUF9edbW.mjs} +1 -1
  29. package/dist/{vault-indexer-D04KuFhI.mjs.map → vault-indexer-CUF9edbW.mjs.map} +1 -1
  30. package/dist/{zettelkasten-DePb0LVI.mjs → zettelkasten-W-h8G2is.mjs} +2 -2
  31. package/dist/{zettelkasten-DePb0LVI.mjs.map → zettelkasten-W-h8G2is.mjs.map} +1 -1
  32. package/package.json +1 -1
  33. package/src/hooks/ts/post-tool-use/whisper-reinject.ts +88 -0
  34. package/src/hooks/ts/user-prompt/whisper-rules.ts +62 -8
  35. package/dist/daemon-D6qwIKjj.mjs.map +0 -1
  36. package/dist/postgres-CYQuLAfD.mjs.map +0 -1
  37. package/dist/tools-B5t3lZ7v.mjs.map +0 -1
@@ -1 +0,0 @@
1
- {"version":3,"file":"daemon-D6qwIKjj.mjs","names":["getQueueStats"],"sources":["../src/notifications/config.ts","../src/daemon/daemon/scheduler.ts","../src/observations/store.ts","../src/daemon/daemon/dispatcher.ts","../src/daemon/daemon/handler.ts","../src/daemon/daemon/server.ts","../src/daemon/daemon.ts"],"sourcesContent":["/**\n * config.ts — Notification config persistence helpers\n *\n * Reads and writes the `notifications` section of ~/.config/pai/config.json.\n * Deep-merges with defaults so partial configs work fine.\n *\n * This module is intentionally separate from the daemon's config loader\n * so it can be used standalone (e.g. from CLI commands).\n */\n\nimport {\n existsSync,\n readFileSync,\n writeFileSync,\n mkdirSync,\n} from \"node:fs\";\nimport {\n CONFIG_FILE,\n CONFIG_DIR,\n expandHome,\n} from \"../daemon/config.js\";\nimport type {\n NotificationConfig,\n ChannelConfigs,\n RoutingTable,\n NotificationMode,\n} from \"./types.js\";\nimport {\n DEFAULT_NOTIFICATION_CONFIG,\n DEFAULT_CHANNELS,\n DEFAULT_ROUTING,\n} from \"./types.js\";\n\n// ---------------------------------------------------------------------------\n// Deep merge helper (same approach as daemon/config.ts)\n// ---------------------------------------------------------------------------\n\nfunction deepMerge<T extends object>(\n target: T,\n source: Record<string, unknown>\n): T {\n const result = { ...target };\n for (const key of Object.keys(source)) {\n const srcVal = source[key];\n if (srcVal === undefined || srcVal === null) continue;\n const tgtVal = (target as Record<string, unknown>)[key];\n if (\n typeof srcVal === \"object\" &&\n !Array.isArray(srcVal) &&\n typeof tgtVal === \"object\" &&\n tgtVal !== null &&\n !Array.isArray(tgtVal)\n ) {\n (result as Record<string, unknown>)[key] = deepMerge(\n tgtVal as object,\n srcVal as Record<string, unknown>\n );\n } else {\n (result as Record<string, unknown>)[key] = srcVal;\n }\n }\n return result;\n}\n\n// ---------------------------------------------------------------------------\n// Load\n// ---------------------------------------------------------------------------\n\n/**\n * Load the notification config from the PAI config file.\n * Returns defaults merged with any stored values.\n */\nexport function loadNotificationConfig(): NotificationConfig {\n if (!existsSync(CONFIG_FILE)) {\n return { ...DEFAULT_NOTIFICATION_CONFIG };\n }\n\n let raw: string;\n try {\n raw = readFileSync(CONFIG_FILE, \"utf-8\");\n } catch {\n return { ...DEFAULT_NOTIFICATION_CONFIG };\n }\n\n let parsed: Record<string, unknown>;\n try {\n parsed = JSON.parse(raw) as Record<string, unknown>;\n } catch {\n return { ...DEFAULT_NOTIFICATION_CONFIG };\n }\n\n const stored = parsed[\"notifications\"];\n if (!stored || typeof stored !== \"object\") {\n return { ...DEFAULT_NOTIFICATION_CONFIG };\n }\n\n return deepMerge(\n DEFAULT_NOTIFICATION_CONFIG,\n stored as Record<string, unknown>\n );\n}\n\n// ---------------------------------------------------------------------------\n// Save\n// ---------------------------------------------------------------------------\n\n/**\n * Persist the notification config by merging it into the existing\n * ~/.config/pai/config.json. Creates the file if it does not exist.\n */\nexport function saveNotificationConfig(config: NotificationConfig): void {\n // Ensure the config dir exists\n if (!existsSync(CONFIG_DIR)) {\n mkdirSync(CONFIG_DIR, { recursive: true });\n }\n\n // Read current full config\n let full: Record<string, unknown> = {};\n if (existsSync(CONFIG_FILE)) {\n try {\n full = JSON.parse(readFileSync(CONFIG_FILE, \"utf-8\")) as Record<\n string,\n unknown\n >;\n } catch {\n // Start fresh if the file is unreadable\n }\n }\n\n // Replace the notifications section\n full[\"notifications\"] = config;\n\n writeFileSync(CONFIG_FILE, JSON.stringify(full, null, 2) + \"\\n\", \"utf-8\");\n}\n\n// ---------------------------------------------------------------------------\n// Patch helpers (used by the set command)\n// ---------------------------------------------------------------------------\n\n/**\n * Apply a partial update to the current notification config and persist it.\n * Returns the new merged config.\n */\nexport function patchNotificationConfig(patch: {\n mode?: NotificationMode;\n channels?: Partial<Partial<ChannelConfigs>>;\n routing?: Partial<RoutingTable>;\n}): NotificationConfig {\n const current = loadNotificationConfig();\n\n if (patch.mode !== undefined) {\n current.mode = patch.mode;\n }\n\n if (patch.channels) {\n current.channels = deepMerge(\n current.channels,\n patch.channels as Record<string, unknown>\n );\n }\n\n if (patch.routing) {\n current.routing = deepMerge(\n current.routing,\n patch.routing as Record<string, unknown>\n );\n }\n\n saveNotificationConfig(current);\n return current;\n}\n\n// Re-export defaults for convenience\nexport { DEFAULT_NOTIFICATION_CONFIG, DEFAULT_CHANNELS, DEFAULT_ROUTING };\nexport { expandHome };\n","/**\n * Index, embed, vault index, and registry scan schedulers for the PAI daemon.\n * Exports run* functions (also called on-demand by the IPC handler)\n * and the start* functions invoked once at daemon startup.\n */\n\nimport { indexAll } from \"../../memory/indexer.js\";\nimport type { SQLiteBackendWithDb } from \"./types.js\";\nimport {\n registryDb,\n storageBackend,\n daemonConfig,\n indexInProgress,\n embedInProgress,\n vaultIndexInProgress,\n shutdownRequested,\n setIndexInProgress,\n setLastIndexTime,\n setIndexSchedulerTimer,\n setEmbedInProgress,\n setLastEmbedTime,\n setEmbedSchedulerTimer,\n setVaultIndexInProgress,\n setLastVaultIndexTime,\n} from \"./state.js\";\n\n// ---------------------------------------------------------------------------\n// Index scheduler\n// ---------------------------------------------------------------------------\n\n/** Minimum interval between vault index runs (30 minutes). */\nconst VAULT_INDEX_MIN_INTERVAL_MS = 30 * 60 * 1000;\n\n/**\n * Run a full index pass. Guards against overlapping runs with indexInProgress.\n * Called both by the scheduler and by the index_now IPC method.\n */\nexport async function runIndex(): Promise<void> {\n if (indexInProgress) {\n process.stderr.write(\"[pai-daemon] Index already in progress, skipping.\\n\");\n return;\n }\n\n if (embedInProgress) {\n process.stderr.write(\"[pai-daemon] Embed in progress, deferring index run.\\n\");\n return;\n }\n\n setIndexInProgress(true);\n const t0 = Date.now();\n\n try {\n process.stderr.write(\"[pai-daemon] Starting scheduled index run...\\n\");\n\n if (storageBackend.backendType === \"sqlite\") {\n const { SQLiteBackend } = await import(\"../../storage/sqlite.js\");\n if (storageBackend instanceof SQLiteBackend) {\n const db = (storageBackend as SQLiteBackendWithDb).getRawDb();\n const { projects, result } = await indexAll(db, registryDb);\n const elapsed = Date.now() - t0;\n setLastIndexTime(Date.now());\n process.stderr.write(\n `[pai-daemon] Index complete: ${projects} projects, ` +\n `${result.filesProcessed} files, ${result.chunksCreated} chunks ` +\n `(${elapsed}ms)\\n`\n );\n }\n } else {\n const { indexAllWithBackend } = await import(\"../../memory/indexer-backend.js\");\n const { projects, result } = await indexAllWithBackend(storageBackend, registryDb);\n const elapsed = Date.now() - t0;\n setLastIndexTime(Date.now());\n process.stderr.write(\n `[pai-daemon] Index complete (postgres): ${projects} projects, ` +\n `${result.filesProcessed} files, ${result.chunksCreated} chunks ` +\n `(${elapsed}ms)\\n`\n );\n }\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n process.stderr.write(`[pai-daemon] Index error: ${msg}\\n`);\n } finally {\n setIndexInProgress(false);\n }\n}\n\n/**\n * Run a vault index pass. Guards against overlapping runs with vaultIndexInProgress.\n * Skips if no vaultPath is configured, or if project index/embed is in progress.\n */\nexport async function runVaultIndex(): Promise<void> {\n if (!daemonConfig.vaultPath) return;\n\n if (vaultIndexInProgress) {\n process.stderr.write(\"[pai-daemon] Vault index already in progress, skipping.\\n\");\n return;\n }\n\n if (indexInProgress || embedInProgress) {\n process.stderr.write(\"[pai-daemon] Index/embed in progress, deferring vault index.\\n\");\n return;\n }\n\n // Import lastVaultIndexTime from state (re-read each call since it may change)\n const { lastVaultIndexTime } = await import(\"./state.js\");\n if (lastVaultIndexTime > 0 && Date.now() - lastVaultIndexTime < VAULT_INDEX_MIN_INTERVAL_MS) {\n return;\n }\n\n let vaultProjectId = daemonConfig.vaultProjectId;\n if (!vaultProjectId) {\n const row = registryDb\n .prepare(\"SELECT id FROM projects WHERE root_path = ?\")\n .get(daemonConfig.vaultPath) as { id: number } | undefined;\n vaultProjectId = row?.id ?? 999;\n if (!row) {\n process.stderr.write(\"[pai-daemon] Vault not in project registry — using synthetic project ID 999.\\n\");\n }\n }\n\n setVaultIndexInProgress(true);\n const t0 = Date.now();\n\n process.stderr.write(\"[pai-daemon] Starting vault index run...\\n\");\n\n try {\n const { indexVault } = await import(\"../../memory/vault-indexer.js\");\n const r = await indexVault(storageBackend, vaultProjectId, daemonConfig.vaultPath!);\n const elapsed = Date.now() - t0;\n setLastVaultIndexTime(Date.now());\n process.stderr.write(\n `[pai-daemon] Vault index complete: ${r.filesIndexed} files, ` +\n `${r.linksExtracted} links, ${r.deadLinksFound} dead, ` +\n `${r.orphansFound} orphans (${elapsed}ms)\\n`\n );\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n process.stderr.write(`[pai-daemon] Vault index error: ${msg}\\n`);\n } finally {\n setVaultIndexInProgress(false);\n }\n}\n\n/**\n * Start the periodic index scheduler. Runs an initial pass 2 seconds after startup.\n */\nexport function startIndexScheduler(): void {\n const intervalMs = daemonConfig.indexIntervalSecs * 1_000;\n\n process.stderr.write(\n `[pai-daemon] Index scheduler: every ${daemonConfig.indexIntervalSecs}s\\n`\n );\n\n // Indexing and embedding are mutually exclusive (each defers to the other),\n // and an index pass regularly outlasts the index interval. Left to two\n // independent timers, the embed tick therefore lands while indexing is in\n // progress and is dropped — observed live on 2026-08-01 as six consecutive\n // index passes with no embed pass at all, and a 143k-chunk embedding backlog.\n // Chaining embed onto the end of every index pass makes the alternation\n // guaranteed rather than a race: index, then vault, then embed, every cycle.\n const cycle = (label: string) =>\n runIndex()\n .then(() => runVaultIndex())\n .then(() => runEmbed())\n .catch((e) => {\n process.stderr.write(`[pai-daemon] ${label} index error: ${e}\\n`);\n });\n\n setTimeout(() => void cycle(\"Startup\"), 2_000);\n\n const timer = setInterval(() => void cycle(\"Scheduled\"), intervalMs);\n\n if (timer.unref) timer.unref();\n setIndexSchedulerTimer(timer);\n}\n\n// ---------------------------------------------------------------------------\n// Embed scheduler\n// ---------------------------------------------------------------------------\n\n/**\n * Run an embedding pass for all unembedded chunks (Postgres backend only).\n */\nexport async function runEmbed(): Promise<void> {\n if (embedInProgress) {\n process.stderr.write(\"[pai-daemon] Embed already in progress, skipping.\\n\");\n return;\n }\n\n if (indexInProgress) {\n process.stderr.write(\"[pai-daemon] Index in progress, deferring embed pass.\\n\");\n return;\n }\n\n if (storageBackend.backendType !== \"postgres\") {\n return;\n }\n\n setEmbedInProgress(true);\n const t0 = Date.now();\n\n try {\n process.stderr.write(\"[pai-daemon] Starting scheduled embed pass...\\n\");\n\n const projectNames = new Map<number, string>();\n try {\n const rows = registryDb\n .prepare(\"SELECT id, slug FROM projects WHERE status = 'active'\")\n .all() as Array<{ id: number; slug: string }>;\n for (const r of rows) projectNames.set(r.id, r.slug);\n } catch { /* registry unavailable — IDs will be used instead */ }\n\n const { embedChunksWithBackend } = await import(\"../../memory/indexer-backend.js\");\n // Explicit budgets so one embed phase can never outlast an index cycle.\n // Backlogs drain across passes, not within one.\n const count = await embedChunksWithBackend(\n storageBackend,\n () => shutdownRequested,\n projectNames,\n // Indexing settles to a few chunks per pass once caught up, so the daemon\n // is idle most of the cycle and the embed budget is what actually sets\n // drain rate. Measured 450 chunks per 90s against real (long) chunks, so\n // a backlog only clears at roughly the budget's share of the cycle. What\n // broke the starvation was bounding the pass at all, not this number —\n // it can be raised freely as long as it stays finite.\n { maxMillis: 240_000 },\n );\n\n let vaultEmbedCount = 0;\n if (daemonConfig.vaultPath) {\n try {\n const { SQLiteBackend } = await import(\"../../storage/sqlite.js\");\n const { openFederation } = await import(\"../../memory/db.js\");\n const federationDb = openFederation();\n const vaultSqliteBackend = new SQLiteBackend(federationDb);\n\n const vaultProjectNames = new Map(projectNames);\n if (!vaultProjectNames.has(999)) {\n vaultProjectNames.set(999, \"obsidian-vault\");\n }\n\n vaultEmbedCount = await embedChunksWithBackend(\n vaultSqliteBackend,\n () => shutdownRequested,\n vaultProjectNames,\n { maxMillis: 45_000 },\n );\n\n try { federationDb.close(); } catch { /* ignore */ }\n\n if (vaultEmbedCount > 0) {\n process.stderr.write(\n `[pai-daemon] Vault embed pass complete: ${vaultEmbedCount} vault chunks embedded\\n`\n );\n }\n } catch (ve) {\n const vmsg = ve instanceof Error ? ve.message : String(ve);\n process.stderr.write(`[pai-daemon] Vault embed error: ${vmsg}\\n`);\n }\n }\n\n const elapsed = Date.now() - t0;\n setLastEmbedTime(Date.now());\n process.stderr.write(\n `[pai-daemon] Embed pass complete: ${count} postgres chunks + ${vaultEmbedCount} vault chunks embedded (${elapsed}ms)\\n`\n );\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n process.stderr.write(`[pai-daemon] Embed error: ${msg}\\n`);\n } finally {\n setEmbedInProgress(false);\n }\n}\n\n/**\n * Start the periodic embed scheduler. Initial run is 60 seconds after startup.\n */\nexport function startEmbedScheduler(): void {\n const intervalMs = daemonConfig.embedIntervalSecs * 1_000;\n\n process.stderr.write(\n `[pai-daemon] Embed scheduler: every ${daemonConfig.embedIntervalSecs}s\\n`\n );\n\n // Startup embed pass — OFF unless explicitly enabled.\n //\n // This used to run unconditionally 60s after every start, ignoring\n // embedIntervalSecs entirely. With a large backlog that makes every restart a\n // CPU storm: the interval was set to a day to stop the machine being bogged\n // down, the daemon was restarted, and it immediately began a 240-second pass\n // anyway. Throttling the recurring timer while leaving this armed means the\n // throttle silently does nothing for anyone who restarts.\n //\n // Measured backlog when this was found: 1,868,098 unembedded chunks at ~5.7\n // chunks/s — about 91 days of work that a restart would resume unbidden.\n if (daemonConfig.embedOnStartup) {\n setTimeout(() => {\n runEmbed().catch((e) => {\n process.stderr.write(`[pai-daemon] Startup embed error: ${e}\\n`);\n });\n }, 60_000);\n } else {\n process.stderr.write(\n \"[pai-daemon] Startup embed pass skipped (embedOnStartup=false).\\n\"\n );\n }\n\n const timer = setInterval(() => {\n runEmbed().catch((e) => {\n process.stderr.write(`[pai-daemon] Scheduled embed error: ${e}\\n`);\n });\n }, intervalMs);\n\n if (timer.unref) timer.unref();\n setEmbedSchedulerTimer(timer);\n}\n\n// ---------------------------------------------------------------------------\n// Registry scan scheduler\n// ---------------------------------------------------------------------------\n\nconst REGISTRY_SCAN_INTERVAL_MS = 30 * 60 * 1000; // 30 minutes\nconst REGISTRY_SCAN_STARTUP_DELAY_MS = 10_000; // 10 seconds after daemon start\n\n/**\n * Start the periodic registry scan scheduler.\n * First scan runs 10 seconds after startup (enough time for IPC to stabilise).\n * Subsequent scans run every 30 minutes.\n * Uses the work-queue so the scan runs on the worker thread without blocking IPC.\n */\nexport function startRegistryScanScheduler(): void {\n process.stderr.write(\n \"[pai-daemon] Registry scan scheduler: every 30min (first in 10s)\\n\"\n );\n\n setTimeout(() => {\n import(\"../../daemon/work-queue-worker.js\")\n .then(({ enqueueRegistryScan }) => enqueueRegistryScan())\n .catch((e) => {\n process.stderr.write(`[pai-daemon] Startup registry scan error: ${e}\\n`);\n });\n }, REGISTRY_SCAN_STARTUP_DELAY_MS);\n\n const timer = setInterval(() => {\n import(\"../../daemon/work-queue-worker.js\")\n .then(({ enqueueRegistryScan }) => enqueueRegistryScan())\n .catch((e) => {\n process.stderr.write(`[pai-daemon] Scheduled registry scan error: ${e}\\n`);\n });\n }, REGISTRY_SCAN_INTERVAL_MS);\n\n if (timer.unref) timer.unref();\n}\n","/**\n * store.ts — PostgreSQL persistence for PAI observations.\n *\n * All functions accept a pg.Pool and are safe to call concurrently.\n * Schema is initialized lazily via ensureObservationTables().\n *\n * Content-hash deduplication: observations with the same hash\n * created within a 30-second window are silently dropped to prevent\n * duplicate entries from rapid repeated tool calls.\n */\n\nimport { sha256 } from '../utils/hash.js';\nimport type { Pool } from 'pg';\nimport type { ClassifiedObservation } from './classifier.js';\n\n// ---------------------------------------------------------------------------\n// Row types\n// ---------------------------------------------------------------------------\n\nexport interface ObservationRow {\n id: number;\n session_id: string;\n project_id: number | null;\n project_slug: string | null;\n type: string;\n title: string;\n narrative: string | null;\n tool_name: string | null;\n tool_input_summary: string | null;\n files_read: string[];\n files_modified: string[];\n concepts: string[];\n content_hash: string | null;\n created_at: Date;\n}\n\nexport interface SessionSummaryRow {\n id: number;\n session_id: string;\n project_id: number | null;\n project_slug: string | null;\n request: string | null;\n investigated: string | null;\n learned: string | null;\n completed: string | null;\n next_steps: string | null;\n observation_count: number;\n created_at: Date;\n}\n\n// ---------------------------------------------------------------------------\n// Input types\n// ---------------------------------------------------------------------------\n\nexport interface StoreObservationInput extends Omit<ClassifiedObservation, 'narrative'> {\n session_id: string;\n project_id?: number | null;\n project_slug?: string | null;\n narrative?: string | null;\n}\n\nexport interface StoreSessionSummaryInput {\n session_id: string;\n project_id?: number | null;\n project_slug?: string | null;\n request?: string | null;\n investigated?: string | null;\n learned?: string | null;\n completed?: string | null;\n next_steps?: string | null;\n observation_count?: number;\n}\n\nexport interface QueryObservationsOptions {\n projectId?: number;\n sessionId?: string;\n type?: string;\n limit?: number;\n offset?: number;\n}\n\n// ---------------------------------------------------------------------------\n// Schema initialisation\n// ---------------------------------------------------------------------------\n\nlet _tablesEnsured = false;\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\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 */\nexport async function ensureObservationTables(pool: Pool): Promise<void> {\n if (_tablesEnsured) return;\n await pool.query(SCHEMA_SQL);\n _tablesEnsured = true;\n}\n\n// ---------------------------------------------------------------------------\n// Content-hash deduplication\n// ---------------------------------------------------------------------------\n\n/**\n * Compute a 16-character hex content hash for deduplication.\n * Hash = SHA256(session_id + tool_name + title).slice(0, 16)\n */\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 // Check for a recent duplicate (30-second window)\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 // Duplicate within dedup window — silently skip\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// Query observations\n// ---------------------------------------------------------------------------\n\n/**\n * Filtered query for observations with optional projectId, sessionId, type,\n * limit, and offset. Returns results ordered by created_at DESC.\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\n/**\n * Most recent observations for a project, ordered by created_at DESC.\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\n/**\n * All observations for a specific session, ordered chronologically.\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\n// ---------------------------------------------------------------------------\n// Session summaries\n// ---------------------------------------------------------------------------\n\n/**\n * Upsert a session summary. Uses ON CONFLICT on session_id so calling this\n * multiple times with updated content is safe.\n */\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\n// ---------------------------------------------------------------------------\n// Skill telemetry — self-educating skill system, Phase 1 (capture)\n// ---------------------------------------------------------------------------\n\nexport interface SkillTelemetryRow {\n id: number;\n scope: string;\n skill_name: string;\n source: string;\n status: string;\n trigger_count: number;\n accept_count: number;\n first_triggered: Date;\n last_triggered: Date;\n context_projects: string[];\n hash: string | null;\n audit_status: string | null;\n last_audited: Date | null;\n}\n\nexport interface RecordSkillInvocationInput {\n skill_name: string;\n /** 'local' | 'skills.sh' | repo slug */\n source?: string;\n /** governance seam — defaults to 'default' */\n scope?: string;\n /** project slug for context_projects rollup */\n project_slug?: string | null;\n}\n\nexport interface QuerySkillTelemetryOptions {\n scope?: string;\n status?: string;\n limit?: number;\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\nexport async 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 * 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/**\n * List skill telemetry rows, most-triggered first.\n */\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/**\n * Most recent session summaries for a project, ordered by created_at DESC.\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 * Tool dispatcher — maps IPC method names to PAI tool functions.\n */\n\nimport {\n toolMemorySearch,\n toolMemoryGet,\n toolProjectInfo,\n toolProjectList,\n toolSessionList,\n toolRegistrySearch,\n toolProjectDetect,\n toolProjectHealth,\n toolProjectTodo,\n toolSessionRoute,\n toolMemoryWakeup,\n toolMemoryTaxonomy,\n toolMemoryFeedback,\n toolMemoryKgSearch,\n} from \"../../mcp/tools.js\";\nimport { detectTopicShift } from \"../../topics/detector.js\";\nimport { registryDb, storageBackend, daemonConfig } from \"./state.js\";\nimport { openFederation } from \"../../memory/db.js\";\nimport type { PostgresBackendWithPool } from \"./types.js\";\n\n/**\n * Dispatch an IPC tool call to the appropriate tool function.\n * Returns the tool result or throws on unknown/failed methods.\n */\nexport async function dispatchTool(\n method: string,\n params: Record<string, unknown>\n): Promise<unknown> {\n // Cast through unknown to satisfy TypeScript's strict overlap check on\n // Record<string, unknown> → specific param types. Runtime validation is\n // the responsibility of each tool function (they surface errors gracefully).\n const p = params as unknown;\n\n switch (method) {\n case \"memory_search\":\n return toolMemorySearch(registryDb, storageBackend, p as Parameters<typeof toolMemorySearch>[2]);\n\n case \"memory_get\":\n return toolMemoryGet(registryDb, p as Parameters<typeof toolMemoryGet>[1]);\n\n case \"project_info\":\n return toolProjectInfo(registryDb, p as Parameters<typeof toolProjectInfo>[1]);\n\n case \"project_list\":\n return toolProjectList(registryDb, p as Parameters<typeof toolProjectList>[1]);\n\n case \"session_list\":\n return toolSessionList(registryDb, p as Parameters<typeof toolSessionList>[1]);\n\n case \"registry_search\":\n return toolRegistrySearch(registryDb, p as Parameters<typeof toolRegistrySearch>[1]);\n\n case \"project_detect\":\n return toolProjectDetect(registryDb, p as Parameters<typeof toolProjectDetect>[1]);\n\n case \"project_health\":\n return toolProjectHealth(registryDb, p as Parameters<typeof toolProjectHealth>[1]);\n\n case \"project_todo\":\n return toolProjectTodo(registryDb, p as Parameters<typeof toolProjectTodo>[1]);\n\n case \"memory_wakeup\":\n return toolMemoryWakeup(registryDb, p as Parameters<typeof toolMemoryWakeup>[1]);\n\n case \"memory_taxonomy\":\n return toolMemoryTaxonomy(registryDb, storageBackend, p as Parameters<typeof toolMemoryTaxonomy>[2]);\n\n case \"topic_check\":\n return detectTopicShift(\n registryDb,\n storageBackend,\n p as Parameters<typeof detectTopicShift>[2]\n );\n\n case \"session_auto_route\":\n return toolSessionRoute(\n registryDb,\n storageBackend,\n p as Parameters<typeof toolSessionRoute>[2]\n );\n\n case \"zettel_explore\":\n case \"zettel_health\":\n case \"zettel_surprise\":\n case \"zettel_suggest\":\n case \"zettel_converse\":\n case \"zettel_themes\":\n case \"zettel_god_notes\":\n case \"zettel_communities\": {\n const { toolZettelExplore, toolZettelHealth, toolZettelSurprise, toolZettelSuggest, toolZettelConverse, toolZettelThemes, toolZettelGodNotes, toolZettelCommunities } = await import(\"../../mcp/tools.js\");\n\n switch (method) {\n case \"zettel_explore\": return toolZettelExplore(storageBackend, p as Parameters<typeof toolZettelExplore>[1]);\n case \"zettel_health\": return toolZettelHealth(storageBackend, p as Parameters<typeof toolZettelHealth>[1]);\n case \"zettel_surprise\": return toolZettelSurprise(storageBackend, p as Parameters<typeof toolZettelSurprise>[1]);\n case \"zettel_suggest\": return toolZettelSuggest(storageBackend, p as Parameters<typeof toolZettelSuggest>[1]);\n case \"zettel_converse\": return toolZettelConverse(storageBackend, p as Parameters<typeof toolZettelConverse>[1]);\n case \"zettel_themes\": return toolZettelThemes(storageBackend, p as Parameters<typeof toolZettelThemes>[1]);\n case \"zettel_god_notes\": return toolZettelGodNotes(storageBackend, p as Parameters<typeof toolZettelGodNotes>[1]);\n case \"zettel_communities\": return toolZettelCommunities(storageBackend, p as Parameters<typeof toolZettelCommunities>[1]);\n }\n break;\n }\n\n case \"graph_clusters\": {\n const { handleGraphClusters } = await import(\"../../graph/clusters.js\");\n const pgPool = (storageBackend as PostgresBackendWithPool).getPool?.() ?? null;\n return handleGraphClusters(pgPool, storageBackend, p as Parameters<typeof handleGraphClusters>[2]);\n }\n\n case \"graph_neighborhood\": {\n const { handleGraphNeighborhood } = await import(\"../../graph/neighborhood.js\");\n const pgPool = (storageBackend as PostgresBackendWithPool).getPool?.() ?? null;\n return handleGraphNeighborhood(pgPool, storageBackend, p as Parameters<typeof handleGraphNeighborhood>[2]);\n }\n\n case \"graph_note_context\": {\n const { handleGraphNoteContext } = await import(\"../../graph/note-context.js\");\n const pgPool = (storageBackend as PostgresBackendWithPool).getPool?.() ?? null;\n return handleGraphNoteContext(pgPool, storageBackend, p as Parameters<typeof handleGraphNoteContext>[2]);\n }\n\n case \"graph_trace\": {\n const { handleGraphTrace } = await import(\"../../graph/trace.js\");\n return handleGraphTrace(storageBackend, p as Parameters<typeof handleGraphTrace>[1]);\n }\n\n case \"graph_latent_ideas\": {\n const { handleGraphLatentIdeas } = await import(\"../../graph/latent-ideas.js\");\n return handleGraphLatentIdeas(storageBackend, p as Parameters<typeof handleGraphLatentIdeas>[1]);\n }\n\n case \"idea_materialize\": {\n const { handleIdeaMaterialize } = await import(\"../../graph/latent-ideas.js\");\n if (!daemonConfig.vaultPath) {\n throw new Error(\"idea_materialize requires vaultPath to be configured in the daemon config\");\n }\n return handleIdeaMaterialize(\n p as Parameters<typeof handleIdeaMaterialize>[0],\n daemonConfig.vaultPath\n );\n }\n\n case \"kg_add\":\n case \"kg_query\":\n case \"kg_invalidate\":\n case \"kg_contradictions\": {\n const { toolKgAdd, toolKgQuery, toolKgInvalidate, toolKgContradictions } = await import(\"../../mcp/tools.js\");\n const pgPool = (storageBackend as PostgresBackendWithPool).getPool?.() ?? null;\n if (!pgPool) {\n throw new Error(`${method} requires a Postgres storage backend`);\n }\n switch (method) {\n case \"kg_add\": return toolKgAdd(pgPool, p as Parameters<typeof toolKgAdd>[1]);\n case \"kg_query\": return toolKgQuery(pgPool, p as Parameters<typeof toolKgQuery>[1]);\n case \"kg_invalidate\": return toolKgInvalidate(pgPool, p as Parameters<typeof toolKgInvalidate>[1]);\n case \"kg_contradictions\": return toolKgContradictions(pgPool, p as Parameters<typeof toolKgContradictions>[1]);\n }\n break;\n }\n\n case \"memory_tunnels\": {\n const { toolMemoryTunnels } = await import(\"../../mcp/tools.js\");\n return toolMemoryTunnels(registryDb, storageBackend, p as Parameters<typeof toolMemoryTunnels>[2]);\n }\n\n case \"memory_feedback\": {\n // MR2: feedback weight loop — uses federation.db (SQLite) directly\n const federationDb = openFederation();\n try {\n return await toolMemoryFeedback(federationDb, p as Parameters<typeof toolMemoryFeedback>[1]);\n } finally {\n federationDb.close();\n }\n }\n\n case \"memory_kg_search\": {\n // MR1: graph-completion retrieval — federation.db (SQLite) + Postgres pool\n const federationDb = openFederation();\n const pgPool = (storageBackend as PostgresBackendWithPool).getPool?.() ?? null;\n if (!pgPool) {\n federationDb.close();\n throw new Error(\"memory_kg_search requires a Postgres storage backend for KG triple expansion\");\n }\n try {\n return await toolMemoryKgSearch(federationDb, pgPool, p as Parameters<typeof toolMemoryKgSearch>[2]);\n } finally {\n federationDb.close();\n }\n }\n\n default:\n throw new Error(`Unknown method: ${method}`);\n }\n}\n","/**\n * IPC request handler — processes all inbound IPC methods and sends responses.\n */\n\nimport type { Socket } from \"node:net\";\nimport type { IpcRequest, IpcResponse, PostgresBackendWithPool } from \"./types.js\";\nimport type { NotificationMode } from \"../../notifications/types.js\";\nimport {\n patchNotificationConfig,\n} from \"../../notifications/config.js\";\nimport { routeNotification } from \"../../notifications/router.js\";\nimport {\n ensureObservationTables,\n storeObservation,\n queryObservations,\n queryRecentObservations,\n storeSessionSummary,\n recordSkillInvocation,\n querySkillTelemetry,\n} from \"../../observations/store.js\";\nimport {\n registryDb,\n storageBackend,\n daemonConfig,\n startTime,\n indexInProgress,\n lastIndexTime,\n embedInProgress,\n lastEmbedTime,\n vaultIndexInProgress,\n lastVaultIndexTime,\n notificationConfig,\n setNotificationConfig,\n} from \"./state.js\";\nimport { runIndex, runVaultIndex } from \"./scheduler.js\";\nimport { dispatchTool } from \"./dispatcher.js\";\nimport { enqueue, getStats as getQueueStats } from \"../../daemon/work-queue.js\";\nimport { notifyNewWork } from \"../../daemon/work-queue-worker.js\";\nimport { getBackendOutage } from \"../../storage/outage.js\";\n\n// ---------------------------------------------------------------------------\n// Helper\n// ---------------------------------------------------------------------------\n\nexport function sendResponse(socket: Socket, response: IpcResponse): void {\n try {\n socket.write(JSON.stringify(response) + \"\\n\");\n } catch {\n // Socket may already be closed\n }\n}\n\n// ---------------------------------------------------------------------------\n// Main handler\n// ---------------------------------------------------------------------------\n\n/**\n * Handle a single IPC request.\n */\nexport async function handleRequest(\n request: IpcRequest,\n socket: Socket\n): Promise<void> {\n const { id, method, params } = request;\n\n // Special: status\n if (method === \"status\") {\n const dbStats = await (async () => {\n try {\n if (!storageBackend) return null;\n const fedStats = await storageBackend.getStats();\n const projects = (\n registryDb\n .prepare(\"SELECT COUNT(*) AS n FROM projects\")\n .get() as { n: number }\n ).n;\n return { files: fedStats.files, chunks: fedStats.chunks, projects };\n } catch {\n return null;\n }\n })();\n\n sendResponse(socket, {\n id,\n ok: true,\n result: {\n uptime: Math.floor((Date.now() - startTime) / 1000),\n indexInProgress,\n lastIndexTime: lastIndexTime ? new Date(lastIndexTime).toISOString() : null,\n indexIntervalSecs: daemonConfig.indexIntervalSecs,\n embedInProgress,\n lastEmbedTime: lastEmbedTime ? new Date(lastEmbedTime).toISOString() : null,\n embedIntervalSecs: daemonConfig.embedIntervalSecs,\n socketPath: daemonConfig.socketPath,\n storageBackend: storageBackend?.backendType ?? \"initializing\",\n db: dbStats,\n vaultIndexInProgress,\n lastVaultIndexTime: lastVaultIndexTime ? new Date(lastVaultIndexTime).toISOString() : null,\n vaultPath: daemonConfig.vaultPath ?? null,\n workQueue: getQueueStats(),\n // Null when the backend is answering. Present, this is the reason\n // everything else in this payload is stalled — and the reason status\n // used to report \"idle\" for two days while nothing progressed.\n backendOutage: getBackendOutage(),\n },\n });\n socket.end();\n return;\n }\n\n // Special: index_now — trigger immediate index (non-blocking response)\n if (method === \"index_now\") {\n runIndex().catch((e) => {\n process.stderr.write(`[pai-daemon] index_now error: ${e}\\n`);\n });\n sendResponse(socket, { id, ok: true, result: { triggered: true } });\n socket.end();\n return;\n }\n\n // Special: vault_index_now — trigger immediate vault index (non-blocking response)\n if (method === \"vault_index_now\") {\n runVaultIndex().catch((e) => {\n process.stderr.write(`[pai-daemon] vault_index_now error: ${e}\\n`);\n });\n sendResponse(socket, { id, ok: true, result: { triggered: true } });\n socket.end();\n return;\n }\n\n // Special: notification_get_config\n if (method === \"notification_get_config\") {\n sendResponse(socket, {\n id,\n ok: true,\n result: {\n config: notificationConfig,\n activeChannels: Object.entries(notificationConfig.channels)\n .filter(([ch, cfg]) => ch !== \"voice\" && (cfg as { enabled: boolean }).enabled)\n .map(([ch]) => ch),\n },\n });\n socket.end();\n return;\n }\n\n // Special: notification_set_config\n if (method === \"notification_set_config\") {\n try {\n const p = params as {\n mode?: NotificationMode;\n channels?: Record<string, unknown>;\n routing?: Record<string, unknown>;\n };\n const updated = patchNotificationConfig({\n mode: p.mode,\n channels: p.channels as Parameters<typeof patchNotificationConfig>[0][\"channels\"],\n routing: p.routing as Parameters<typeof patchNotificationConfig>[0][\"routing\"],\n });\n setNotificationConfig(updated);\n sendResponse(socket, { id, ok: true, result: { config: updated } });\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n sendResponse(socket, { id, ok: false, error: msg });\n }\n socket.end();\n return;\n }\n\n // Special: notification_send\n if (method === \"notification_send\") {\n const p = params as {\n event?: string;\n message?: string;\n title?: string;\n };\n\n if (!p.message) {\n sendResponse(socket, { id, ok: false, error: \"notification_send: message is required\" });\n socket.end();\n return;\n }\n\n const event = (p.event as Parameters<typeof routeNotification>[0][\"event\"]) ?? \"info\";\n\n routeNotification(\n { event, message: p.message, title: p.title },\n notificationConfig\n ).then((result) => {\n sendResponse(socket, { id, ok: true, result });\n socket.end();\n }).catch((e) => {\n const msg = e instanceof Error ? e.message : String(e);\n sendResponse(socket, { id, ok: false, error: msg });\n socket.end();\n });\n return;\n }\n\n // ---- Observation methods (Postgres only) --------------------------------\n\n if (method === \"observation_store\") {\n const pool = (storageBackend as PostgresBackendWithPool).getPool?.();\n if (!pool) {\n sendResponse(socket, { id, ok: false, error: \"Observations require Postgres backend\" });\n socket.end();\n return;\n }\n try {\n const p = params as {\n session_id: string;\n type: string;\n title: string;\n narrative?: string;\n tool_name: string;\n tool_input_summary?: string;\n files_read?: string[];\n files_modified?: string[];\n concepts?: string[];\n content_hash?: string;\n cwd?: string;\n };\n\n let project_id: number | null = null;\n let project_slug: string | null = null;\n if (p.cwd) {\n const row = registryDb.prepare(\n \"SELECT id, slug FROM projects WHERE status = 'active' AND ? LIKE root_path || '%' ORDER BY length(root_path) DESC LIMIT 1\"\n ).get(p.cwd) as { id: number; slug: string } | undefined;\n if (row) {\n project_id = row.id;\n project_slug = row.slug;\n }\n }\n\n await ensureObservationTables(pool);\n const insertedId = await storeObservation(pool, {\n session_id: p.session_id,\n project_id,\n project_slug,\n type: p.type as \"decision\" | \"bugfix\" | \"feature\" | \"refactor\" | \"discovery\" | \"change\",\n title: p.title,\n narrative: p.narrative ?? null,\n tool_name: p.tool_name,\n tool_input_summary: p.tool_input_summary ?? null,\n files_read: p.files_read ?? [],\n files_modified: p.files_modified ?? [],\n concepts: p.concepts ?? [],\n });\n\n sendResponse(socket, { id, ok: true, result: { ok: true, id: insertedId } });\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n sendResponse(socket, { id, ok: false, error: msg });\n }\n socket.end();\n return;\n }\n\n // ---- Skill telemetry (Postgres only) ------------------------------------\n\n if (method === \"skill_telemetry_record\") {\n const pool = (storageBackend as PostgresBackendWithPool).getPool?.();\n if (!pool) {\n sendResponse(socket, { id, ok: false, error: \"Skill telemetry requires Postgres backend\" });\n socket.end();\n return;\n }\n try {\n const p = params as {\n skill_name: string;\n source?: string;\n scope?: string;\n cwd?: string;\n };\n\n let project_slug: string | null = null;\n if (p.cwd) {\n const row = registryDb.prepare(\n \"SELECT slug FROM projects WHERE status = 'active' AND ? LIKE root_path || '%' ORDER BY length(root_path) DESC LIMIT 1\"\n ).get(p.cwd) as { slug: string } | undefined;\n if (row) project_slug = row.slug;\n }\n\n await recordSkillInvocation(pool, {\n skill_name: p.skill_name,\n source: p.source,\n scope: p.scope,\n project_slug,\n });\n\n sendResponse(socket, { id, ok: true, result: { ok: true } });\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n sendResponse(socket, { id, ok: false, error: msg });\n }\n socket.end();\n return;\n }\n\n if (method === \"skill_telemetry_query\") {\n const pool = (storageBackend as PostgresBackendWithPool).getPool?.();\n if (!pool) {\n sendResponse(socket, { id, ok: false, error: \"Skill telemetry requires Postgres backend\" });\n socket.end();\n return;\n }\n try {\n const p = params as { scope?: string; status?: string; limit?: number };\n const rows = await querySkillTelemetry(pool, {\n scope: p.scope,\n status: p.status,\n limit: p.limit,\n });\n sendResponse(socket, { id, ok: true, result: rows });\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n sendResponse(socket, { id, ok: false, error: msg });\n }\n socket.end();\n return;\n }\n\n if (method === \"observation_query\") {\n const pool = (storageBackend as PostgresBackendWithPool).getPool?.();\n if (!pool) {\n sendResponse(socket, { id, ok: false, error: \"Observations require Postgres backend\" });\n socket.end();\n return;\n }\n try {\n const p = params as {\n project_id?: number;\n session_id?: string;\n type?: string;\n limit?: number;\n offset?: number;\n };\n\n const rows = await queryObservations(pool, {\n projectId: p.project_id,\n sessionId: p.session_id,\n type: p.type,\n limit: p.limit,\n offset: p.offset,\n });\n sendResponse(socket, { id, ok: true, result: rows });\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n sendResponse(socket, { id, ok: false, error: msg });\n }\n socket.end();\n return;\n }\n\n if (method === \"observation_recent\") {\n const pool = (storageBackend as PostgresBackendWithPool).getPool?.();\n if (!pool) {\n sendResponse(socket, { id, ok: false, error: \"Observations require Postgres backend\" });\n socket.end();\n return;\n }\n try {\n const p = params as { project_id?: number; cwd?: string; limit?: number };\n const limit = p.limit ?? 20;\n\n let resolvedProjectId = p.project_id;\n let resolvedProjectSlug: string | undefined;\n if (resolvedProjectId === undefined && p.cwd) {\n const row = registryDb.prepare(\n \"SELECT id, slug FROM projects WHERE status = 'active' AND ? LIKE root_path || '%' ORDER BY length(root_path) DESC LIMIT 1\"\n ).get(p.cwd) as { id: number; slug: string } | undefined;\n if (row) {\n resolvedProjectId = row.id;\n resolvedProjectSlug = row.slug;\n }\n }\n\n let rows;\n if (resolvedProjectId !== undefined) {\n rows = await queryRecentObservations(pool, resolvedProjectId, limit);\n } else {\n rows = await queryObservations(pool, { limit });\n }\n sendResponse(socket, { id, ok: true, result: { rows, project_slug: resolvedProjectSlug } });\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n sendResponse(socket, { id, ok: false, error: msg });\n }\n socket.end();\n return;\n }\n\n // observation_list — alias for observation_query with project slug resolution\n if (method === \"observation_list\") {\n const pool = (storageBackend as PostgresBackendWithPool).getPool?.();\n if (!pool) {\n sendResponse(socket, { id, ok: false, error: \"Observations require Postgres backend\" });\n socket.end();\n return;\n }\n try {\n const p = params as {\n project_slug?: string;\n session_id?: string;\n type?: string;\n limit?: number;\n offset?: number;\n };\n\n let projectId: number | undefined;\n if (p.project_slug) {\n const row = registryDb.prepare(\n \"SELECT id FROM projects WHERE slug = ?\"\n ).get(p.project_slug) as { id: number } | undefined;\n projectId = row?.id;\n }\n\n const rows = await queryObservations(pool, {\n projectId,\n sessionId: p.session_id,\n type: p.type,\n limit: p.limit ?? 20,\n offset: p.offset ?? 0,\n });\n sendResponse(socket, { id, ok: true, result: rows });\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n sendResponse(socket, { id, ok: false, error: msg });\n }\n socket.end();\n return;\n }\n\n // observation_stats — aggregate statistics\n if (method === \"observation_stats\") {\n const pool = (storageBackend as PostgresBackendWithPool).getPool?.();\n if (!pool) {\n sendResponse(socket, { id, ok: false, error: \"Observations require Postgres backend\" });\n socket.end();\n return;\n }\n try {\n await ensureObservationTables(pool);\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 sendResponse(socket, {\n id,\n ok: true,\n result: {\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 } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n sendResponse(socket, { id, ok: false, error: msg });\n }\n socket.end();\n return;\n }\n\n if (method === \"session_summary_store\") {\n const pool = (storageBackend as PostgresBackendWithPool).getPool?.();\n if (!pool) {\n sendResponse(socket, { id, ok: false, error: \"Session summaries require Postgres backend\" });\n socket.end();\n return;\n }\n try {\n const p = params as {\n session_id: string;\n project_id?: number;\n project_slug?: string;\n cwd?: string;\n request?: string;\n investigated?: string;\n learned?: string;\n completed?: string;\n next_steps?: string;\n observation_count?: number;\n };\n\n let resolvedProjectId = p.project_id ?? null;\n let resolvedProjectSlug = p.project_slug ?? null;\n if (resolvedProjectId === null && p.cwd) {\n const row = registryDb.prepare(\n \"SELECT id, slug FROM projects WHERE status = 'active' AND ? LIKE root_path || '%' ORDER BY length(root_path) DESC LIMIT 1\"\n ).get(p.cwd) as { id: number; slug: string } | undefined;\n if (row) {\n resolvedProjectId = row.id;\n resolvedProjectSlug = row.slug;\n }\n }\n\n await storeSessionSummary(pool, {\n session_id: p.session_id,\n project_id: resolvedProjectId,\n project_slug: resolvedProjectSlug,\n request: p.request ?? null,\n investigated: p.investigated ?? null,\n learned: p.learned ?? null,\n completed: p.completed ?? null,\n next_steps: p.next_steps ?? null,\n observation_count: p.observation_count ?? 0,\n });\n\n sendResponse(socket, { id, ok: true, result: { ok: true } });\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n sendResponse(socket, { id, ok: false, error: msg });\n }\n socket.end();\n return;\n }\n\n // work_queue_enqueue — hooks push work items here instead of doing heavy work inline\n if (method === \"work_queue_enqueue\") {\n try {\n const p = params as {\n type?: string;\n priority?: number;\n payload?: Record<string, unknown>;\n maxAttempts?: number;\n };\n\n if (!p.type) {\n sendResponse(socket, { id, ok: false, error: \"work_queue_enqueue: type is required\" });\n socket.end();\n return;\n }\n\n const item = enqueue({\n type: p.type as import(\"../../daemon/work-queue.js\").WorkItemType,\n priority: p.priority,\n payload: p.payload ?? {},\n maxAttempts: p.maxAttempts,\n });\n\n notifyNewWork();\n\n sendResponse(socket, { id, ok: true, result: { id: item.id, status: item.status } });\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n sendResponse(socket, { id, ok: false, error: msg });\n }\n socket.end();\n return;\n }\n\n // work_queue_stats — return current queue statistics\n if (method === \"work_queue_stats\") {\n sendResponse(socket, { id, ok: true, result: getQueueStats() });\n socket.end();\n return;\n }\n\n // All other methods: PAI tool dispatch\n try {\n const result = await dispatchTool(method, params);\n sendResponse(socket, { id, ok: true, result });\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n sendResponse(socket, { id, ok: false, error: msg });\n }\n socket.end();\n}\n","/**\n * IPC server and daemon entry point.\n * Owns: isSocketLive, startIpcServer, serve (exported).\n */\n\nimport { existsSync, unlinkSync } from \"node:fs\";\nimport { createServer, connect, Socket, Server } from \"node:net\";\nimport { setPriority } from \"node:os\";\nimport { openRegistry } from \"../../registry/db.js\";\nimport { createStorageBackend } from \"../../storage/factory.js\";\nimport { configureEmbeddingModel } from \"../../memory/embeddings.js\";\nimport { loadNotificationConfig } from \"../../notifications/config.js\";\nimport type { PaiDaemonConfig } from \"../config.js\";\nimport type { IpcRequest } from \"./types.js\";\nimport {\n setRegistryDb,\n setStorageBackend,\n setDaemonConfig,\n setStartTime,\n setNotificationConfig,\n setShutdownRequested,\n indexInProgress,\n embedInProgress,\n indexSchedulerTimer,\n embedSchedulerTimer,\n storageBackend,\n} from \"./state.js\";\nimport { startIndexScheduler, startEmbedScheduler, startRegistryScanScheduler } from \"./scheduler.js\";\nimport { handleRequest, sendResponse } from \"./handler.js\";\nimport { loadQueue } from \"../../daemon/work-queue.js\";\nimport { startWorker, stopWorker } from \"../../daemon/work-queue-worker.js\";\n\n// ---------------------------------------------------------------------------\n// IPC helpers\n// ---------------------------------------------------------------------------\n\n/**\n * Check whether an existing socket file is actually being served by a live process.\n */\nfunction isSocketLive(path: string): Promise<boolean> {\n return new Promise((resolve) => {\n const client = connect(path);\n const timer = setTimeout(() => { client.destroy(); resolve(false); }, 500);\n client.on(\"connect\", () => { clearTimeout(timer); client.end(); resolve(true); });\n client.on(\"error\", () => { clearTimeout(timer); resolve(false); });\n });\n}\n\n/**\n * Start the Unix Domain Socket IPC server.\n */\nasync function startIpcServer(socketPath: string): Promise<Server> {\n if (existsSync(socketPath)) {\n const live = await isSocketLive(socketPath);\n if (live) {\n throw new Error(\"Another daemon is already running — socket is live. Aborting startup.\");\n }\n try {\n unlinkSync(socketPath);\n process.stderr.write(\"[pai-daemon] Removed stale socket file.\\n\");\n } catch {\n // If we can't remove it, bind will fail with a clear error\n }\n }\n\n const server = createServer((socket: Socket) => {\n let buffer = \"\";\n\n socket.on(\"data\", (chunk: Buffer) => {\n buffer += chunk.toString();\n let nl: number;\n while ((nl = buffer.indexOf(\"\\n\")) !== -1) {\n const line = buffer.slice(0, nl);\n buffer = buffer.slice(nl + 1);\n\n if (line.trim() === \"\") continue;\n\n let request: IpcRequest;\n try {\n request = JSON.parse(line) as IpcRequest;\n } catch {\n sendResponse(socket, { id: \"?\", ok: false, error: \"Invalid JSON\" });\n socket.destroy();\n return;\n }\n\n handleRequest(request, socket).catch((e: unknown) => {\n const msg = e instanceof Error ? e.message : String(e);\n sendResponse(socket, { id: request.id, ok: false, error: msg });\n socket.destroy();\n });\n }\n });\n\n socket.on(\"error\", () => {\n // Client disconnected — nothing to do\n });\n });\n\n server.on(\"error\", (e) => {\n process.stderr.write(`[pai-daemon] IPC server error: ${e}\\n`);\n });\n\n server.listen(socketPath, () => {\n process.stderr.write(\n `[pai-daemon] IPC server listening on ${socketPath}\\n`\n );\n });\n\n return server;\n}\n\n// ---------------------------------------------------------------------------\n// Main daemon entry point\n// ---------------------------------------------------------------------------\n\nexport async function serve(config: PaiDaemonConfig): Promise<void> {\n setDaemonConfig(config);\n setStartTime(Date.now());\n\n setNotificationConfig(loadNotificationConfig());\n\n process.stderr.write(\"[pai-daemon] Starting daemon...\\n\");\n process.stderr.write(`[pai-daemon] Socket: ${config.socketPath}\\n`);\n process.stderr.write(`[pai-daemon] Storage backend: ${config.storageBackend}\\n`);\n const { notificationConfig } = await import(\"./state.js\");\n process.stderr.write(\n `[pai-daemon] Notification mode: ${notificationConfig.mode}\\n`\n );\n\n // Lower scheduling priority so the daemon yields CPU to interactive sessions\n try { setPriority(process.pid, 10); } catch { /* non-fatal */ }\n\n configureEmbeddingModel(config.embeddingModel);\n\n try {\n setRegistryDb(openRegistry());\n process.stderr.write(\"[pai-daemon] Registry database opened.\\n\");\n } catch (e) {\n const msg = e instanceof Error ? e.message : String(e);\n process.stderr.write(`[pai-daemon] Fatal: Could not open registry: ${msg}\\n`);\n process.exit(1);\n }\n\n // Start the IPC server immediately so `pai daemon status` answers even while\n // we are still waiting for the federation backend (e.g. Postgres coming up\n // after a reboot). The status handler tolerates a not-yet-ready backend.\n const server = await startIpcServer(config.socketPath);\n\n // Registry scan scheduler only needs the registry DB — safe to start now.\n startRegistryScanScheduler();\n\n // Connect the federation backend in the background. When storageBackend is\n // \"postgres\" this retries forever (never silently falls back to SQLite), so\n // the boot race with Docker/Postgres resolves itself once Postgres is up.\n // Only then do we start the federation-dependent schedulers and work queue —\n // this also keeps the first index run from firing against an unready backend.\n void createStorageBackend(config, { waitForPostgres: true })\n .then((backend) => {\n setStorageBackend(backend);\n process.stderr.write(\n `[pai-daemon] Federation backend: ${backend.backendType}\\n`\n );\n\n startIndexScheduler();\n\n if (backend.backendType === \"postgres\") {\n startEmbedScheduler();\n } else {\n process.stderr.write(\n \"[pai-daemon] Embed scheduler: disabled (SQLite backend)\\n\"\n );\n }\n\n // Work queue — load persisted items and start worker loop\n loadQueue();\n startWorker();\n })\n .catch((e) => {\n const msg = e instanceof Error ? e.message : String(e);\n process.stderr.write(\n `[pai-daemon] Fatal: Could not open federation storage: ${msg}\\n`\n );\n process.exit(1);\n });\n\n const shutdown = async (signal: string): Promise<void> => {\n process.stderr.write(`\\n[pai-daemon] ${signal} received. Stopping.\\n`);\n\n setShutdownRequested(true);\n\n if (indexSchedulerTimer) clearInterval(indexSchedulerTimer);\n if (embedSchedulerTimer) clearInterval(embedSchedulerTimer);\n\n stopWorker();\n\n server.close();\n\n const SHUTDOWN_TIMEOUT_MS = 10_000;\n const POLL_INTERVAL_MS = 100;\n const deadline = Date.now() + SHUTDOWN_TIMEOUT_MS;\n\n if (indexInProgress || embedInProgress) {\n process.stderr.write(\n `[pai-daemon] Waiting for in-progress operations to finish ` +\n `(index=${indexInProgress}, embed=${embedInProgress})...\\n`\n );\n\n while ((indexInProgress || embedInProgress) && Date.now() < deadline) {\n await new Promise((resolve) => setTimeout(resolve, POLL_INTERVAL_MS));\n }\n\n if (indexInProgress || embedInProgress) {\n process.stderr.write(\"[pai-daemon] Shutdown timeout reached — forcing exit.\\n\");\n } else {\n process.stderr.write(\"[pai-daemon] In-progress operations finished.\\n\");\n }\n }\n\n try {\n await storageBackend.close();\n } catch {\n // ignore\n }\n\n try {\n unlinkSync(config.socketPath);\n } catch {\n // ignore\n }\n\n process.exit(0);\n };\n\n process.on(\"SIGINT\", () => { shutdown(\"SIGINT\").catch(() => process.exit(0)); });\n process.on(\"SIGTERM\", () => { shutdown(\"SIGTERM\").catch(() => process.exit(0)); });\n\n // Keep process alive\n await new Promise(() => {});\n}\n","/**\n * Shim — re-exports serve() from daemon/ directory so existing importers\n * continue to work without modification. See daemon/index.ts.\n */\nexport { serve } from \"./daemon/index.js\";\n"],"mappings":"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"}
@@ -1 +0,0 @@
1
- {"version":3,"file":"postgres-CYQuLAfD.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"],"sources":["../src/storage/postgres/helpers.ts","../src/storage/postgres/search.ts","../src/storage/postgres/vault.ts","../src/storage/postgres/backend.ts"],"sourcesContent":["/**\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 for (let i = 0; i < links.length; i += 500) {\n const batch = links.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 * 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 { join, dirname } from \"node:path\";\nimport { fileURLToPath } from \"node:url\";\nimport type {\n StorageBackend, ChunkRow, FileRow, FederationStats,\n VaultFileRow, VaultAliasRow, VaultLinkRow, VaultHealthRow, VaultNameEntry,\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\";\n\nconst { Pool: PgPool } = pg;\n\nexport class PostgresBackend implements StorageBackend {\n readonly backendType = \"postgres\" as const;\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 __dirname = dirname(fileURLToPath(import.meta.url));\n const initSqlPath = join(__dirname, \"../../docker/init.sql\");\n let initSql: string;\n try {\n initSql = readFileSync(initSqlPath, \"utf-8\");\n } catch {\n const altPath = join(__dirname, \"../docker/init.sql\");\n initSql = readFileSync(altPath, \"utf-8\");\n }\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\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 });\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 /**\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(projectId?: number, limit?: number): Promise<Array<{ id: string; text: string; project_id: number; path: string }>> {\n const params: Array<number> = [];\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 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"],"mappings":"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"}