@tekmidian/pai 0.65.1 → 0.66.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (115) hide show
  1. package/dist/{async-CNn36gm4.mjs → async-C2Bm_Lal.mjs} +2 -2
  2. package/dist/{async-CNn36gm4.mjs.map → async-C2Bm_Lal.mjs.map} +1 -1
  3. package/dist/{auto-route-sLMU-NnM.mjs → auto-route-o0BOsXn7.mjs} +2 -2
  4. package/dist/{auto-route-sLMU-NnM.mjs.map → auto-route-o0BOsXn7.mjs.map} +1 -1
  5. package/dist/{chain-CLb6hbFS.mjs → chain-D4PuQ2c-.mjs} +4 -4
  6. package/dist/{chain-CLb6hbFS.mjs.map → chain-D4PuQ2c-.mjs.map} +1 -1
  7. package/dist/cli/index.mjs +14 -12
  8. package/dist/cli/index.mjs.map +1 -1
  9. package/dist/cli/program.mjs +12 -12
  10. package/dist/{clusters-PgIUYT_v.mjs → clusters-BCtD3fbe.mjs} +1 -1
  11. package/dist/{clusters-PgIUYT_v.mjs.map → clusters-BCtD3fbe.mjs.map} +1 -1
  12. package/dist/{context-handover-cache-mGxq2-8f.mjs → context-handover-cache-pkHzmL3c.mjs} +4 -4
  13. package/dist/{context-handover-cache-mGxq2-8f.mjs.map → context-handover-cache-pkHzmL3c.mjs.map} +1 -1
  14. package/dist/daemon/index.mjs +14 -12
  15. package/dist/daemon/index.mjs.map +1 -1
  16. package/dist/{daemon-DqCB3fO-.mjs → daemon-BEoVgVfJ.mjs} +25 -25
  17. package/dist/{daemon-DqCB3fO-.mjs.map → daemon-BEoVgVfJ.mjs.map} +1 -1
  18. package/dist/daemon-DPAYU9hp.mjs +19 -0
  19. package/dist/daemon-mcp/index.mjs +9 -7
  20. package/dist/daemon-mcp/index.mjs.map +1 -1
  21. package/dist/detector-BEPFsINR.mjs +3 -0
  22. package/dist/{detector-CIHEXKsV.mjs → detector-BRTtAWrM.mjs} +1 -1
  23. package/dist/{detector-CIHEXKsV.mjs.map → detector-BRTtAWrM.mjs.map} +1 -1
  24. package/dist/{embeddings-BbNVXa_0.mjs → embeddings-Bx3q0QOY.mjs} +1 -1
  25. package/dist/{embeddings-BbNVXa_0.mjs.map → embeddings-Bx3q0QOY.mjs.map} +1 -1
  26. package/dist/{embeddings-CDfM63uC.mjs → embeddings-CcscYWwk.mjs} +1 -1
  27. package/dist/env-JNEIrQWg.mjs +14 -0
  28. package/dist/env-JNEIrQWg.mjs.map +1 -0
  29. package/dist/{factory-y36qGegI.mjs → factory-CNpPQQ-5.mjs} +7 -7
  30. package/dist/{factory-y36qGegI.mjs.map → factory-CNpPQQ-5.mjs.map} +1 -1
  31. package/dist/{factory-CaswxuJ0.mjs → factory-DY7x73mE.mjs} +1 -1
  32. package/dist/{fallback-CupzGkuJ.mjs → fallback-B5svk75f.mjs} +15 -7
  33. package/dist/fallback-B5svk75f.mjs.map +1 -0
  34. package/dist/hooks/block-sleep-poll.mjs.map +2 -2
  35. package/dist/hooks/load-project-context.mjs +16 -0
  36. package/dist/hooks/load-project-context.mjs.map +2 -2
  37. package/dist/hooks/route-agents-to-worker.mjs +16 -0
  38. package/dist/hooks/route-agents-to-worker.mjs.map +2 -2
  39. package/dist/hooks/security-validator.mjs +16 -0
  40. package/dist/hooks/security-validator.mjs.map +2 -2
  41. package/dist/hooks/worker-guard.mjs +10 -0
  42. package/dist/hooks/worker-guard.mjs.map +3 -3
  43. package/dist/hooks/worker-proxy.mjs +16 -0
  44. package/dist/hooks/worker-proxy.mjs.map +2 -2
  45. package/dist/hooks/worker-status-line.mjs +16 -0
  46. package/dist/hooks/worker-status-line.mjs.map +2 -2
  47. package/dist/hooks/worker-supervision.mjs +16 -0
  48. package/dist/hooks/worker-supervision.mjs.map +2 -2
  49. package/dist/{indexer-backend-Cnc7Tf5C.mjs → indexer-backend-DFF2FrYx.mjs} +1 -1
  50. package/dist/{kg-entity-COTUj1ZC.mjs → kg-entity-DCOcsVFD.mjs} +1 -1
  51. package/dist/{kg-entity-COTUj1ZC.mjs.map → kg-entity-DCOcsVFD.mjs.map} +1 -1
  52. package/dist/{latent-ideas-ByvMjVcb.mjs → latent-ideas-wwAXquGe.mjs} +3 -3
  53. package/dist/{latent-ideas-ByvMjVcb.mjs.map → latent-ideas-wwAXquGe.mjs.map} +1 -1
  54. package/dist/{link-boost-UiE-uooT.mjs → link-boost-CnI7UVMJ.mjs} +1 -1
  55. package/dist/{link-boost-UiE-uooT.mjs.map → link-boost-CnI7UVMJ.mjs.map} +1 -1
  56. package/dist/{main-resolver-kHW6FewU.mjs → main-resolver-BKz_OuEh.mjs} +2 -2
  57. package/dist/{main-resolver-BeYWNzrt.mjs → main-resolver-DPPwHNtn.mjs} +4 -4
  58. package/dist/{main-resolver-BeYWNzrt.mjs.map → main-resolver-DPPwHNtn.mjs.map} +1 -1
  59. package/dist/{neighborhood-CvRHlqdR.mjs → neighborhood-2FsmzoxG.mjs} +2 -2
  60. package/dist/{neighborhood-CvRHlqdR.mjs.map → neighborhood-2FsmzoxG.mjs.map} +1 -1
  61. package/dist/{note-context-BrbfUIoP.mjs → note-context-De63k8na.mjs} +1 -1
  62. package/dist/{note-context-BrbfUIoP.mjs.map → note-context-De63k8na.mjs.map} +1 -1
  63. package/dist/{planner-DAq4Yx-H.mjs → planner-EJPZnnif.mjs} +6 -6
  64. package/dist/{planner-DAq4Yx-H.mjs.map → planner-EJPZnnif.mjs.map} +1 -1
  65. package/dist/{postgres-DYtZg7J7.mjs → postgres-Ceqsa64C.mjs} +3 -3
  66. package/dist/{postgres-DYtZg7J7.mjs.map → postgres-Ceqsa64C.mjs.map} +1 -1
  67. package/dist/{program-CWf9mT7Z.mjs → program-DSETzrWg.mjs} +91 -51
  68. package/dist/{program-CWf9mT7Z.mjs.map → program-DSETzrWg.mjs.map} +1 -1
  69. package/dist/{registry-postgres-DSrkxqfF.mjs → registry-postgres-nOfyBSIW.mjs} +1 -1
  70. package/dist/{registry-postgres-DSrkxqfF.mjs.map → registry-postgres-nOfyBSIW.mjs.map} +1 -1
  71. package/dist/{registry-sqlite-B2436JgX.mjs → registry-sqlite-DrCW1aRK.mjs} +1 -1
  72. package/dist/{registry-sqlite-B2436JgX.mjs.map → registry-sqlite-DrCW1aRK.mjs.map} +1 -1
  73. package/dist/router-CXUGsv85.mjs +3 -0
  74. package/dist/{router-aqLMvNMg.mjs → router-a5G7q8_3.mjs} +1 -1
  75. package/dist/{router-aqLMvNMg.mjs.map → router-a5G7q8_3.mjs.map} +1 -1
  76. package/dist/{run-uAkNItb6.mjs → run-Z4akoa8G.mjs} +326 -24
  77. package/dist/run-Z4akoa8G.mjs.map +1 -0
  78. package/dist/{run-env-BHWnXqle.mjs → run-env-DRcp7A8K.mjs} +16 -2
  79. package/dist/run-env-DRcp7A8K.mjs.map +1 -0
  80. package/dist/{server-DlL1QI2k.mjs → server-DgmAHyFK.mjs} +2 -2
  81. package/dist/{server-DlL1QI2k.mjs.map → server-DgmAHyFK.mjs.map} +1 -1
  82. package/dist/{session-keepalive-BWEjcRrh.mjs → session-keepalive-BDhytXEo.mjs} +4 -4
  83. package/dist/{session-keepalive-BWEjcRrh.mjs.map → session-keepalive-BDhytXEo.mjs.map} +1 -1
  84. package/dist/{sources-DX4ElmrE.mjs → sources-CijVso3n.mjs} +1 -1
  85. package/dist/{sources-DX4ElmrE.mjs.map → sources-CijVso3n.mjs.map} +1 -1
  86. package/dist/{sqlite-BrEu3avy.mjs → sqlite-CqsTy6xo.mjs} +3 -3
  87. package/dist/{sqlite-BrEu3avy.mjs.map → sqlite-CqsTy6xo.mjs.map} +1 -1
  88. package/dist/{themes-BgqahYVM.mjs → themes-CTaOj3e1.mjs} +2 -2
  89. package/dist/{themes-BgqahYVM.mjs.map → themes-CTaOj3e1.mjs.map} +1 -1
  90. package/dist/{tools-gMGDIpJ9.mjs → tools-BcOKPBtg.mjs} +16 -16
  91. package/dist/{tools-gMGDIpJ9.mjs.map → tools-BcOKPBtg.mjs.map} +1 -1
  92. package/dist/{tools-BbqQIHPe.mjs → tools-C-_l0o8Z.mjs} +4 -4
  93. package/dist/{trace-CmAB7iJZ.mjs → trace-CtV0RO6n.mjs} +1 -1
  94. package/dist/{trace-CmAB7iJZ.mjs.map → trace-CtV0RO6n.mjs.map} +1 -1
  95. package/dist/{vault-indexer-Ddq51X20.mjs → vault-indexer-BG1sEVyM.mjs} +1 -1
  96. package/dist/{vault-indexer-Ddq51X20.mjs.map → vault-indexer-BG1sEVyM.mjs.map} +1 -1
  97. package/dist/{wakeup-CZw88uXf.mjs → wakeup-D8n33hYV.mjs} +2 -2
  98. package/dist/{wakeup-CZw88uXf.mjs.map → wakeup-D8n33hYV.mjs.map} +1 -1
  99. package/dist/{work-queue-worker-DCzfH0d1.mjs → work-queue-worker-4tLa5gpQ.mjs} +3 -3
  100. package/dist/{work-queue-worker-DCzfH0d1.mjs.map → work-queue-worker-4tLa5gpQ.mjs.map} +1 -1
  101. package/dist/work-queue-worker-rFZvTO3p.mjs +11 -0
  102. package/dist/{zettelkasten-Dwj67e4p.mjs → zettelkasten-CtGHQWPU.mjs} +3 -3
  103. package/dist/{zettelkasten-Dwj67e4p.mjs.map → zettelkasten-CtGHQWPU.mjs.map} +1 -1
  104. package/docs/commands/README.md +1 -0
  105. package/docs/commands/worker.md +30 -0
  106. package/package.json +1 -1
  107. package/src/hooks/ts/lib/worker-guard.ts +4 -0
  108. package/statusline-command.sh +1 -1
  109. package/dist/daemon-BjaPR39W.mjs +0 -19
  110. package/dist/detector-C3Q7mxQU.mjs +0 -3
  111. package/dist/fallback-CupzGkuJ.mjs.map +0 -1
  112. package/dist/router-S6C5BxzZ.mjs +0 -3
  113. package/dist/run-env-BHWnXqle.mjs.map +0 -1
  114. package/dist/run-uAkNItb6.mjs.map +0 -1
  115. package/dist/work-queue-worker-Taba_vcA.mjs +0 -11
@@ -1 +1 @@
1
- {"version":3,"file":"tools-gMGDIpJ9.mjs","names":[],"sources":["../src/mcp/tools/types.ts","../src/mcp/tools/memory.ts","../src/mcp/tools/projects.ts","../src/mcp/tools/sessions.ts","../src/mcp/tools/registry.ts","../src/mcp/tools/zettel.ts","../src/mcp/tools/kg.ts","../src/mcp/tools/wakeup.ts","../src/memory/taxonomy.ts","../src/mcp/tools/taxonomy.ts","../src/memory/tunnels.ts","../src/mcp/tools/tunnels.ts","../src/mcp/tools/feedback.ts","../src/memory/kg-search.ts","../src/mcp/tools/kg-search.ts"],"sourcesContent":["/**\n * Shared types and project-row helpers used across all MCP tool handler modules.\n */\n\nimport { resolve } from \"node:path\";\nimport type { RegistryBackend, Project } from \"../../storage/registry-interface.js\";\n\nexport interface ToolContent {\n type: \"text\";\n text: string;\n}\n\nexport interface ToolResult {\n content: ToolContent[];\n isError?: boolean;\n}\n\n// ---------------------------------------------------------------------------\n// Shared row type — mirrors the projects schema (RegistryBackend's Project)\n// ---------------------------------------------------------------------------\n\nexport type ProjectRow = Project;\n\n// ---------------------------------------------------------------------------\n// Helper: lookup project_id by slug (also checks aliases)\n// ---------------------------------------------------------------------------\n\nexport async function lookupProjectId(\n registry: RegistryBackend,\n slug: string\n): Promise<number | null> {\n const bySlug = await registry.getProjectBySlug(slug);\n if (bySlug) return bySlug.id;\n\n const projectId = await registry.resolveAlias(slug);\n return projectId ?? null;\n}\n\n// ---------------------------------------------------------------------------\n// Helper: detect project from a filesystem path\n// ---------------------------------------------------------------------------\n\nexport async function detectProjectFromPath(\n registry: RegistryBackend,\n fsPath: string\n): Promise<ProjectRow | null> {\n const resolved = resolve(fsPath);\n\n const exact = await registry.getProjectByRootPath(resolved);\n if (exact) return exact;\n\n const all = await registry.listProjectsByPathLengthDesc();\n\n for (const project of all) {\n if (\n resolved.startsWith(project.root_path + \"/\") ||\n resolved === project.root_path\n ) {\n return project;\n }\n }\n\n return null;\n}\n\n// ---------------------------------------------------------------------------\n// Helper: format project row for tool output\n// ---------------------------------------------------------------------------\n\nexport async function formatProject(registry: RegistryBackend, project: ProjectRow): Promise<string> {\n const sessionCount = await registry.countSessionsForProject(project.id);\n const lastSessionDate = await registry.getMostRecentSessionDate(project.id);\n const tags = await registry.listTagsForProject(project.id);\n const aliases = await registry.listAliasesForProject(project.id);\n\n const lines: string[] = [\n `slug: ${project.slug}`,\n `display_name: ${project.display_name}`,\n `root_path: ${project.root_path}`,\n `type: ${project.type}`,\n `status: ${project.status}`,\n `sessions: ${sessionCount}`,\n ];\n\n if (lastSessionDate) lines.push(`last_session: ${lastSessionDate}`);\n if (tags.length) lines.push(`tags: ${tags.join(\", \")}`);\n if (aliases.length) lines.push(`aliases: ${aliases.join(\", \")}`);\n if (project.obsidian_link) lines.push(`obsidian_link: ${project.obsidian_link}`);\n if (project.archived_at) {\n lines.push(\n `archived_at: ${new Date(project.archived_at).toISOString().slice(0, 10)}`\n );\n }\n\n return lines.join(\"\\n\");\n}\n","/**\n * MCP tool handlers: memory_search, memory_get, memory_outline\n */\n\nimport { readFileSync, existsSync, statSync } from \"node:fs\";\nimport { join, resolve, isAbsolute } from \"node:path\";\nimport { populateSlugs } from \"../../memory/search.js\";\nimport { buildOutline, type OutlineNode } from \"../../memory/chunker.js\";\nimport type { StorageBackend } from \"../../storage/interface.js\";\nimport type { RegistryBackend } from \"../../storage/registry-interface.js\";\nimport type { SearchConfig } from \"../../daemon/config.js\";\nimport type { SearchResult } from \"../../memory/search.js\";\nimport {\n lookupProjectId,\n type ToolResult,\n} from \"./types.js\";\n\n// ---------------------------------------------------------------------------\n// Tool: memory_search\n// ---------------------------------------------------------------------------\n\nexport interface MemorySearchParams {\n query: string;\n project?: string;\n all_projects?: boolean;\n sources?: Array<\"memory\" | \"notes\">;\n limit?: number;\n mode?: \"keyword\" | \"semantic\" | \"hybrid\";\n /** Rerank results using cross-encoder model for better relevance ordering. */\n rerank?: boolean;\n /** Apply recency boost — score decays by half every N days. 0 = off (default). */\n recencyBoost?: number;\n /** Maximum characters per result snippet. Default 200.\n * Limit context consumption — MCP results go into Claude's context window. */\n snippetLength?: number;\n /** Output format: \"full\" (default, includes snippets) or \"compact\" (IDs + metadata only, ~10x fewer tokens). */\n format?: \"full\" | \"compact\";\n}\n\nexport async function toolMemorySearch(\n registry: RegistryBackend,\n federation: StorageBackend,\n params: MemorySearchParams,\n searchDefaults?: SearchConfig,\n): Promise<ToolResult> {\n try {\n const projectIds: number[] | undefined = params.project\n ? await (async () => {\n const id = await lookupProjectId(registry, params.project!);\n return id != null ? [id] : [];\n })()\n : undefined;\n\n if (params.project && (!projectIds || projectIds.length === 0)) {\n return {\n content: [\n { type: \"text\", text: `Project not found: ${params.project}` },\n ],\n isError: true,\n };\n }\n\n // NOTE: No indexAll() here — indexing is handled by the daemon scheduler.\n // The daemon ensures the index stays fresh; the search hot path is read-only.\n\n const mode = params.mode ?? (searchDefaults?.mode ?? \"keyword\");\n // Limit context consumption — MCP results go into Claude's context window.\n // Default to 5 results and 200-char snippets to keep a single search call\n // within ~1-2K tokens rather than 5K+.\n const snippetLength = params.snippetLength ?? (searchDefaults?.snippetLength ?? 200);\n const searchOpts = {\n projectIds,\n sources: params.sources,\n maxResults: params.limit ?? (searchDefaults?.defaultLimit ?? 5),\n };\n\n let results: SearchResult[];\n\n if (mode === \"keyword\") {\n results = await federation.searchKeyword(params.query, searchOpts);\n } else if (mode === \"semantic\" || mode === \"hybrid\") {\n const { generateEmbedding } = await import(\"../../memory/embeddings.js\");\n const queryEmbedding = await generateEmbedding(params.query, true);\n\n if (mode === \"semantic\") {\n results = await federation.searchSemantic(queryEmbedding, searchOpts);\n } else {\n // Hybrid: combine keyword + semantic\n const [kwResults, semResults] = await Promise.all([\n federation.searchKeyword(params.query, { ...searchOpts, maxResults: 50 }),\n federation.searchSemantic(queryEmbedding, { ...searchOpts, maxResults: 50 }),\n ]); // 50 candidates is sufficient for min-max normalization\n // Reuse the existing hybrid scoring logic\n results = combineHybridResults(kwResults, semResults, searchOpts.maxResults ?? 10);\n }\n } else {\n results = await federation.searchKeyword(params.query, searchOpts);\n }\n\n // Re-rank by the corpus's own links before returning.\n //\n // Similarity answers \"what reads like the query\"; it cannot answer \"which\n // of these is the one the others refer back to\", which is usually the note\n // to read first. The wikilinks are facts a person wrote, they cost nothing\n // to maintain, and they are correct for chunks that have no embedding yet —\n // which matters while a large embedding backlog drains.\n //\n // Only EXTRACTED links count: INFERRED ones are themselves derived by\n // similarity, so folding them in would feed the ranking its own output.\n try {\n const { applyLinkBoost } = await import(\"../../memory/link-boost.js\");\n const paths = [...new Set(results.map((r) => r.path))];\n // Bounded: one lookup per distinct result path, not per chunk, and the\n // result set is already capped by maxResults.\n if (paths.length > 1) {\n const edges: Array<{ sourcePath: string; targetPath: string }> = [];\n for (const p of paths) {\n for (const l of await federation.getLinksToTarget(p)) {\n if (l.confidence && l.confidence !== \"EXTRACTED\") continue;\n edges.push({ sourcePath: l.sourcePath, targetPath: p });\n }\n }\n if (edges.length > 0) results = applyLinkBoost(results, edges);\n }\n } catch {\n // A ranking aid, never a reason to fail a search.\n }\n\n // QW2: Update last_accessed_at for returned chunks (best-effort, non-blocking)\n try {\n const chunkIds = results\n .map((r) => r.chunkId)\n .filter((id): id is string => id != null);\n if (chunkIds.length > 0) {\n await federation.touchChunksLastAccessed(chunkIds);\n }\n } catch {\n // non-critical — never block search results\n }\n\n // Cross-encoder reranking (on by default)\n const shouldRerank = params.rerank ?? (searchDefaults?.rerank ?? true);\n if (shouldRerank && results.length > 0) {\n const { rerankResults } = await import(\"../../memory/reranker.js\");\n results = await rerankResults(params.query, results, {\n topK: searchOpts.maxResults ?? 5,\n });\n }\n\n // Recency boost (off by default, applied after reranking)\n const recencyDays = params.recencyBoost ?? (searchDefaults?.recencyBoostDays ?? 0);\n if (recencyDays > 0 && results.length > 0) {\n const { applyRecencyBoost } = await import(\"../../memory/search.js\");\n results = applyRecencyBoost(results, recencyDays);\n }\n\n const withSlugs = await populateSlugs(results, registry);\n\n if (withSlugs.length === 0) {\n return {\n content: [\n {\n type: \"text\",\n text: `No results found for query: \"${params.query}\" (mode: ${mode})`,\n },\n ],\n };\n }\n\n const rerankLabel = shouldRerank ? \" +rerank\" : \"\";\n const useCompact = params.format === \"compact\";\n const formatted = withSlugs\n .map((r, i) => {\n const slug = r.projectSlug ?? `project:${r.projectId}`;\n const idPart = r.chunkId ? ` id=${r.chunkId}` : \"\";\n if (useCompact) {\n // Compact format: chunk ID, path, score — pass id to memory_feedback to rate this result\n return `[${i + 1}]${idPart} ${slug} — ${r.path} L${r.startLine}-${r.endLine} score=${r.score.toFixed(3)}`;\n }\n const header = `[${i + 1}]${idPart} ${slug} — ${r.path} (lines ${r.startLine}-${r.endLine}) score=${r.score.toFixed(4)} tier=${r.tier} source=${r.source}`;\n // Truncate snippet to snippetLength — limit context consumption.\n // MCP results go into Claude's context window; keep each result tight.\n const raw = r.snippet.trim();\n const snippet = raw.length > snippetLength\n ? raw.slice(0, snippetLength) + \"...\"\n : raw;\n return `${header}\\n${snippet}`;\n })\n .join(useCompact ? \"\\n\" : \"\\n\\n---\\n\\n\");\n\n // Query feedback loop: save query + result metadata for future indexing\n try {\n const { saveQueryResult } = await import(\"../../zettelkasten/query-feedback.js\");\n saveQueryResult({\n query: params.query,\n timestamp: Date.now(),\n source: \"memory_search\",\n sourceSlugs: withSlugs.slice(0, 5).map((r) => r.path),\n answerPreview: withSlugs.slice(0, 3).map((r) => r.snippet.trim().slice(0, 150)).join(\" | \"),\n resultCount: withSlugs.length,\n });\n } catch {\n // Non-critical — don't fail the search if feedback logging errors\n }\n\n return {\n content: [\n {\n type: \"text\",\n text: `Found ${withSlugs.length} result(s) for \"${params.query}\" (mode: ${mode}${rerankLabel}):\\n\\n${formatted}`,\n },\n ],\n };\n } catch (e) {\n return {\n content: [{ type: \"text\", text: `Search error: ${String(e)}` }],\n isError: true,\n };\n }\n}\n\n// ---------------------------------------------------------------------------\n// Tool: memory_get\n// ---------------------------------------------------------------------------\n\nexport interface MemoryGetParams {\n project: string;\n path: string;\n from?: number;\n lines?: number;\n}\n\n/**\n * Resolve `project` + `path` to a readable file inside the project root.\n * Shared by memory_get and memory_outline so both apply the same path and\n * access checks. Returns a ToolResult on any failure.\n */\nasync function resolveProjectFile(\n registry: RegistryBackend,\n projectSlug: string,\n requestedPath: string,\n): Promise<{ fullPath: string } | ToolResult> {\n const fail = (text: string): ToolResult => ({\n content: [{ type: \"text\", text }],\n isError: true,\n });\n\n const projectId = await lookupProjectId(registry, projectSlug);\n if (projectId == null) return fail(`Project not found: ${projectSlug}`);\n\n const project = await registry.getProjectById(projectId);\n if (!project) return fail(`Project not found: ${projectSlug}`);\n\n if (requestedPath.includes(\"..\") || isAbsolute(requestedPath)) {\n return fail(\n `Invalid path: ${requestedPath} (must be a relative path within the project root, no ../ allowed)`,\n );\n }\n\n const fullPath = join(project.root_path, requestedPath);\n const resolvedFull = resolve(fullPath);\n const resolvedRoot = resolve(project.root_path);\n\n if (!resolvedFull.startsWith(resolvedRoot + \"/\") && resolvedFull !== resolvedRoot) {\n return fail(`Path traversal blocked: ${requestedPath}`);\n }\n\n if (!existsSync(fullPath)) {\n return fail(`File not found: ${requestedPath} (project: ${projectSlug})`);\n }\n\n const stat = statSync(fullPath);\n if (stat.size > 5 * 1024 * 1024) {\n return {\n content: [\n {\n type: \"text\",\n text: `Error: file too large (${(stat.size / 1024 / 1024).toFixed(1)} MB). Maximum 5 MB.`,\n },\n ],\n };\n }\n\n return { fullPath };\n}\n\nexport async function toolMemoryGet(\n registry: RegistryBackend,\n params: MemoryGetParams\n): Promise<ToolResult> {\n try {\n const resolved = await resolveProjectFile(registry, params.project, params.path);\n if (\"content\" in resolved) return resolved;\n const { fullPath } = resolved;\n const requestedPath = params.path;\n\n const content = readFileSync(fullPath, \"utf8\");\n const allLines = content.split(\"\\n\");\n\n const fromLine = (params.from ?? 1) - 1;\n const toLine =\n params.lines != null\n ? Math.min(fromLine + params.lines, allLines.length)\n : allLines.length;\n\n const selectedLines = allLines.slice(fromLine, toLine);\n const text = selectedLines.join(\"\\n\");\n\n const header =\n params.from != null\n ? `${params.project}/${requestedPath} (lines ${fromLine + 1}-${toLine}):`\n : `${params.project}/${requestedPath}:`;\n\n return {\n content: [{ type: \"text\", text: `${header}\\n\\n${text}` }],\n };\n } catch (e) {\n return {\n content: [{ type: \"text\", text: `Read error: ${String(e)}` }],\n isError: true,\n };\n }\n}\n\n// ---------------------------------------------------------------------------\n// Tool: memory_outline\n// ---------------------------------------------------------------------------\n\nexport interface MemoryOutlineParams {\n project: string;\n path: string;\n max_depth?: number;\n}\n\nfunction renderOutline(nodes: OutlineNode[], maxDepth: number, depth = 0, out: string[] = []): string[] {\n for (const n of nodes) {\n out.push(\n `${\" \".repeat(depth)}${\"#\".repeat(n.level)} ${n.title} L${n.startLine}-${n.endLine} ~${n.tokens}t`,\n );\n if (depth + 1 < maxDepth) renderOutline(n.children, maxDepth, depth + 1, out);\n }\n return out;\n}\n\nexport async function toolMemoryOutline(\n registry: RegistryBackend,\n params: MemoryOutlineParams\n): Promise<ToolResult> {\n try {\n const resolved = await resolveProjectFile(registry, params.project, params.path);\n if (\"content\" in resolved) return resolved;\n\n const outline = buildOutline(readFileSync(resolved.fullPath, \"utf8\"));\n const body = outline.length\n ? renderOutline(outline, params.max_depth ?? Infinity).join(\"\\n\")\n : \"(no headings)\";\n\n return {\n content: [{ type: \"text\", text: `${params.project}/${params.path} outline:\\n\\n${body}` }],\n };\n } catch (e) {\n return {\n content: [{ type: \"text\", text: `Read error: ${String(e)}` }],\n isError: true,\n };\n }\n}\n\n// ---------------------------------------------------------------------------\n// Hybrid search helper (backend-agnostic)\n// ---------------------------------------------------------------------------\n\n/**\n * Combine keyword + semantic results using min-max normalized scoring.\n * Mirrors the logic in searchMemoryHybrid() from memory/search.ts,\n * but works on pre-computed result arrays so it works for any backend.\n */\nexport function combineHybridResults(\n keywordResults: SearchResult[],\n semanticResults: SearchResult[],\n maxResults: number,\n keywordWeight = 0.5,\n semanticWeight = 0.5\n): SearchResult[] {\n if (keywordResults.length === 0 && semanticResults.length === 0) return [];\n\n const keyFor = (r: SearchResult) =>\n `${r.projectId}:${r.path}:${r.startLine}:${r.endLine}`;\n\n function minMaxNormalize(items: SearchResult[]): Map<string, number> {\n if (items.length === 0) return new Map();\n const min = Math.min(...items.map((r) => r.score));\n const max = Math.max(...items.map((r) => r.score));\n const range = max - min;\n const m = new Map<string, number>();\n for (const r of items) {\n m.set(keyFor(r), range === 0 ? 1 : (r.score - min) / range);\n }\n return m;\n }\n\n const kwNorm = minMaxNormalize(keywordResults);\n const semNorm = minMaxNormalize(semanticResults);\n\n const allKeys = new Set<string>([\n ...keywordResults.map(keyFor),\n ...semanticResults.map(keyFor),\n ]);\n\n const metaMap = new Map<string, SearchResult>();\n for (const r of [...keywordResults, ...semanticResults]) {\n metaMap.set(keyFor(r), r);\n }\n\n const combined: Array<SearchResult & { combinedScore: number }> = [];\n for (const key of allKeys) {\n const meta = metaMap.get(key)!;\n const kwScore = kwNorm.get(key) ?? 0;\n const semScore = semNorm.get(key) ?? 0;\n const combinedScore = keywordWeight * kwScore + semanticWeight * semScore;\n combined.push({ ...meta, score: combinedScore, combinedScore });\n }\n\n return combined\n .sort((a, b) => b.score - a.score)\n .slice(0, maxResults)\n .map(({ combinedScore: _unused, ...r }) => r);\n}\n","/**\n * MCP tool handlers: project_info, project_list, project_detect,\n * project_health, project_todo\n */\n\nimport { readFileSync, existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { detectProject, formatDetectionJson } from \"../../cli/commands/detect.js\";\nimport type { RegistryBackend } from \"../../storage/registry-interface.js\";\nimport {\n lookupProjectId,\n detectProjectFromPath,\n formatProject,\n type ProjectRow,\n type ToolResult,\n} from \"./types.js\";\n\n// ---------------------------------------------------------------------------\n// Tool: project_info\n// ---------------------------------------------------------------------------\n\nexport interface ProjectInfoParams {\n slug?: string;\n}\n\nexport async function toolProjectInfo(\n registry: RegistryBackend,\n params: ProjectInfoParams\n): Promise<ToolResult> {\n try {\n let project: ProjectRow | null = null;\n\n if (params.slug) {\n const projectId = await lookupProjectId(registry, params.slug);\n if (projectId != null) {\n project = await registry.getProjectById(projectId);\n }\n } else {\n const cwd = process.cwd();\n project = await detectProjectFromPath(registry, cwd);\n }\n\n if (!project) {\n const message = params.slug\n ? `Project not found: ${params.slug}`\n : `No PAI project found matching the current directory: ${process.cwd()}`;\n return {\n content: [{ type: \"text\", text: message }],\n isError: !params.slug,\n };\n }\n\n return {\n content: [{ type: \"text\", text: await formatProject(registry, project) }],\n };\n } catch (e) {\n return {\n content: [{ type: \"text\", text: `project_info error: ${String(e)}` }],\n isError: true,\n };\n }\n}\n\n// ---------------------------------------------------------------------------\n// Tool: project_list\n// ---------------------------------------------------------------------------\n\nexport interface ProjectListParams {\n status?: \"active\" | \"archived\" | \"migrating\";\n tag?: string;\n limit?: number;\n}\n\nexport async function toolProjectList(\n registry: RegistryBackend,\n params: ProjectListParams\n): Promise<ToolResult> {\n try {\n const limit = params.limit ?? 50;\n\n const projects = await registry.listProjects({\n status: params.status,\n tagName: params.tag,\n orderBy: \"updated_desc\",\n limit,\n });\n\n if (projects.length === 0) {\n return {\n content: [\n {\n type: \"text\",\n text: \"No projects found matching the given filters.\",\n },\n ],\n };\n }\n\n const lines = projects.map(\n (p) =>\n `${p.slug} [${p.status}] ${p.root_path} (updated: ${new Date(p.updated_at).toISOString().slice(0, 10)})`\n );\n\n return {\n content: [\n {\n type: \"text\",\n text: `${projects.length} project(s):\\n\\n${lines.join(\"\\n\")}`,\n },\n ],\n };\n } catch (e) {\n return {\n content: [{ type: \"text\", text: `project_list error: ${String(e)}` }],\n isError: true,\n };\n }\n}\n\n// ---------------------------------------------------------------------------\n// Tool: project_detect\n// ---------------------------------------------------------------------------\n\nexport interface ProjectDetectParams {\n cwd?: string;\n}\n\nexport async function toolProjectDetect(\n params: ProjectDetectParams\n): Promise<ToolResult> {\n try {\n const detection = await detectProject(params.cwd);\n\n if (!detection) {\n const target = params.cwd ?? process.cwd();\n return {\n content: [\n {\n type: \"text\",\n text: `No registered project found for path: ${target}\\n\\nRun 'pai project add .' to register this directory.`,\n },\n ],\n };\n }\n\n return {\n content: [{ type: \"text\", text: formatDetectionJson(detection) }],\n };\n } catch (e) {\n return {\n content: [\n { type: \"text\", text: `project_detect error: ${String(e)}` },\n ],\n isError: true,\n };\n }\n}\n\n// ---------------------------------------------------------------------------\n// Tool: project_health\n// ---------------------------------------------------------------------------\n\nexport interface ProjectHealthParams {\n category?: \"active\" | \"stale\" | \"dead\" | \"all\";\n}\n\nexport async function toolProjectHealth(\n registry: RegistryBackend,\n params: ProjectHealthParams\n): Promise<ToolResult> {\n try {\n const { existsSync: fsExists, readdirSync, statSync } = await import(\n \"node:fs\"\n );\n const {\n join: pathJoin,\n basename: pathBasename,\n } = await import(\"node:path\");\n const { homedir } = await import(\"node:os\");\n const { encodeDir: enc } = await import(\"../../cli/utils.js\");\n\n const rows = (await registry.listProjectsWithSessionStats())\n .slice()\n .sort((a, b) => a.slug.localeCompare(b.slug));\n\n const home = homedir();\n const claudeProjects = pathJoin(home, \".claude\", \"projects\");\n\n function suggestMoved(rootPath: string): string | undefined {\n const name = pathBasename(rootPath);\n const candidates = [\n pathJoin(home, \"dev\", name),\n pathJoin(home, \"dev\", \"ai\", name),\n pathJoin(home, \"Desktop\", name),\n pathJoin(home, \"Projects\", name),\n ];\n return candidates.find((c) => fsExists(c));\n }\n\n function hasClaudeNotes(encodedDir: string): boolean {\n if (!fsExists(claudeProjects)) return false;\n try {\n for (const entry of readdirSync(claudeProjects)) {\n if (entry !== encodedDir && !entry.startsWith(encodedDir)) continue;\n const full = pathJoin(claudeProjects, entry);\n try {\n if (!statSync(full).isDirectory()) continue;\n } catch {\n continue;\n }\n if (fsExists(pathJoin(full, \"Notes\"))) return true;\n }\n } catch {\n /* ignore */\n }\n return false;\n }\n\n interface HealthResult {\n slug: string;\n display_name: string;\n root_path: string;\n status: string;\n type: string;\n session_count: number;\n health: string;\n suggested_path: string | null;\n has_claude_notes: boolean;\n todo: {\n found: boolean;\n path: string | null;\n has_continue: boolean;\n };\n }\n\n function findTodoForProject(rootPath: string): {\n found: boolean;\n path: string | null;\n has_continue: boolean;\n } {\n const locs = [\n \"Notes/TODO.md\",\n \".claude/Notes/TODO.md\",\n \"tasks/todo.md\",\n \"TODO.md\",\n ];\n for (const rel of locs) {\n const full = pathJoin(rootPath, rel);\n if (fsExists(full)) {\n try {\n const raw = readFileSync(full, \"utf8\");\n const hasContinue = /^## Continue$/m.test(raw);\n return { found: true, path: rel, has_continue: hasContinue };\n } catch {\n return { found: true, path: rel, has_continue: false };\n }\n }\n }\n return { found: false, path: null, has_continue: false };\n }\n\n const results: HealthResult[] = rows.map((p) => {\n const pathExists = fsExists(p.root_path);\n let health: string;\n let suggestedPath: string | null = null;\n\n if (pathExists) {\n health = \"active\";\n } else {\n suggestedPath = suggestMoved(p.root_path) ?? null;\n health = suggestedPath ? \"stale\" : \"dead\";\n }\n\n const todo = pathExists\n ? findTodoForProject(p.root_path)\n : { found: false, path: null, has_continue: false };\n\n return {\n slug: p.slug,\n display_name: p.display_name,\n root_path: p.root_path,\n status: p.status,\n type: p.type,\n session_count: p.session_count,\n health,\n suggested_path: suggestedPath,\n has_claude_notes: hasClaudeNotes(p.encoded_dir),\n todo,\n };\n });\n\n const filtered =\n !params.category || params.category === \"all\"\n ? results\n : results.filter((r) => r.health === params.category);\n\n const summary = {\n total: rows.length,\n active: results.filter((r) => r.health === \"active\").length,\n stale: results.filter((r) => r.health === \"stale\").length,\n dead: results.filter((r) => r.health === \"dead\").length,\n };\n\n return {\n content: [\n {\n type: \"text\",\n text: JSON.stringify({ summary, projects: filtered }, null, 2),\n },\n ],\n };\n } catch (e) {\n return {\n content: [\n { type: \"text\", text: `project_health error: ${String(e)}` },\n ],\n isError: true,\n };\n }\n}\n\n// ---------------------------------------------------------------------------\n// Tool: project_todo\n// ---------------------------------------------------------------------------\n\nexport interface ProjectTodoParams {\n project?: string;\n}\n\n/**\n * TODO candidate locations searched in priority order.\n * Returns the first one that exists, along with its label.\n */\nconst TODO_LOCATIONS = [\n { rel: \"Notes/TODO.md\", label: \"Notes/TODO.md\" },\n { rel: \".claude/Notes/TODO.md\", label: \".claude/Notes/TODO.md\" },\n { rel: \"tasks/todo.md\", label: \"tasks/todo.md\" },\n { rel: \"TODO.md\", label: \"TODO.md\" },\n];\n\n/**\n * Given TODO file content, extract and surface the ## Continue section first,\n * then return the remaining content. Returns an object with:\n * continueSection: string | null\n * fullContent: string\n * hasContinue: boolean\n */\nfunction parseTodoContent(raw: string): {\n continueSection: string | null;\n fullContent: string;\n hasContinue: boolean;\n} {\n const lines = raw.split(\"\\n\");\n\n // Find the ## Continue heading\n const continueIdx = lines.findIndex(\n (l) => l.trim() === \"## Continue\"\n );\n\n if (continueIdx === -1) {\n return { continueSection: null, fullContent: raw, hasContinue: false };\n }\n\n // The section ends at the first `---` separator or next `##` heading after\n // the Continue heading (whichever comes first).\n let endIdx = lines.length;\n for (let i = continueIdx + 1; i < lines.length; i++) {\n const trimmed = lines[i].trim();\n if (trimmed === \"---\" || (trimmed.startsWith(\"##\") && trimmed !== \"## Continue\")) {\n endIdx = i;\n break;\n }\n }\n\n const continueLines = lines.slice(continueIdx, endIdx);\n const continueSection = continueLines.join(\"\\n\").trim();\n\n return { continueSection, fullContent: raw, hasContinue: true };\n}\n\nexport async function toolProjectTodo(\n registry: RegistryBackend,\n params: ProjectTodoParams\n): Promise<ToolResult> {\n try {\n let rootPath: string;\n let projectSlug: string;\n\n if (params.project) {\n const projectId = await lookupProjectId(registry, params.project);\n if (projectId == null) {\n return {\n content: [\n { type: \"text\", text: `Project not found: ${params.project}` },\n ],\n isError: true,\n };\n }\n\n const row = await registry.getProjectById(projectId);\n\n if (!row) {\n return {\n content: [\n { type: \"text\", text: `Project not found: ${params.project}` },\n ],\n isError: true,\n };\n }\n\n rootPath = row.root_path;\n projectSlug = row.slug;\n } else {\n // Auto-detect from cwd\n const project = await detectProjectFromPath(registry, process.cwd());\n if (!project) {\n return {\n content: [\n {\n type: \"text\",\n text: `No PAI project found matching the current directory: ${process.cwd()}\\n\\nProvide a project slug or run 'pai project add .' to register this directory.`,\n },\n ],\n };\n }\n rootPath = project.root_path;\n projectSlug = project.slug;\n }\n\n // Search for TODO in priority order\n for (const loc of TODO_LOCATIONS) {\n const fullPath = join(rootPath, loc.rel);\n if (existsSync(fullPath)) {\n const raw = readFileSync(fullPath, \"utf8\");\n const { continueSection, fullContent, hasContinue } = parseTodoContent(raw);\n\n let output: string;\n if (hasContinue && continueSection) {\n // Surface the ## Continue section first, then the full content\n output = [\n `TODO found: ${projectSlug}/${loc.label}`,\n \"\",\n \"=== CONTINUE SECTION (surfaced first) ===\",\n continueSection,\n \"\",\n \"=== FULL TODO CONTENT ===\",\n fullContent,\n ].join(\"\\n\");\n } else {\n output = [\n `TODO found: ${projectSlug}/${loc.label}`,\n \"\",\n fullContent,\n ].join(\"\\n\");\n }\n\n return {\n content: [{ type: \"text\", text: output }],\n };\n }\n }\n\n // No TODO found in any location\n const searched = TODO_LOCATIONS.map((l) => ` ${rootPath}/${l.rel}`).join(\"\\n\");\n return {\n content: [\n {\n type: \"text\",\n text: [\n `No TODO.md found for project: ${projectSlug}`,\n \"\",\n \"Searched locations (in order):\",\n searched,\n \"\",\n \"Create a TODO with: echo '## Tasks\\\\n- [ ] First task' > Notes/TODO.md\",\n ].join(\"\\n\"),\n },\n ],\n };\n } catch (e) {\n return {\n content: [{ type: \"text\", text: `project_todo error: ${String(e)}` }],\n isError: true,\n };\n }\n}\n","/**\n * MCP tool handlers: session_list, session_route\n */\n\nimport type { StorageBackend } from \"../../storage/interface.js\";\nimport type { RegistryBackend } from \"../../storage/registry-interface.js\";\nimport {\n lookupProjectId,\n type ToolResult,\n} from \"./types.js\";\n\n// ---------------------------------------------------------------------------\n// Tool: session_list\n// ---------------------------------------------------------------------------\n\nexport interface SessionListParams {\n project: string;\n limit?: number;\n status?: \"open\" | \"completed\" | \"compacted\";\n}\n\nexport async function toolSessionList(\n registry: RegistryBackend,\n params: SessionListParams\n): Promise<ToolResult> {\n try {\n const projectId = await lookupProjectId(registry, params.project);\n if (projectId == null) {\n return {\n content: [\n { type: \"text\", text: `Project not found: ${params.project}` },\n ],\n isError: true,\n };\n }\n\n const limit = params.limit ?? 10;\n const sessions = await registry.listSessions({\n projectId,\n status: params.status,\n limit,\n });\n\n if (sessions.length === 0) {\n return {\n content: [\n {\n type: \"text\",\n text: `No sessions found for project: ${params.project}`,\n },\n ],\n };\n }\n\n const lines = sessions.map(\n (s) =>\n `#${String(s.number).padStart(4, \"0\")} ${s.date} [${s.status}] ${s.title}\\n file: Notes/${s.filename}`\n );\n\n return {\n content: [\n {\n type: \"text\",\n text: `${sessions.length} session(s) for ${params.project}:\\n\\n${lines.join(\"\\n\\n\")}`,\n },\n ],\n };\n } catch (e) {\n return {\n content: [{ type: \"text\", text: `session_list error: ${String(e)}` }],\n isError: true,\n };\n }\n}\n\n// ---------------------------------------------------------------------------\n// Tool: session_route\n// ---------------------------------------------------------------------------\n\nexport interface SessionRouteParams {\n /** Working directory to route from (defaults to process.cwd()) */\n cwd?: string;\n /** Optional conversation context for topic-based fallback routing */\n context?: string;\n}\n\n/**\n * Automatically suggest which project a session belongs to.\n *\n * Strategy (in priority order):\n * 1. path — exact or parent-directory match in the project registry\n * 2. marker — walk up from cwd looking for Notes/PAI.md\n * 3. topic — BM25 keyword search against memory (requires context)\n *\n * Call this at session start (e.g., from CLAUDE.md or a session-start hook)\n * to automatically route the session to the correct project.\n */\nexport async function toolSessionRoute(\n registry: RegistryBackend,\n federation: StorageBackend,\n params: SessionRouteParams\n): Promise<ToolResult> {\n try {\n const { autoRoute, formatAutoRouteJson } = await import(\"../../session/auto-route.js\");\n\n const result = await autoRoute(\n registry,\n federation,\n params.cwd,\n params.context\n );\n\n if (!result) {\n const target = params.cwd ?? process.cwd();\n return {\n content: [\n {\n type: \"text\",\n text: [\n `No project match found for: ${target}`,\n \"\",\n \"Tried: path match, PAI.md marker walk\" +\n (params.context ? \", topic detection\" : \"\"),\n \"\",\n \"Run 'pai project add .' to register this directory,\",\n \"or provide conversation context for topic-based routing.\",\n ].join(\"\\n\"),\n },\n ],\n };\n }\n\n return {\n content: [{ type: \"text\", text: formatAutoRouteJson(result) }],\n };\n } catch (e) {\n return {\n content: [{ type: \"text\", text: `session_route error: ${String(e)}` }],\n isError: true,\n };\n }\n}\n","/**\n * MCP tool handler: registry_search\n */\n\nimport type { RegistryBackend } from \"../../storage/registry-interface.js\";\nimport type { ToolResult } from \"./types.js\";\n\n// ---------------------------------------------------------------------------\n// Tool: registry_search\n// ---------------------------------------------------------------------------\n\nexport interface RegistrySearchParams {\n query: string;\n}\n\nexport async function toolRegistrySearch(\n registry: RegistryBackend,\n params: RegistrySearchParams\n): Promise<ToolResult> {\n try {\n const projects = await registry.searchProjects(params.query, 20);\n\n if (projects.length === 0) {\n return {\n content: [\n {\n type: \"text\",\n text: `No projects found matching: \"${params.query}\"`,\n },\n ],\n };\n }\n\n const lines = projects.map((p) => `${p.slug} [${p.status}] ${p.root_path}`);\n\n return {\n content: [\n {\n type: \"text\",\n text: `${projects.length} match(es) for \"${params.query}\":\\n\\n${lines.join(\"\\n\")}`,\n },\n ],\n };\n } catch (e) {\n return {\n content: [\n { type: \"text\", text: `registry_search error: ${String(e)}` },\n ],\n isError: true,\n };\n }\n}\n","/**\n * MCP tool handlers: zettel_explore, zettel_health, zettel_surprise,\n * zettel_suggest, zettel_converse, zettel_themes,\n * zettel_god_notes, zettel_communities\n */\n\nimport type { StorageBackend } from \"../../storage/interface.js\";\nimport type { ToolResult } from \"./types.js\";\n\n// ---------------------------------------------------------------------------\n// Tool: zettel_explore\n// ---------------------------------------------------------------------------\n\nexport interface ZettelExploreParams {\n start_note: string;\n depth?: number;\n direction?: string;\n mode?: string;\n}\n\nexport async function toolZettelExplore(\n backend: StorageBackend,\n params: ZettelExploreParams\n): Promise<ToolResult> {\n try {\n const { zettelExplore } = await import(\"../../zettelkasten/index.js\");\n const result = await zettelExplore(backend, {\n startNote: params.start_note,\n depth: params.depth,\n direction: params.direction as \"forward\" | \"backward\" | \"both\" | undefined,\n mode: params.mode as \"sequential\" | \"associative\" | \"all\" | undefined,\n });\n return {\n content: [{ type: \"text\", text: JSON.stringify(result, null, 2) }],\n };\n } catch (e) {\n return {\n content: [{ type: \"text\", text: `zettel_explore error: ${String(e)}` }],\n isError: true,\n };\n }\n}\n\n// ---------------------------------------------------------------------------\n// Tool: zettel_health\n// ---------------------------------------------------------------------------\n\nexport interface ZettelHealthParams {\n scope?: string;\n project_path?: string;\n recent_days?: number;\n include?: string[];\n}\n\nexport async function toolZettelHealth(\n backend: StorageBackend,\n params: ZettelHealthParams\n): Promise<ToolResult> {\n try {\n const { zettelHealth } = await import(\"../../zettelkasten/index.js\");\n const result = await zettelHealth(backend, {\n scope: params.scope as \"full\" | \"recent\" | \"project\" | undefined,\n projectPath: params.project_path,\n recentDays: params.recent_days,\n include: params.include as Array<\"dead_links\" | \"orphans\" | \"disconnected\" | \"low_connectivity\"> | undefined,\n });\n return {\n content: [{ type: \"text\", text: JSON.stringify(result, null, 2) }],\n };\n } catch (e) {\n return {\n content: [{ type: \"text\", text: `zettel_health error: ${String(e)}` }],\n isError: true,\n };\n }\n}\n\n// ---------------------------------------------------------------------------\n// Tool: zettel_surprise\n// ---------------------------------------------------------------------------\n\nexport interface ZettelSurpriseParams {\n reference_path: string;\n vault_project_id: number;\n limit?: number;\n min_similarity?: number;\n min_graph_distance?: number;\n}\n\nexport async function toolZettelSurprise(\n backend: StorageBackend,\n params: ZettelSurpriseParams\n): Promise<ToolResult> {\n try {\n const { zettelSurprise } = await import(\"../../zettelkasten/index.js\");\n const results = await zettelSurprise(backend, {\n referencePath: params.reference_path,\n vaultProjectId: params.vault_project_id,\n limit: params.limit,\n minSimilarity: params.min_similarity,\n minGraphDistance: params.min_graph_distance,\n });\n return {\n content: [{ type: \"text\", text: JSON.stringify(results, null, 2) }],\n };\n } catch (e) {\n return {\n content: [{ type: \"text\", text: `zettel_surprise error: ${String(e)}` }],\n isError: true,\n };\n }\n}\n\n// ---------------------------------------------------------------------------\n// Tool: zettel_suggest\n// ---------------------------------------------------------------------------\n\nexport interface ZettelSuggestParams {\n note_path: string;\n vault_project_id: number;\n limit?: number;\n exclude_linked?: boolean;\n}\n\nexport async function toolZettelSuggest(\n backend: StorageBackend,\n params: ZettelSuggestParams\n): Promise<ToolResult> {\n try {\n const { zettelSuggest } = await import(\"../../zettelkasten/index.js\");\n const results = await zettelSuggest(backend, {\n notePath: params.note_path,\n vaultProjectId: params.vault_project_id,\n limit: params.limit,\n excludeLinked: params.exclude_linked,\n });\n return {\n content: [{ type: \"text\", text: JSON.stringify(results, null, 2) }],\n };\n } catch (e) {\n return {\n content: [{ type: \"text\", text: `zettel_suggest error: ${String(e)}` }],\n isError: true,\n };\n }\n}\n\n// ---------------------------------------------------------------------------\n// Tool: zettel_converse\n// ---------------------------------------------------------------------------\n\nexport interface ZettelConverseParams {\n question: string;\n vault_project_id: number;\n depth?: number;\n limit?: number;\n}\n\nexport async function toolZettelConverse(\n backend: StorageBackend,\n params: ZettelConverseParams\n): Promise<ToolResult> {\n try {\n const { zettelConverse } = await import(\"../../zettelkasten/index.js\");\n const result = await zettelConverse(backend, {\n question: params.question,\n vaultProjectId: params.vault_project_id,\n depth: params.depth,\n limit: params.limit,\n });\n\n // Query feedback loop: save query + result metadata for future indexing\n try {\n const { saveQueryResult } = await import(\"../../zettelkasten/query-feedback.js\");\n saveQueryResult({\n query: params.question,\n timestamp: Date.now(),\n source: \"zettel_converse\",\n sourceSlugs: result.relevantNotes.slice(0, 5).map((n) => n.path),\n answerPreview: result.relevantNotes.slice(0, 3).map((n) => {\n const title = n.title ?? \"(untitled)\";\n return `${title}: ${n.snippet.trim().slice(0, 100)}`;\n }).join(\" | \"),\n resultCount: result.relevantNotes.length,\n });\n } catch {\n // Non-critical\n }\n\n return {\n content: [{ type: \"text\", text: JSON.stringify(result, null, 2) }],\n };\n } catch (e) {\n return {\n content: [{ type: \"text\", text: `zettel_converse error: ${String(e)}` }],\n isError: true,\n };\n }\n}\n\n// ---------------------------------------------------------------------------\n// Tool: zettel_themes\n// ---------------------------------------------------------------------------\n\nexport interface ZettelThemesParams {\n vault_project_id: number;\n lookback_days?: number;\n min_cluster_size?: number;\n max_themes?: number;\n similarity_threshold?: number;\n}\n\nexport async function toolZettelThemes(\n backend: StorageBackend,\n params: ZettelThemesParams\n): Promise<ToolResult> {\n try {\n const { zettelThemes } = await import(\"../../zettelkasten/index.js\");\n const result = await zettelThemes(backend, {\n vaultProjectId: params.vault_project_id,\n lookbackDays: params.lookback_days,\n minClusterSize: params.min_cluster_size,\n maxThemes: params.max_themes,\n similarityThreshold: params.similarity_threshold,\n });\n return {\n content: [{ type: \"text\", text: JSON.stringify(result, null, 2) }],\n };\n } catch (e) {\n return {\n content: [{ type: \"text\", text: `zettel_themes error: ${String(e)}` }],\n isError: true,\n };\n }\n}\n\n// ---------------------------------------------------------------------------\n// Tool: zettel_god_notes\n// ---------------------------------------------------------------------------\n\nexport interface ZettelGodNotesParams {\n limit?: number;\n min_inbound?: number;\n}\n\nexport async function toolZettelGodNotes(\n backend: StorageBackend,\n params: ZettelGodNotesParams\n): Promise<ToolResult> {\n try {\n const { zettelGodNotes } = await import(\"../../zettelkasten/index.js\");\n const result = await zettelGodNotes(backend, {\n limit: params.limit,\n minInbound: params.min_inbound,\n });\n return {\n content: [{ type: \"text\", text: JSON.stringify(result, null, 2) }],\n };\n } catch (e) {\n return {\n content: [{ type: \"text\", text: `zettel_god_notes error: ${String(e)}` }],\n isError: true,\n };\n }\n}\n\n// ---------------------------------------------------------------------------\n// Tool: zettel_communities\n// ---------------------------------------------------------------------------\n\nexport interface ZettelCommunitiesParams {\n min_size?: number;\n max_communities?: number;\n resolution?: number;\n}\n\nexport async function toolZettelCommunities(\n backend: StorageBackend,\n params: ZettelCommunitiesParams\n): Promise<ToolResult> {\n try {\n const { zettelCommunities } = await import(\"../../zettelkasten/index.js\");\n const result = await zettelCommunities(backend, {\n minSize: params.min_size,\n maxCommunities: params.max_communities,\n resolution: params.resolution,\n });\n return {\n content: [{ type: \"text\", text: JSON.stringify(result, null, 2) }],\n };\n } catch (e) {\n return {\n content: [{ type: \"text\", text: `zettel_communities error: ${String(e)}` }],\n isError: true,\n };\n }\n}\n","/**\n * MCP tool handlers for the temporal knowledge graph.\n *\n * Tools:\n * kg_add — insert a new (subject, predicate, object) triple\n * kg_query — query triples with optional temporal filter\n * kg_invalidate — mark a triple as no longer valid (sets valid_to)\n * kg_contradictions — find (subject, predicate) pairs with multiple valid objects\n */\n\nimport type { StorageBackend } from \"../../storage/interface.js\";\nimport type { ToolResult } from \"./types.js\";\n\n// ---------------------------------------------------------------------------\n// Tool: kg_add\n// ---------------------------------------------------------------------------\n\nexport interface KgAddToolParams {\n subject: string;\n predicate: string;\n object: string;\n project_id?: number;\n source_session?: string;\n confidence?: \"EXTRACTED\" | \"INFERRED\" | \"AMBIGUOUS\";\n}\n\nexport async function toolKgAdd(\n backend: StorageBackend,\n params: KgAddToolParams\n): Promise<ToolResult> {\n try {\n if (!params.subject || !params.predicate || !params.object) {\n return {\n content: [{ type: \"text\", text: \"kg_add error: subject, predicate, and object are required\" }],\n isError: true,\n };\n }\n const triple = await backend.addKgTriple(params);\n return {\n content: [{ type: \"text\", text: JSON.stringify(triple, null, 2) }],\n };\n } catch (e) {\n return {\n content: [{ type: \"text\", text: `kg_add error: ${String(e)}` }],\n isError: true,\n };\n }\n}\n\n// ---------------------------------------------------------------------------\n// Tool: kg_query\n// ---------------------------------------------------------------------------\n\nexport interface KgQueryToolParams {\n subject?: string;\n predicate?: string;\n object?: string;\n project_id?: number;\n as_of?: string; // ISO 8601 string; converted to Date\n include_invalidated?: boolean;\n}\n\nexport async function toolKgQuery(\n backend: StorageBackend,\n params: KgQueryToolParams\n): Promise<ToolResult> {\n try {\n const asOf = params.as_of ? new Date(params.as_of) : undefined;\n if (asOf && isNaN(asOf.getTime())) {\n return {\n content: [{ type: \"text\", text: `kg_query error: invalid as_of date: ${params.as_of}` }],\n isError: true,\n };\n }\n const triples = await backend.queryKgTriples({\n subject: params.subject,\n predicate: params.predicate,\n object: params.object,\n project_id: params.project_id,\n as_of: asOf,\n include_invalidated: params.include_invalidated,\n });\n return {\n content: [{ type: \"text\", text: JSON.stringify(triples, null, 2) }],\n };\n } catch (e) {\n return {\n content: [{ type: \"text\", text: `kg_query error: ${String(e)}` }],\n isError: true,\n };\n }\n}\n\n// ---------------------------------------------------------------------------\n// Tool: kg_invalidate\n// ---------------------------------------------------------------------------\n\nexport interface KgInvalidateToolParams {\n triple_id: number;\n}\n\nexport async function toolKgInvalidate(\n backend: StorageBackend,\n params: KgInvalidateToolParams\n): Promise<ToolResult> {\n try {\n if (params.triple_id === undefined || params.triple_id === null) {\n return {\n content: [{ type: \"text\", text: \"kg_invalidate error: triple_id is required\" }],\n isError: true,\n };\n }\n await backend.invalidateKgTriple(params.triple_id);\n return {\n content: [{ type: \"text\", text: JSON.stringify({ invalidated: true, triple_id: params.triple_id }) }],\n };\n } catch (e) {\n return {\n content: [{ type: \"text\", text: `kg_invalidate error: ${String(e)}` }],\n isError: true,\n };\n }\n}\n\n// ---------------------------------------------------------------------------\n// Tool: kg_contradictions\n// ---------------------------------------------------------------------------\n\nexport interface KgContradictionsToolParams {\n subject: string;\n}\n\nexport async function toolKgContradictions(\n backend: StorageBackend,\n params: KgContradictionsToolParams\n): Promise<ToolResult> {\n try {\n if (!params.subject) {\n return {\n content: [{ type: \"text\", text: \"kg_contradictions error: subject is required\" }],\n isError: true,\n };\n }\n const contradictions = await backend.getKgContradictions(params.subject);\n return {\n content: [{ type: \"text\", text: JSON.stringify(contradictions, null, 2) }],\n };\n } catch (e) {\n return {\n content: [{ type: \"text\", text: `kg_contradictions error: ${String(e)}` }],\n isError: true,\n };\n }\n}\n","/**\n * MCP tool handler: memory_wakeup\n *\n * Returns the L0+L1 wake-up context block for a project:\n * L0 — user identity from ~/.pai/identity.txt\n * L1 — recent session note highlights (Work Done / Key Decisions / Next Steps)\n */\n\nimport type { RegistryBackend } from \"../../storage/registry-interface.js\";\nimport { buildWakeupContext } from \"../../memory/wakeup.js\";\nimport { detectProjectFromPath } from \"./types.js\";\nimport type { ToolResult } from \"./types.js\";\n\n// ---------------------------------------------------------------------------\n// Default token budget (constant, overridable per call)\n// ---------------------------------------------------------------------------\n\nconst DEFAULT_TOKEN_BUDGET = 800;\n\n// ---------------------------------------------------------------------------\n// Params\n// ---------------------------------------------------------------------------\n\nexport interface MemoryWakeupParams {\n /** Project slug or absolute path. Omit to auto-detect from process.cwd(). */\n project?: string;\n /** L1 token budget. Default 800. */\n token_budget?: number;\n}\n\n// ---------------------------------------------------------------------------\n// Tool handler\n// ---------------------------------------------------------------------------\n\nexport async function toolMemoryWakeup(\n registry: RegistryBackend,\n params: MemoryWakeupParams\n): Promise<ToolResult> {\n try {\n const tokenBudget = params.token_budget ?? DEFAULT_TOKEN_BUDGET;\n\n // Resolve the project root path\n let rootPath: string | undefined;\n\n if (params.project) {\n // Try slug lookup first\n const bySlug = await registry.getProjectBySlug(params.project);\n\n if (bySlug) {\n rootPath = bySlug.root_path;\n } else {\n // Maybe it's an absolute path — try path-based detect\n const detected = await detectProjectFromPath(registry, params.project);\n if (detected) rootPath = detected.root_path;\n }\n } else {\n // Auto-detect from cwd\n const detected = await detectProjectFromPath(registry, process.cwd());\n if (detected) rootPath = detected.root_path;\n }\n\n const wakeupBlock = buildWakeupContext(rootPath, tokenBudget);\n\n if (!wakeupBlock) {\n return {\n content: [\n {\n type: \"text\",\n text: \"No wake-up context available. Create ~/.pai/identity.txt for L0 identity, or ensure session notes exist for L1 story.\",\n },\n ],\n };\n }\n\n return {\n content: [\n {\n type: \"text\",\n text: `WAKEUP CONTEXT\\n\\n${wakeupBlock}`,\n },\n ],\n };\n } catch (e) {\n return {\n content: [{ type: \"text\", text: `Wakeup context error: ${String(e)}` }],\n isError: true,\n };\n }\n}\n","/**\n * getTaxonomy — returns the shape of stored memory without requiring a query.\n *\n * Answers \"what do I know about?\" not \"what do I know about X?\"\n *\n * Uses the registry DB (projects, sessions) and the storage backend\n * (memory_files, memory_chunks) to build a structural overview.\n */\n\nimport type { StorageBackend } from \"../storage/interface.js\";\nimport type { RegistryBackend } from \"../storage/registry-interface.js\";\n\n// ---------------------------------------------------------------------------\n// Return types\n// ---------------------------------------------------------------------------\n\nexport interface TaxonomyProject {\n slug: string;\n display_name: string;\n session_count: number;\n note_count: number;\n last_activity: string | null; // ISO date string, e.g. \"2026-04-07\"\n top_tags: string[]; // project tags from the registry\n}\n\nexport interface TaxonomyTotals {\n projects: number;\n sessions: number;\n notes: number;\n chunks: number;\n}\n\nexport interface TaxonomyRecentActivity {\n project_slug: string;\n action: string;\n timestamp: string; // ISO date string\n}\n\nexport interface TaxonomyResult {\n projects: TaxonomyProject[];\n totals: TaxonomyTotals;\n recent_activity: TaxonomyRecentActivity[];\n}\n\n// ---------------------------------------------------------------------------\n// Options\n// ---------------------------------------------------------------------------\n\nexport interface TaxonomyOptions {\n /** Include archived projects. Default: false. */\n include_archived?: boolean;\n /** Maximum projects to return. Default: 50. */\n limit?: number;\n}\n\n// ---------------------------------------------------------------------------\n// Main function\n// ---------------------------------------------------------------------------\n\n/**\n * Build a taxonomy of stored memory — what projects exist, how much is stored,\n * and what has been active recently.\n *\n * Registry queries (projects, sessions) go through RegistryBackend (async).\n * Storage backend queries (files, chunks) are async.\n */\nexport async function getTaxonomy(\n registry: RegistryBackend,\n storage: StorageBackend,\n options: TaxonomyOptions = {}\n): Promise<TaxonomyResult> {\n const includeArchived = options.include_archived ?? false;\n const limit = options.limit ?? 50;\n\n // -------------------------------------------------------------------------\n // 1. Load all (active) projects from the registry\n // -------------------------------------------------------------------------\n\n const projectRows = includeArchived\n ? await registry.listProjects({ orderBy: \"updated_desc\", limit })\n : await registry.listProjects({ status: \"active\", orderBy: \"updated_desc\", limit });\n\n if (projectRows.length === 0) {\n return {\n projects: [],\n totals: { projects: 0, sessions: 0, notes: 0, chunks: 0 },\n recent_activity: [],\n };\n }\n\n const projectIds = projectRows.map((p) => p.id);\n const projectIdSet = new Set(projectIds);\n\n // -------------------------------------------------------------------------\n // 2. Session counts per project (registry, async)\n // -------------------------------------------------------------------------\n\n const sessionCountsByProject = new Map<number, number>();\n const lastSessionDateByProject = new Map<number, string | null>();\n\n for (const projectId of projectIds) {\n sessionCountsByProject.set(projectId, await registry.countSessionsForProject(projectId));\n lastSessionDateByProject.set(projectId, await registry.getMostRecentSessionDate(projectId));\n }\n\n // -------------------------------------------------------------------------\n // 3. Tags per project (registry, async)\n // -------------------------------------------------------------------------\n\n const tagsByProject = new Map<number, string[]>();\n\n for (const projectId of projectIds) {\n tagsByProject.set(projectId, await registry.listTagsForProject(projectId));\n }\n\n // -------------------------------------------------------------------------\n // 4. Note and chunk counts per project (storage backend, async)\n // memory_files (one row per indexed file) and memory_chunks (many per\n // file), via StorageBackend.getProjectStats() — same on both backends.\n // -------------------------------------------------------------------------\n\n const noteCountsByProject = new Map<number, number>();\n const chunkCountsByProject = new Map<number, number>();\n\n for (const projectId of projectIds) {\n const projectStats = await storage.getProjectStats(projectId);\n noteCountsByProject.set(projectId, projectStats.files);\n chunkCountsByProject.set(projectId, projectStats.chunks);\n }\n\n // -------------------------------------------------------------------------\n // 5. Global totals\n // -------------------------------------------------------------------------\n\n const stats = await storage.getStats();\n\n const totalProjects = includeArchived\n ? await registry.countProjects()\n : await registry.countProjects({ status: \"active\" });\n\n // No RegistryBackend method for a global session count (flagged in the\n // report) — approximate as the sum over the projects already fetched\n // above (bounded by `limit`), the closest available without raw SQL.\n const totalSessions = [...sessionCountsByProject.values()].reduce((a, b) => a + b, 0);\n\n // -------------------------------------------------------------------------\n // 6. Recent activity — last 10 sessions across all (visible) projects\n // No RegistryBackend method filters sessions by their project's status\n // directly (flagged in the report) — over-fetch and filter client-side\n // against the project set already resolved above.\n // -------------------------------------------------------------------------\n\n const recentSessions = (await registry.listSessions({ limit: Math.max(50, limit) }))\n .filter((s) => projectIdSet.has(s.project_id))\n .slice(0, 10);\n\n const recentActivity: TaxonomyRecentActivity[] = recentSessions.map((row) => ({\n project_slug: row.project_slug,\n action: `session: ${row.title || \"(untitled)\"}`,\n timestamp: row.date,\n }));\n\n // -------------------------------------------------------------------------\n // 7. Assemble result\n // -------------------------------------------------------------------------\n\n const projects: TaxonomyProject[] = projectRows.map((row) => ({\n slug: row.slug,\n display_name: row.display_name,\n session_count: sessionCountsByProject.get(row.id) ?? 0,\n note_count: noteCountsByProject.get(row.id) ?? 0,\n last_activity: lastSessionDateByProject.get(row.id) ?? null,\n top_tags: tagsByProject.get(row.id) ?? [],\n }));\n\n return {\n projects,\n totals: {\n projects: totalProjects,\n sessions: totalSessions,\n notes: stats.files,\n chunks: stats.chunks,\n },\n recent_activity: recentActivity,\n };\n}\n","/**\n * MCP tool handler: memory_taxonomy\n *\n * Returns the SHAPE of stored memory without requiring a query.\n * Answers \"what do I know about?\" not \"what do I know about X?\"\n */\n\nimport type { StorageBackend } from \"../../storage/interface.js\";\nimport type { RegistryBackend } from \"../../storage/registry-interface.js\";\nimport { getTaxonomy } from \"../../memory/taxonomy.js\";\nimport type { ToolResult } from \"./types.js\";\n\n// ---------------------------------------------------------------------------\n// Tool: memory_taxonomy\n// ---------------------------------------------------------------------------\n\nexport interface MemoryTaxonomyParams {\n /** Include archived projects in the result. Default: false. */\n include_archived?: boolean;\n /** Maximum number of projects to return. Default: 50. */\n limit?: number;\n}\n\nexport async function toolMemoryTaxonomy(\n registry: RegistryBackend,\n storage: StorageBackend,\n params: MemoryTaxonomyParams = {}\n): Promise<ToolResult> {\n try {\n const result = await getTaxonomy(registry, storage, {\n include_archived: params.include_archived,\n limit: params.limit,\n });\n\n // -----------------------------------------------------------------------\n // Format output as human-readable text\n // -----------------------------------------------------------------------\n\n const lines: string[] = [];\n\n // Totals header\n lines.push(\n `PAI Memory Taxonomy — ${result.totals.projects} project(s), ` +\n `${result.totals.sessions} session(s), ` +\n `${result.totals.notes} indexed file(s), ` +\n `${result.totals.chunks} chunk(s)`\n );\n lines.push(\"\");\n\n // Per-project breakdown\n if (result.projects.length === 0) {\n lines.push(\"No active projects found.\");\n } else {\n lines.push(\"Projects:\");\n for (const p of result.projects) {\n const tagStr = p.top_tags.length > 0 ? ` [${p.top_tags.join(\", \")}]` : \"\";\n const activityStr = p.last_activity ? ` last: ${p.last_activity}` : \"\";\n lines.push(\n ` ${p.slug} — ${p.display_name}` +\n ` sessions=${p.session_count}` +\n (p.note_count > 0 ? ` files=${p.note_count}` : \"\") +\n activityStr +\n tagStr\n );\n }\n }\n\n // Recent activity\n if (result.recent_activity.length > 0) {\n lines.push(\"\");\n lines.push(\"Recent activity (last 10 sessions across all projects):\");\n for (const a of result.recent_activity) {\n lines.push(` ${a.timestamp} ${a.project_slug} ${a.action}`);\n }\n }\n\n return {\n content: [{ type: \"text\", text: lines.join(\"\\n\") }],\n };\n } catch (e) {\n return {\n content: [{ type: \"text\", text: `memory_taxonomy error: ${String(e)}` }],\n isError: true,\n };\n }\n}\n","/**\n * tunnels.ts — cross-project concept detection (\"palace graph / tunnel detection\")\n *\n * A \"tunnel\" is a concept (word or short phrase) that appears in chunks from\n * at least two distinct projects. These serendipitous cross-project connections\n * are surfaced so the user can discover unexpected relationships between their\n * work streams.\n *\n * The actual SQL (SQLite FTS5 vocab / Postgres ts_stat) lives under\n * src/storage/{sqlite,postgres}/tunnels.ts, reached through\n * StorageBackend.findTunnels() — this module just re-exports the public\n * types and forwards the call, so existing callers (MCP tools) don't have\n * to change their import path.\n */\n\nimport type { StorageBackend } from \"../storage/interface.js\";\nimport type { RegistryBackend } from \"../storage/registry-interface.js\";\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\nexport interface Tunnel {\n /** The shared term or phrase. */\n concept: string;\n /** Project slugs where this concept appears. */\n projects: string[];\n /** Total chunk occurrences across all projects. */\n occurrences: number;\n /** First time the concept appeared (Unix ms). */\n first_seen: number;\n /** Most recent time the concept appeared (Unix ms). */\n last_seen: number;\n}\n\nexport interface FindTunnelsOptions {\n /** Minimum distinct projects a concept must appear in. Default 2. */\n min_projects?: number;\n /** Minimum total chunk occurrences across all projects. Default 3. */\n min_occurrences?: number;\n /** Maximum number of tunnels to return. Default 20. */\n limit?: number;\n}\n\nexport interface FindTunnelsResult {\n tunnels: Tunnel[];\n projects_analyzed: number;\n total_concepts_evaluated: number;\n}\n\n// ---------------------------------------------------------------------------\n// Public entry point\n// ---------------------------------------------------------------------------\n\n/**\n * Find cross-project concept tunnels.\n *\n * Works with both SQLite and Postgres storage backends.\n * Requires the RegistryBackend for project slug resolution.\n *\n * @param backend Active PAI storage backend.\n * @param registry Registry backend for project slug resolution.\n * @param options Filter and limit options.\n */\nexport async function findTunnels(\n backend: StorageBackend,\n registry: RegistryBackend,\n options?: FindTunnelsOptions\n): Promise<FindTunnelsResult> {\n return backend.findTunnels(registry, options);\n}\n","/**\n * MCP tool handler: memory_tunnels\n *\n * Finds \"tunnels\" — concepts that appear across multiple projects —\n * surfacing cross-project serendipitous connections in the PAI memory graph.\n */\n\nimport type { StorageBackend } from \"../../storage/interface.js\";\nimport type { RegistryBackend } from \"../../storage/registry-interface.js\";\nimport type { ToolResult } from \"./types.js\";\nimport { findTunnels } from \"../../memory/tunnels.js\";\n\n// ---------------------------------------------------------------------------\n// Tool: memory_tunnels\n// ---------------------------------------------------------------------------\n\nexport interface MemoryTunnelsParams {\n /** Minimum distinct projects a concept must appear in. Default 2. */\n min_projects?: number;\n /** Minimum total chunk occurrences across all projects. Default 3. */\n min_occurrences?: number;\n /** Maximum number of tunnels to return. Default 20. */\n limit?: number;\n}\n\nexport async function toolMemoryTunnels(\n registry: RegistryBackend,\n backend: StorageBackend,\n params: MemoryTunnelsParams\n): Promise<ToolResult> {\n try {\n const result = await findTunnels(backend, registry, {\n min_projects: params.min_projects,\n min_occurrences: params.min_occurrences,\n limit: params.limit,\n });\n\n return {\n content: [{ type: \"text\", text: JSON.stringify(result, null, 2) }],\n };\n } catch (e) {\n return {\n content: [{ type: \"text\", text: `memory_tunnels error: ${String(e)}` }],\n isError: true,\n };\n }\n}\n","/**\n * MCP tool handler: memory_feedback\n *\n * Implements the MR2 feedback weight loop:\n * - Accepts chunk IDs and a rating (1-5)\n * - Applies EMA update to memory_chunks.relevance_score\n * - Finds entity names mentioned in those chunks and updates kg_entities.feedback_weight\n *\n * EMA formula: new = old + alpha * (normalized_rating - old)\n * where normalized_rating = (rating - 1) / 4 → maps [1,5] to [0,1]\n * and alpha = 0.1\n *\n * Integration into search scoring:\n * final_score *= (0.5 + relevance_score)\n * This multiplier ranges from 0.5 (relevance_score=0) to 1.5 (relevance_score=1),\n * giving a ±50% boost/penalty based on accumulated feedback.\n */\n\nimport type { ToolResult } from \"./types.js\";\nimport type { StorageBackend } from \"../../storage/interface.js\";\n\n// EMA learning rate\nconst FEEDBACK_ALPHA = 0.1;\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface MemoryFeedbackParams {\n /** Array of chunk IDs to apply feedback to */\n chunk_ids: string[];\n /** Rating from 1 (not relevant) to 5 (highly relevant) */\n rating: number;\n /** Optional tenant ID for entity feedback scoping. Default: \"default\" */\n tenant_id?: string;\n}\n\n// ---------------------------------------------------------------------------\n// Tool handler\n// ---------------------------------------------------------------------------\n\n/**\n * Apply relevance feedback to memory chunks and associated KG entities.\n * Both memory_chunks.relevance_score and kg_entities.feedback_weight go\n * through the passed StorageBackend, so this works on either backend.\n *\n * @param backend StorageBackend for both the chunk and kg_entities portions\n * @param params Feedback parameters\n */\nexport async function toolMemoryFeedback(\n backend: StorageBackend,\n params: MemoryFeedbackParams\n): Promise<ToolResult> {\n try {\n if (!Array.isArray(params.chunk_ids) || params.chunk_ids.length === 0) {\n return {\n content: [{ type: \"text\", text: \"memory_feedback error: chunk_ids must be a non-empty array\" }],\n isError: true,\n };\n }\n\n const rating = params.rating;\n if (typeof rating !== \"number\" || rating < 1 || rating > 5) {\n return {\n content: [{ type: \"text\", text: \"memory_feedback error: rating must be a number between 1 and 5\" }],\n isError: true,\n };\n }\n\n // Normalize rating [1,5] → [0,1]\n const normalizedRating = (rating - 1) / 4;\n\n // Fetch current relevance scores for the given chunk IDs\n const chunks = await backend.getChunksForFeedback(params.chunk_ids);\n\n if (chunks.length === 0) {\n return {\n content: [{ type: \"text\", text: \"memory_feedback: no matching chunks found\" }],\n isError: true,\n };\n }\n\n let updatedChunks = 0;\n const combinedText: string[] = [];\n\n // Apply EMA update to each chunk's relevance_score\n for (const chunk of chunks) {\n const oldScore = chunk.relevanceScore ?? 0.5;\n const newScore = oldScore + FEEDBACK_ALPHA * (normalizedRating - oldScore);\n await backend.updateChunkRelevanceScore(chunk.id, newScore);\n updatedChunks++;\n combinedText.push(chunk.text);\n }\n\n // Find entity mentions in updated chunks and apply EMA to kg_entities.feedback_weight\n const tenantId = params.tenant_id ?? \"default\";\n const entities = await backend.listKgEntities(tenantId, undefined, 500);\n const text = combinedText.join(\"\\n\").toLowerCase();\n\n let updatedEntities = 0;\n for (const entity of entities) {\n if (text.includes(entity.name.toLowerCase())) {\n await backend.updateEntityFeedbackWeight(entity.entity_id, normalizedRating, FEEDBACK_ALPHA);\n updatedEntities++;\n }\n }\n\n return {\n content: [\n {\n type: \"text\",\n text: JSON.stringify({\n updated_chunks: updatedChunks,\n updated_entities: updatedEntities,\n rating,\n normalized_rating: normalizedRating,\n }),\n },\n ],\n };\n } catch (e) {\n return {\n content: [{ type: \"text\", text: `memory_feedback error: ${String(e)}` }],\n isError: true,\n };\n }\n}\n","/**\n * kg-search.ts — Graph-completion retrieval (MR1).\n *\n * Implements graphCompletionSearch: a multi-phase retrieval strategy that\n * combines vector search with knowledge-graph neighborhood expansion.\n *\n * Algorithm:\n * Phase 1: Wide vector search (50 candidates) using queryVec\n * Phase 2: Extract entity mentions from retrieved chunks (substring match vs kg_entities)\n * Phase 3: BFS neighborhood expansion in kg_triples (1-2 hops from matched entities)\n * Phase 4: Re-rank all collected triples against query embedding using cosine similarity\n *\n * The result is a ranked list of KgTriple objects most relevant to the query,\n * augmented with graph-derived context that pure vector search would miss.\n */\n\nimport { cosineSimilarity, deserializeEmbedding } from \"./embeddings.js\";\nimport type { SearchResult, SearchOptions } from \"./search.js\";\nimport type { KgTriple } from \"./kg.js\";\nimport type { StorageBackend } from \"../storage/interface.js\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface GraphCompletionResult {\n /** Ranked KG triples relevant to the query */\n triples: Array<KgTriple & { relevanceScore: number }>;\n /** Chunks from the initial wide vector search (Phase 1) */\n seedChunks: SearchResult[];\n /** Entity names that were matched and used for BFS expansion */\n expandedEntities: string[];\n}\n\nexport interface GraphCompletionOptions {\n /** Number of seed chunks to fetch in Phase 1. Default: 50 */\n seedCount?: number;\n /** BFS hop depth for neighborhood expansion. Default: 1 */\n hops?: number;\n /** Maximum triples to return after re-ranking. Default: 20 */\n maxTriples?: number;\n /** Postgres project_id filter for triples. */\n projectId?: number;\n /** Tenant ID for entity lookup in the storage backend. Default: \"default\" */\n tenantId?: string;\n /** StorageBackend search options to pass to Phase 1 */\n searchOpts?: SearchOptions;\n}\n\n// ---------------------------------------------------------------------------\n// Phase 2: Entity mention extraction\n// ---------------------------------------------------------------------------\n\n/**\n * Find entity names (from the kg_entities table) that appear\n * as substrings in the given text chunks.\n *\n * Returns a deduplicated list of matching entity names.\n */\nasync function extractEntityMentions(\n federation: StorageBackend,\n chunks: SearchResult[],\n tenantId = \"default\"\n): Promise<string[]> {\n // Load all entity names for this tenant (sorted by length desc so longer\n // entity names are matched before substrings)\n const entities = await federation.listKgEntities(tenantId, undefined, 500);\n if (entities.length === 0) return [];\n\n const combinedText = chunks.map((c) => c.snippet).join(\"\\n\").toLowerCase();\n const matched = new Set<string>();\n\n for (const entity of entities) {\n if (combinedText.includes(entity.name.toLowerCase())) {\n matched.add(entity.name);\n }\n }\n\n return Array.from(matched);\n}\n\n// ---------------------------------------------------------------------------\n// Phase 3: BFS neighborhood expansion\n// ---------------------------------------------------------------------------\n\n/**\n * Perform BFS over kg_triples to find neighbors of the given entity names.\n *\n * For each entity name, fetches triples where the entity is the subject\n * or the object (bi-directional expansion). Supports 1 or 2 hop depths.\n *\n * Returns all unique triples found in the BFS expansion.\n */\nasync function bfsExpand(\n federation: StorageBackend,\n entityNames: string[],\n hops: number,\n projectId?: number\n): Promise<KgTriple[]> {\n const seen = new Set<number>(); // triple IDs already included\n const tripleMap = new Map<number, KgTriple>();\n const frontier = new Set(entityNames);\n\n for (let hop = 0; hop < hops; hop++) {\n const nextFrontier = new Set<string>();\n\n for (const name of frontier) {\n // Expand as subject\n const asSubject = await federation.queryKgTriples({\n subject: name,\n project_id: projectId,\n });\n\n // Expand as object\n const asObject = await federation.queryKgTriples({\n object: name,\n project_id: projectId,\n });\n\n for (const triple of [...asSubject, ...asObject]) {\n if (!seen.has(triple.id)) {\n seen.add(triple.id);\n tripleMap.set(triple.id, triple);\n // Add neighbors to next frontier for deeper hops\n nextFrontier.add(triple.subject);\n nextFrontier.add(triple.object);\n }\n }\n }\n\n // Next hop frontier: only new names not already processed\n frontier.clear();\n for (const name of nextFrontier) {\n if (!entityNames.includes(name)) {\n frontier.add(name);\n entityNames.push(name);\n }\n }\n }\n\n return Array.from(tripleMap.values());\n}\n\n// ---------------------------------------------------------------------------\n// Phase 4: Re-rank triples against query embedding\n// ---------------------------------------------------------------------------\n\n/**\n * Score each triple by cosine similarity to the query embedding.\n *\n * The triple text representation is: \"subject predicate object\"\n * We embed this on-the-fly using generateEmbedding(), which may use a cache.\n *\n * Returns triples sorted by relevance score descending.\n */\nasync function rerankTriples(\n triples: KgTriple[],\n queryVec: Float32Array,\n maxTriples: number\n): Promise<Array<KgTriple & { relevanceScore: number }>> {\n if (triples.length === 0) return [];\n\n const { generateEmbedding } = await import(\"./embeddings.js\");\n\n const scored = await Promise.all(\n triples.map(async (triple) => {\n const text = `${triple.subject} ${triple.predicate} ${triple.object}`;\n try {\n const vec = await generateEmbedding(text, false);\n const score = cosineSimilarity(queryVec, vec);\n return { ...triple, relevanceScore: score };\n } catch {\n return { ...triple, relevanceScore: 0 };\n }\n })\n );\n\n return scored\n .sort((a, b) => b.relevanceScore - a.relevanceScore)\n .slice(0, maxTriples);\n}\n\n// ---------------------------------------------------------------------------\n// Main: graphCompletionSearch\n// ---------------------------------------------------------------------------\n\n/**\n * Graph-completion retrieval combining vector search with KG neighborhood BFS.\n *\n * @param federation Active StorageBackend (for chunk/entity/kg_triples lookup)\n * @param queryVec Pre-computed embedding for the search query\n * @param opts Configuration options\n */\nexport async function graphCompletionSearch(\n federation: StorageBackend,\n queryVec: Float32Array,\n opts: GraphCompletionOptions = {}\n): Promise<GraphCompletionResult> {\n const {\n seedCount = 50,\n hops = 1,\n maxTriples = 20,\n projectId,\n tenantId = \"default\",\n searchOpts,\n } = opts;\n\n // Phase 1: Wide vector search for seed chunks\n const seedSearchOpts: SearchOptions = {\n ...searchOpts,\n maxResults: seedCount,\n projectIds: projectId != null ? [projectId] : searchOpts?.projectIds,\n };\n\n let seedChunks: SearchResult[] = [];\n try {\n seedChunks = await federation.searchSemantic(queryVec, seedSearchOpts);\n } catch (e) {\n process.stderr.write(`[kg-search] Phase 1 seed search error: ${e}\\n`);\n }\n\n if (seedChunks.length === 0) {\n return { triples: [], seedChunks: [], expandedEntities: [] };\n }\n\n // Phase 2: Extract entity mentions from seed chunks\n const expandedEntities = await extractEntityMentions(federation, seedChunks, tenantId);\n\n if (expandedEntities.length === 0) {\n // No entities matched — return empty graph results (seed chunks still useful)\n return { triples: [], seedChunks, expandedEntities: [] };\n }\n\n // Phase 3: BFS neighborhood expansion in kg_triples\n let expandedTriples: KgTriple[] = [];\n try {\n expandedTriples = await bfsExpand(federation, [...expandedEntities], hops, projectId);\n } catch (e) {\n process.stderr.write(`[kg-search] Phase 3 BFS expansion error: ${e}\\n`);\n }\n\n if (expandedTriples.length === 0) {\n return { triples: [], seedChunks, expandedEntities };\n }\n\n // Phase 4: Re-rank triples against query embedding\n let rankedTriples: Array<KgTriple & { relevanceScore: number }> = [];\n try {\n rankedTriples = await rerankTriples(expandedTriples, queryVec, maxTriples);\n } catch (e) {\n process.stderr.write(`[kg-search] Phase 4 rerank error: ${e}\\n`);\n // Fall back to unranked triples\n rankedTriples = expandedTriples.slice(0, maxTriples).map((t) => ({\n ...t,\n relevanceScore: 0,\n }));\n }\n\n return { triples: rankedTriples, seedChunks, expandedEntities };\n}\n","/**\n * MCP tool handler: memory_kg_search\n *\n * Exposes graphCompletionSearch as an MCP tool (MR1 — graph-completion retrieval).\n *\n * Algorithm: wide vector search → entity mention extraction → BFS neighborhood\n * expansion in kg_triples → re-rank triples by cosine similarity to query.\n *\n * Requires the storage backend for chunk/entity lookups and kg_triples BFS\n * expansion.\n *\n * Returns ranked KG triples with relevanceScore, plus seed chunk metadata.\n */\n\nimport type { ToolResult } from \"./types.js\";\nimport type { StorageBackend } from \"../../storage/interface.js\";\nimport { graphCompletionSearch } from \"../../memory/kg-search.js\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface MemoryKgSearchParams {\n /** Free-text query — used to generate embedding for Phase 1 vector search. */\n query: string;\n /** Restrict seed vector search to a specific project ID. Optional. */\n project_id?: number;\n /** Number of seed chunks from Phase 1 vector search. Default: 50 */\n wide_k?: number;\n /** BFS hop depth for KG neighborhood expansion. Default: 1 */\n neighborhood_depth?: number;\n /** Maximum triples to return after Phase 4 re-ranking. Default: 20 */\n top_k?: number;\n /** Tenant ID for entity lookup in the storage backend. Default: \"default\" */\n tenant_id?: string;\n}\n\n// ---------------------------------------------------------------------------\n// Tool handler\n// ---------------------------------------------------------------------------\n\n/**\n * Graph-completion search: vector search + KG neighborhood BFS.\n *\n * @param federation Active StorageBackend for chunk/entity/kg_triples operations\n * @param params Tool parameters\n */\nexport async function toolMemoryKgSearch(\n federation: StorageBackend,\n params: MemoryKgSearchParams\n): Promise<ToolResult> {\n try {\n if (!params.query || typeof params.query !== \"string\" || params.query.trim() === \"\") {\n return {\n content: [{ type: \"text\", text: \"memory_kg_search error: query is required\" }],\n isError: true,\n };\n }\n\n const { generateEmbedding } = await import(\"../../memory/embeddings.js\");\n const queryVec = await generateEmbedding(params.query, true);\n\n const result = await graphCompletionSearch(federation, queryVec, {\n seedCount: params.wide_k ?? 50,\n hops: params.neighborhood_depth ?? 1,\n maxTriples: params.top_k ?? 20,\n projectId: params.project_id,\n tenantId: params.tenant_id ?? \"default\",\n });\n\n if (result.triples.length === 0) {\n return {\n content: [\n {\n type: \"text\",\n text: `No KG triples found for query: \"${params.query}\"\\n` +\n `Seed chunks: ${result.seedChunks.length}, ` +\n `Expanded entities: ${result.expandedEntities.length}`,\n },\n ],\n };\n }\n\n const tripleLines = result.triples\n .map((t, i) =>\n `[${i + 1}] (score=${t.relevanceScore.toFixed(4)}) ${t.subject} — ${t.predicate} — ${t.object}`\n )\n .join(\"\\n\");\n\n const entityList = result.expandedEntities.slice(0, 10).join(\", \");\n const summary = [\n `Found ${result.triples.length} KG triple(s) for \"${params.query}\"`,\n `Seed chunks: ${result.seedChunks.length}`,\n `Expanded entities (${result.expandedEntities.length}): ${entityList}`,\n \"\",\n tripleLines,\n ].join(\"\\n\");\n\n return {\n content: [{ type: \"text\", text: summary }],\n };\n } catch (e) {\n return {\n content: [{ type: \"text\", text: `memory_kg_search error: ${String(e)}` }],\n isError: true,\n };\n }\n}\n"],"mappings":";;;;;;;;;;;AA2BA,eAAsB,gBACpB,UACA,MACwB;CACxB,MAAM,SAAS,MAAM,SAAS,iBAAiB,KAAK;AACpD,KAAI,OAAQ,QAAO,OAAO;AAG1B,QADkB,MAAM,SAAS,aAAa,KAAK,IAC/B;;AAOtB,eAAsB,sBACpB,UACA,QAC4B;CAC5B,MAAM,WAAW,QAAQ,OAAO;CAEhC,MAAM,QAAQ,MAAM,SAAS,qBAAqB,SAAS;AAC3D,KAAI,MAAO,QAAO;CAElB,MAAM,MAAM,MAAM,SAAS,8BAA8B;AAEzD,MAAK,MAAM,WAAW,IACpB,KACE,SAAS,WAAW,QAAQ,YAAY,IAAI,IAC5C,aAAa,QAAQ,UAErB,QAAO;AAIX,QAAO;;AAOT,eAAsB,cAAc,UAA2B,SAAsC;CACnG,MAAM,eAAe,MAAM,SAAS,wBAAwB,QAAQ,GAAG;CACvE,MAAM,kBAAkB,MAAM,SAAS,yBAAyB,QAAQ,GAAG;CAC3E,MAAM,OAAO,MAAM,SAAS,mBAAmB,QAAQ,GAAG;CAC1D,MAAM,UAAU,MAAM,SAAS,sBAAsB,QAAQ,GAAG;CAEhE,MAAM,QAAkB;EACtB,SAAS,QAAQ;EACjB,iBAAiB,QAAQ;EACzB,cAAc,QAAQ;EACtB,SAAS,QAAQ;EACjB,WAAW,QAAQ;EACnB,aAAa;EACd;AAED,KAAI,gBAAiB,OAAM,KAAK,iBAAiB,kBAAkB;AACnE,KAAI,KAAK,OAAQ,OAAM,KAAK,SAAS,KAAK,KAAK,KAAK,GAAG;AACvD,KAAI,QAAQ,OAAQ,OAAM,KAAK,YAAY,QAAQ,KAAK,KAAK,GAAG;AAChE,KAAI,QAAQ,cAAe,OAAM,KAAK,kBAAkB,QAAQ,gBAAgB;AAChF,KAAI,QAAQ,YACV,OAAM,KACJ,gBAAgB,IAAI,KAAK,QAAQ,YAAY,CAAC,aAAa,CAAC,MAAM,GAAG,GAAG,GACzE;AAGH,QAAO,MAAM,KAAK,KAAK;;;;;;;;ACvDzB,eAAsB,iBACpB,UACA,YACA,QACA,gBACqB;AACrB,KAAI;EACF,MAAM,aAAmC,OAAO,UAC5C,OAAO,YAAY;GACjB,MAAM,KAAK,MAAM,gBAAgB,UAAU,OAAO,QAAS;AAC3D,UAAO,MAAM,OAAO,CAAC,GAAG,GAAG,EAAE;MAC3B,GACJ;AAEJ,MAAI,OAAO,YAAY,CAAC,cAAc,WAAW,WAAW,GAC1D,QAAO;GACL,SAAS,CACP;IAAE,MAAM;IAAQ,MAAM,sBAAsB,OAAO;IAAW,CAC/D;GACD,SAAS;GACV;EAMH,MAAM,OAAO,OAAO,QAAS,gBAAgB,QAAQ;EAIrD,MAAM,gBAAgB,OAAO,iBAAkB,gBAAgB,iBAAiB;EAChF,MAAM,aAAa;GACjB;GACA,SAAS,OAAO;GAChB,YAAY,OAAO,SAAU,gBAAgB,gBAAgB;GAC9D;EAED,IAAI;AAEJ,MAAI,SAAS,UACX,WAAU,MAAM,WAAW,cAAc,OAAO,OAAO,WAAW;WACzD,SAAS,cAAc,SAAS,UAAU;GACnD,MAAM,EAAE,sBAAsB,MAAM,OAAO;GAC3C,MAAM,iBAAiB,MAAM,kBAAkB,OAAO,OAAO,KAAK;AAElE,OAAI,SAAS,WACX,WAAU,MAAM,WAAW,eAAe,gBAAgB,WAAW;QAChE;IAEL,MAAM,CAAC,WAAW,cAAc,MAAM,QAAQ,IAAI,CAChD,WAAW,cAAc,OAAO,OAAO;KAAE,GAAG;KAAY,YAAY;KAAI,CAAC,EACzE,WAAW,eAAe,gBAAgB;KAAE,GAAG;KAAY,YAAY;KAAI,CAAC,CAC7E,CAAC;AAEF,cAAU,qBAAqB,WAAW,YAAY,WAAW,cAAc,GAAG;;QAGpF,WAAU,MAAM,WAAW,cAAc,OAAO,OAAO,WAAW;AAapE,MAAI;GACF,MAAM,EAAE,mBAAmB,MAAM,OAAO;GACxC,MAAM,QAAQ,CAAC,GAAG,IAAI,IAAI,QAAQ,KAAK,MAAM,EAAE,KAAK,CAAC,CAAC;AAGtD,OAAI,MAAM,SAAS,GAAG;IACpB,MAAM,QAA2D,EAAE;AACnE,SAAK,MAAM,KAAK,MACd,MAAK,MAAM,KAAK,MAAM,WAAW,iBAAiB,EAAE,EAAE;AACpD,SAAI,EAAE,cAAc,EAAE,eAAe,YAAa;AAClD,WAAM,KAAK;MAAE,YAAY,EAAE;MAAY,YAAY;MAAG,CAAC;;AAG3D,QAAI,MAAM,SAAS,EAAG,WAAU,eAAe,SAAS,MAAM;;UAE1D;AAKR,MAAI;GACF,MAAM,WAAW,QACd,KAAK,MAAM,EAAE,QAAQ,CACrB,QAAQ,OAAqB,MAAM,KAAK;AAC3C,OAAI,SAAS,SAAS,EACpB,OAAM,WAAW,wBAAwB,SAAS;UAE9C;EAKR,MAAM,eAAe,OAAO,UAAW,gBAAgB,UAAU;AACjE,MAAI,gBAAgB,QAAQ,SAAS,GAAG;GACtC,MAAM,EAAE,kBAAkB,MAAM,OAAO;AACvC,aAAU,MAAM,cAAc,OAAO,OAAO,SAAS,EACnD,MAAM,WAAW,cAAc,GAChC,CAAC;;EAIJ,MAAM,cAAc,OAAO,gBAAiB,gBAAgB,oBAAoB;AAChF,MAAI,cAAc,KAAK,QAAQ,SAAS,GAAG;GACzC,MAAM,EAAE,sBAAsB,MAAM,OAAO;AAC3C,aAAU,kBAAkB,SAAS,YAAY;;EAGnD,MAAM,YAAY,MAAM,cAAc,SAAS,SAAS;AAExD,MAAI,UAAU,WAAW,EACvB,QAAO,EACL,SAAS,CACP;GACE,MAAM;GACN,MAAM,gCAAgC,OAAO,MAAM,WAAW,KAAK;GACpE,CACF,EACF;EAGH,MAAM,cAAc,eAAe,aAAa;EAChD,MAAM,aAAa,OAAO,WAAW;EACrC,MAAM,YAAY,UACf,KAAK,GAAG,MAAM;GACb,MAAM,OAAO,EAAE,eAAe,WAAW,EAAE;GAC3C,MAAM,SAAS,EAAE,UAAU,OAAO,EAAE,YAAY;AAChD,OAAI,WAEF,QAAO,IAAI,IAAI,EAAE,GAAG,OAAO,GAAG,KAAK,KAAK,EAAE,KAAK,IAAI,EAAE,UAAU,GAAG,EAAE,QAAQ,SAAS,EAAE,MAAM,QAAQ,EAAE;GAEzG,MAAM,SAAS,IAAI,IAAI,EAAE,GAAG,OAAO,GAAG,KAAK,KAAK,EAAE,KAAK,UAAU,EAAE,UAAU,GAAG,EAAE,QAAQ,UAAU,EAAE,MAAM,QAAQ,EAAE,CAAC,QAAQ,EAAE,KAAK,UAAU,EAAE;GAGlJ,MAAM,MAAM,EAAE,QAAQ,MAAM;AAI5B,UAAO,GAAG,OAAO,IAHD,IAAI,SAAS,gBACzB,IAAI,MAAM,GAAG,cAAc,GAAG,QAC9B;IAEJ,CACD,KAAK,aAAa,OAAO,cAAc;AAG1C,MAAI;GACF,MAAM,EAAE,oBAAoB,MAAM,OAAO;AACzC,mBAAgB;IACd,OAAO,OAAO;IACd,WAAW,KAAK,KAAK;IACrB,QAAQ;IACR,aAAa,UAAU,MAAM,GAAG,EAAE,CAAC,KAAK,MAAM,EAAE,KAAK;IACrD,eAAe,UAAU,MAAM,GAAG,EAAE,CAAC,KAAK,MAAM,EAAE,QAAQ,MAAM,CAAC,MAAM,GAAG,IAAI,CAAC,CAAC,KAAK,MAAM;IAC3F,aAAa,UAAU;IACxB,CAAC;UACI;AAIR,SAAO,EACL,SAAS,CACP;GACE,MAAM;GACN,MAAM,SAAS,UAAU,OAAO,kBAAkB,OAAO,MAAM,WAAW,OAAO,YAAY,QAAQ;GACtG,CACF,EACF;UACM,GAAG;AACV,SAAO;GACL,SAAS,CAAC;IAAE,MAAM;IAAQ,MAAM,iBAAiB,OAAO,EAAE;IAAI,CAAC;GAC/D,SAAS;GACV;;;;;;;;AAoBL,eAAe,mBACb,UACA,aACA,eAC4C;CAC5C,MAAM,QAAQ,UAA8B;EAC1C,SAAS,CAAC;GAAE,MAAM;GAAQ;GAAM,CAAC;EACjC,SAAS;EACV;CAED,MAAM,YAAY,MAAM,gBAAgB,UAAU,YAAY;AAC9D,KAAI,aAAa,KAAM,QAAO,KAAK,sBAAsB,cAAc;CAEvE,MAAM,UAAU,MAAM,SAAS,eAAe,UAAU;AACxD,KAAI,CAAC,QAAS,QAAO,KAAK,sBAAsB,cAAc;AAE9D,KAAI,cAAc,SAAS,KAAK,IAAI,WAAW,cAAc,CAC3D,QAAO,KACL,iBAAiB,cAAc,oEAChC;CAGH,MAAM,WAAW,KAAK,QAAQ,WAAW,cAAc;CACvD,MAAM,eAAe,QAAQ,SAAS;CACtC,MAAM,eAAe,QAAQ,QAAQ,UAAU;AAE/C,KAAI,CAAC,aAAa,WAAW,eAAe,IAAI,IAAI,iBAAiB,aACnE,QAAO,KAAK,2BAA2B,gBAAgB;AAGzD,KAAI,CAAC,WAAW,SAAS,CACvB,QAAO,KAAK,mBAAmB,cAAc,aAAa,YAAY,GAAG;CAG3E,MAAM,OAAO,SAAS,SAAS;AAC/B,KAAI,KAAK,OAAO,IAAI,OAAO,KACzB,QAAO,EACL,SAAS,CACP;EACE,MAAM;EACN,MAAM,2BAA2B,KAAK,OAAO,OAAO,MAAM,QAAQ,EAAE,CAAC;EACtE,CACF,EACF;AAGH,QAAO,EAAE,UAAU;;AAGrB,eAAsB,cACpB,UACA,QACqB;AACrB,KAAI;EACF,MAAM,WAAW,MAAM,mBAAmB,UAAU,OAAO,SAAS,OAAO,KAAK;AAChF,MAAI,aAAa,SAAU,QAAO;EAClC,MAAM,EAAE,aAAa;EACrB,MAAM,gBAAgB,OAAO;EAG7B,MAAM,WADU,aAAa,UAAU,OAAO,CACrB,MAAM,KAAK;EAEpC,MAAM,YAAY,OAAO,QAAQ,KAAK;EACtC,MAAM,SACJ,OAAO,SAAS,OACZ,KAAK,IAAI,WAAW,OAAO,OAAO,SAAS,OAAO,GAClD,SAAS;EAGf,MAAM,OADgB,SAAS,MAAM,UAAU,OAAO,CAC3B,KAAK,KAAK;AAOrC,SAAO,EACL,SAAS,CAAC;GAAE,MAAM;GAAQ,MAAM,GALhC,OAAO,QAAQ,OACX,GAAG,OAAO,QAAQ,GAAG,cAAc,UAAU,WAAW,EAAE,GAAG,OAAO,MACpE,GAAG,OAAO,QAAQ,GAAG,cAAc,GAGG,MAAM;GAAQ,CAAC,EAC1D;UACM,GAAG;AACV,SAAO;GACL,SAAS,CAAC;IAAE,MAAM;IAAQ,MAAM,eAAe,OAAO,EAAE;IAAI,CAAC;GAC7D,SAAS;GACV;;;AAcL,SAAS,cAAc,OAAsB,UAAkB,QAAQ,GAAG,MAAgB,EAAE,EAAY;AACtG,MAAK,MAAM,KAAK,OAAO;AACrB,MAAI,KACF,GAAG,KAAK,OAAO,MAAM,GAAG,IAAI,OAAO,EAAE,MAAM,CAAC,GAAG,EAAE,MAAM,KAAK,EAAE,UAAU,GAAG,EAAE,QAAQ,IAAI,EAAE,OAAO,GACnG;AACD,MAAI,QAAQ,IAAI,SAAU,eAAc,EAAE,UAAU,UAAU,QAAQ,GAAG,IAAI;;AAE/E,QAAO;;AAGT,eAAsB,kBACpB,UACA,QACqB;AACrB,KAAI;EACF,MAAM,WAAW,MAAM,mBAAmB,UAAU,OAAO,SAAS,OAAO,KAAK;AAChF,MAAI,aAAa,SAAU,QAAO;EAElC,MAAM,UAAU,aAAa,aAAa,SAAS,UAAU,OAAO,CAAC;EACrE,MAAM,OAAO,QAAQ,SACjB,cAAc,SAAS,OAAO,aAAa,SAAS,CAAC,KAAK,KAAK,GAC/D;AAEJ,SAAO,EACL,SAAS,CAAC;GAAE,MAAM;GAAQ,MAAM,GAAG,OAAO,QAAQ,GAAG,OAAO,KAAK,eAAe;GAAQ,CAAC,EAC1F;UACM,GAAG;AACV,SAAO;GACL,SAAS,CAAC;IAAE,MAAM;IAAQ,MAAM,eAAe,OAAO,EAAE;IAAI,CAAC;GAC7D,SAAS;GACV;;;;;;;;AAaL,SAAgB,qBACd,gBACA,iBACA,YACA,gBAAgB,IAChB,iBAAiB,IACD;AAChB,KAAI,eAAe,WAAW,KAAK,gBAAgB,WAAW,EAAG,QAAO,EAAE;CAE1E,MAAM,UAAU,MACd,GAAG,EAAE,UAAU,GAAG,EAAE,KAAK,GAAG,EAAE,UAAU,GAAG,EAAE;CAE/C,SAAS,gBAAgB,OAA4C;AACnE,MAAI,MAAM,WAAW,EAAG,wBAAO,IAAI,KAAK;EACxC,MAAM,MAAM,KAAK,IAAI,GAAG,MAAM,KAAK,MAAM,EAAE,MAAM,CAAC;EAElD,MAAM,QADM,KAAK,IAAI,GAAG,MAAM,KAAK,MAAM,EAAE,MAAM,CAAC,GAC9B;EACpB,MAAM,oBAAI,IAAI,KAAqB;AACnC,OAAK,MAAM,KAAK,MACd,GAAE,IAAI,OAAO,EAAE,EAAE,UAAU,IAAI,KAAK,EAAE,QAAQ,OAAO,MAAM;AAE7D,SAAO;;CAGT,MAAM,SAAS,gBAAgB,eAAe;CAC9C,MAAM,UAAU,gBAAgB,gBAAgB;CAEhD,MAAM,UAAU,IAAI,IAAY,CAC9B,GAAG,eAAe,IAAI,OAAO,EAC7B,GAAG,gBAAgB,IAAI,OAAO,CAC/B,CAAC;CAEF,MAAM,0BAAU,IAAI,KAA2B;AAC/C,MAAK,MAAM,KAAK,CAAC,GAAG,gBAAgB,GAAG,gBAAgB,CACrD,SAAQ,IAAI,OAAO,EAAE,EAAE,EAAE;CAG3B,MAAM,WAA4D,EAAE;AACpE,MAAK,MAAM,OAAO,SAAS;EACzB,MAAM,OAAO,QAAQ,IAAI,IAAI;EAC7B,MAAM,UAAU,OAAO,IAAI,IAAI,IAAI;EACnC,MAAM,WAAW,QAAQ,IAAI,IAAI,IAAI;EACrC,MAAM,gBAAgB,gBAAgB,UAAU,iBAAiB;AACjE,WAAS,KAAK;GAAE,GAAG;GAAM,OAAO;GAAe;GAAe,CAAC;;AAGjE,QAAO,SACJ,MAAM,GAAG,MAAM,EAAE,QAAQ,EAAE,MAAM,CACjC,MAAM,GAAG,WAAW,CACpB,KAAK,EAAE,eAAe,SAAS,GAAG,QAAQ,EAAE;;;;;;;;;ACjZjD,eAAsB,gBACpB,UACA,QACqB;AACrB,KAAI;EACF,IAAI,UAA6B;AAEjC,MAAI,OAAO,MAAM;GACf,MAAM,YAAY,MAAM,gBAAgB,UAAU,OAAO,KAAK;AAC9D,OAAI,aAAa,KACf,WAAU,MAAM,SAAS,eAAe,UAAU;QAIpD,WAAU,MAAM,sBAAsB,UAD1B,QAAQ,KAAK,CAC2B;AAGtD,MAAI,CAAC,QAIH,QAAO;GACL,SAAS,CAAC;IAAE,MAAM;IAAQ,MAJZ,OAAO,OACnB,sBAAsB,OAAO,SAC7B,wDAAwD,QAAQ,KAAK;IAE9B,CAAC;GAC1C,SAAS,CAAC,OAAO;GAClB;AAGH,SAAO,EACL,SAAS,CAAC;GAAE,MAAM;GAAQ,MAAM,MAAM,cAAc,UAAU,QAAQ;GAAE,CAAC,EAC1E;UACM,GAAG;AACV,SAAO;GACL,SAAS,CAAC;IAAE,MAAM;IAAQ,MAAM,uBAAuB,OAAO,EAAE;IAAI,CAAC;GACrE,SAAS;GACV;;;AAcL,eAAsB,gBACpB,UACA,QACqB;AACrB,KAAI;EACF,MAAM,QAAQ,OAAO,SAAS;EAE9B,MAAM,WAAW,MAAM,SAAS,aAAa;GAC3C,QAAQ,OAAO;GACf,SAAS,OAAO;GAChB,SAAS;GACT;GACD,CAAC;AAEF,MAAI,SAAS,WAAW,EACtB,QAAO,EACL,SAAS,CACP;GACE,MAAM;GACN,MAAM;GACP,CACF,EACF;EAGH,MAAM,QAAQ,SAAS,KACpB,MACC,GAAG,EAAE,KAAK,KAAK,EAAE,OAAO,KAAK,EAAE,UAAU,cAAc,IAAI,KAAK,EAAE,WAAW,CAAC,aAAa,CAAC,MAAM,GAAG,GAAG,CAAC,GAC5G;AAED,SAAO,EACL,SAAS,CACP;GACE,MAAM;GACN,MAAM,GAAG,SAAS,OAAO,kBAAkB,MAAM,KAAK,KAAK;GAC5D,CACF,EACF;UACM,GAAG;AACV,SAAO;GACL,SAAS,CAAC;IAAE,MAAM;IAAQ,MAAM,uBAAuB,OAAO,EAAE;IAAI,CAAC;GACrE,SAAS;GACV;;;AAYL,eAAsB,kBACpB,QACqB;AACrB,KAAI;EACF,MAAM,YAAY,MAAM,cAAc,OAAO,IAAI;AAEjD,MAAI,CAAC,UAEH,QAAO,EACL,SAAS,CACP;GACE,MAAM;GACN,MAAM,yCALG,OAAO,OAAO,QAAQ,KAAK,CAKkB;GACvD,CACF,EACF;AAGH,SAAO,EACL,SAAS,CAAC;GAAE,MAAM;GAAQ,MAAM,oBAAoB,UAAU;GAAE,CAAC,EAClE;UACM,GAAG;AACV,SAAO;GACL,SAAS,CACP;IAAE,MAAM;IAAQ,MAAM,yBAAyB,OAAO,EAAE;IAAI,CAC7D;GACD,SAAS;GACV;;;AAYL,eAAsB,kBACpB,UACA,QACqB;AACrB,KAAI;EACF,MAAM,EAAE,YAAY,UAAU,aAAa,aAAa,MAAM,OAC5D;EAEF,MAAM,EACJ,MAAM,UACN,UAAU,iBACR,MAAM,OAAO;EACjB,MAAM,EAAE,YAAY,MAAM,OAAO;EACjC,MAAM,EAAE,WAAW,QAAQ,MAAM,OAAO;EAExC,MAAM,QAAQ,MAAM,SAAS,8BAA8B,EACxD,OAAO,CACP,MAAM,GAAG,MAAM,EAAE,KAAK,cAAc,EAAE,KAAK,CAAC;EAE/C,MAAM,OAAO,SAAS;EACtB,MAAM,iBAAiB,SAAS,MAAM,WAAW,WAAW;EAE5D,SAAS,aAAa,UAAsC;GAC1D,MAAM,OAAO,aAAa,SAAS;AAOnC,UANmB;IACjB,SAAS,MAAM,OAAO,KAAK;IAC3B,SAAS,MAAM,OAAO,MAAM,KAAK;IACjC,SAAS,MAAM,WAAW,KAAK;IAC/B,SAAS,MAAM,YAAY,KAAK;IACjC,CACiB,MAAM,MAAM,SAAS,EAAE,CAAC;;EAG5C,SAAS,eAAe,YAA6B;AACnD,OAAI,CAAC,SAAS,eAAe,CAAE,QAAO;AACtC,OAAI;AACF,SAAK,MAAM,SAAS,YAAY,eAAe,EAAE;AAC/C,SAAI,UAAU,cAAc,CAAC,MAAM,WAAW,WAAW,CAAE;KAC3D,MAAM,OAAO,SAAS,gBAAgB,MAAM;AAC5C,SAAI;AACF,UAAI,CAAC,SAAS,KAAK,CAAC,aAAa,CAAE;aAC7B;AACN;;AAEF,SAAI,SAAS,SAAS,MAAM,QAAQ,CAAC,CAAE,QAAO;;WAE1C;AAGR,UAAO;;EAoBT,SAAS,mBAAmB,UAI1B;AAOA,QAAK,MAAM,OANE;IACX;IACA;IACA;IACA;IACD,EACuB;IACtB,MAAM,OAAO,SAAS,UAAU,IAAI;AACpC,QAAI,SAAS,KAAK,CAChB,KAAI;KACF,MAAM,MAAM,aAAa,MAAM,OAAO;AAEtC,YAAO;MAAE,OAAO;MAAM,MAAM;MAAK,cADb,iBAAiB,KAAK,IAAI;MACc;YACtD;AACN,YAAO;MAAE,OAAO;MAAM,MAAM;MAAK,cAAc;MAAO;;;AAI5D,UAAO;IAAE,OAAO;IAAO,MAAM;IAAM,cAAc;IAAO;;EAG1D,MAAM,UAA0B,KAAK,KAAK,MAAM;GAC9C,MAAM,aAAa,SAAS,EAAE,UAAU;GACxC,IAAI;GACJ,IAAI,gBAA+B;AAEnC,OAAI,WACF,UAAS;QACJ;AACL,oBAAgB,aAAa,EAAE,UAAU,IAAI;AAC7C,aAAS,gBAAgB,UAAU;;GAGrC,MAAM,OAAO,aACT,mBAAmB,EAAE,UAAU,GAC/B;IAAE,OAAO;IAAO,MAAM;IAAM,cAAc;IAAO;AAErD,UAAO;IACL,MAAM,EAAE;IACR,cAAc,EAAE;IAChB,WAAW,EAAE;IACb,QAAQ,EAAE;IACV,MAAM,EAAE;IACR,eAAe,EAAE;IACjB;IACA,gBAAgB;IAChB,kBAAkB,eAAe,EAAE,YAAY;IAC/C;IACD;IACD;EAEF,MAAM,WACJ,CAAC,OAAO,YAAY,OAAO,aAAa,QACpC,UACA,QAAQ,QAAQ,MAAM,EAAE,WAAW,OAAO,SAAS;EAEzD,MAAM,UAAU;GACd,OAAO,KAAK;GACZ,QAAQ,QAAQ,QAAQ,MAAM,EAAE,WAAW,SAAS,CAAC;GACrD,OAAO,QAAQ,QAAQ,MAAM,EAAE,WAAW,QAAQ,CAAC;GACnD,MAAM,QAAQ,QAAQ,MAAM,EAAE,WAAW,OAAO,CAAC;GAClD;AAED,SAAO,EACL,SAAS,CACP;GACE,MAAM;GACN,MAAM,KAAK,UAAU;IAAE;IAAS,UAAU;IAAU,EAAE,MAAM,EAAE;GAC/D,CACF,EACF;UACM,GAAG;AACV,SAAO;GACL,SAAS,CACP;IAAE,MAAM;IAAQ,MAAM,yBAAyB,OAAO,EAAE;IAAI,CAC7D;GACD,SAAS;GACV;;;;;;;AAgBL,MAAM,iBAAiB;CACrB;EAAE,KAAK;EAAuB,OAAO;EAAiB;CACtD;EAAE,KAAK;EAAyB,OAAO;EAAyB;CAChE;EAAE,KAAK;EAAuB,OAAO;EAAiB;CACtD;EAAE,KAAK;EAAuB,OAAO;EAAW;CACjD;;;;;;;;AASD,SAAS,iBAAiB,KAIxB;CACA,MAAM,QAAQ,IAAI,MAAM,KAAK;CAG7B,MAAM,cAAc,MAAM,WACvB,MAAM,EAAE,MAAM,KAAK,cACrB;AAED,KAAI,gBAAgB,GAClB,QAAO;EAAE,iBAAiB;EAAM,aAAa;EAAK,aAAa;EAAO;CAKxE,IAAI,SAAS,MAAM;AACnB,MAAK,IAAI,IAAI,cAAc,GAAG,IAAI,MAAM,QAAQ,KAAK;EACnD,MAAM,UAAU,MAAM,GAAG,MAAM;AAC/B,MAAI,YAAY,SAAU,QAAQ,WAAW,KAAK,IAAI,YAAY,eAAgB;AAChF,YAAS;AACT;;;AAOJ,QAAO;EAAE,iBAHa,MAAM,MAAM,aAAa,OAAO,CAChB,KAAK,KAAK,CAAC,MAAM;EAE7B,aAAa;EAAK,aAAa;EAAM;;AAGjE,eAAsB,gBACpB,UACA,QACqB;AACrB,KAAI;EACF,IAAI;EACJ,IAAI;AAEJ,MAAI,OAAO,SAAS;GAClB,MAAM,YAAY,MAAM,gBAAgB,UAAU,OAAO,QAAQ;AACjE,OAAI,aAAa,KACf,QAAO;IACL,SAAS,CACP;KAAE,MAAM;KAAQ,MAAM,sBAAsB,OAAO;KAAW,CAC/D;IACD,SAAS;IACV;GAGH,MAAM,MAAM,MAAM,SAAS,eAAe,UAAU;AAEpD,OAAI,CAAC,IACH,QAAO;IACL,SAAS,CACP;KAAE,MAAM;KAAQ,MAAM,sBAAsB,OAAO;KAAW,CAC/D;IACD,SAAS;IACV;AAGH,cAAW,IAAI;AACf,iBAAc,IAAI;SACb;GAEL,MAAM,UAAU,MAAM,sBAAsB,UAAU,QAAQ,KAAK,CAAC;AACpE,OAAI,CAAC,QACH,QAAO,EACL,SAAS,CACP;IACE,MAAM;IACN,MAAM,wDAAwD,QAAQ,KAAK,CAAC;IAC7E,CACF,EACF;AAEH,cAAW,QAAQ;AACnB,iBAAc,QAAQ;;AAIxB,OAAK,MAAM,OAAO,gBAAgB;GAChC,MAAM,WAAW,KAAK,UAAU,IAAI,IAAI;AACxC,OAAI,WAAW,SAAS,EAAE;IAExB,MAAM,EAAE,iBAAiB,aAAa,gBAAgB,iBAD1C,aAAa,UAAU,OAAO,CACiC;IAE3E,IAAI;AACJ,QAAI,eAAe,gBAEjB,UAAS;KACP,eAAe,YAAY,GAAG,IAAI;KAClC;KACA;KACA;KACA;KACA;KACA;KACD,CAAC,KAAK,KAAK;QAEZ,UAAS;KACP,eAAe,YAAY,GAAG,IAAI;KAClC;KACA;KACD,CAAC,KAAK,KAAK;AAGd,WAAO,EACL,SAAS,CAAC;KAAE,MAAM;KAAQ,MAAM;KAAQ,CAAC,EAC1C;;;EAKL,MAAM,WAAW,eAAe,KAAK,MAAM,KAAK,SAAS,GAAG,EAAE,MAAM,CAAC,KAAK,KAAK;AAC/E,SAAO,EACL,SAAS,CACP;GACE,MAAM;GACN,MAAM;IACJ,iCAAiC;IACjC;IACA;IACA;IACA;IACA;IACD,CAAC,KAAK,KAAK;GACb,CACF,EACF;UACM,GAAG;AACV,SAAO;GACL,SAAS,CAAC;IAAE,MAAM;IAAQ,MAAM,uBAAuB,OAAO,EAAE;IAAI,CAAC;GACrE,SAAS;GACV;;;;;;AC9cL,eAAsB,gBACpB,UACA,QACqB;AACrB,KAAI;EACF,MAAM,YAAY,MAAM,gBAAgB,UAAU,OAAO,QAAQ;AACjE,MAAI,aAAa,KACf,QAAO;GACL,SAAS,CACP;IAAE,MAAM;IAAQ,MAAM,sBAAsB,OAAO;IAAW,CAC/D;GACD,SAAS;GACV;EAGH,MAAM,QAAQ,OAAO,SAAS;EAC9B,MAAM,WAAW,MAAM,SAAS,aAAa;GAC3C;GACA,QAAQ,OAAO;GACf;GACD,CAAC;AAEF,MAAI,SAAS,WAAW,EACtB,QAAO,EACL,SAAS,CACP;GACE,MAAM;GACN,MAAM,kCAAkC,OAAO;GAChD,CACF,EACF;EAGH,MAAM,QAAQ,SAAS,KACpB,MACC,IAAI,OAAO,EAAE,OAAO,CAAC,SAAS,GAAG,IAAI,CAAC,IAAI,EAAE,KAAK,KAAK,EAAE,OAAO,KAAK,EAAE,MAAM,wBAAwB,EAAE,WACzG;AAED,SAAO,EACL,SAAS,CACP;GACE,MAAM;GACN,MAAM,GAAG,SAAS,OAAO,kBAAkB,OAAO,QAAQ,OAAO,MAAM,KAAK,OAAO;GACpF,CACF,EACF;UACM,GAAG;AACV,SAAO;GACL,SAAS,CAAC;IAAE,MAAM;IAAQ,MAAM,uBAAuB,OAAO,EAAE;IAAI,CAAC;GACrE,SAAS;GACV;;;;;;;;;;;;;;AA0BL,eAAsB,iBACpB,UACA,YACA,QACqB;AACrB,KAAI;EACF,MAAM,EAAE,WAAW,wBAAwB,MAAM,OAAO;EAExD,MAAM,SAAS,MAAM,UACnB,UACA,YACA,OAAO,KACP,OAAO,QACR;AAED,MAAI,CAAC,OAEH,QAAO,EACL,SAAS,CACP;GACE,MAAM;GACN,MAAM;IACJ,+BANO,OAAO,OAAO,QAAQ,KAAK;IAOlC;IACA,2CACG,OAAO,UAAU,sBAAsB;IAC1C;IACA;IACA;IACD,CAAC,KAAK,KAAK;GACb,CACF,EACF;AAGH,SAAO,EACL,SAAS,CAAC;GAAE,MAAM;GAAQ,MAAM,oBAAoB,OAAO;GAAE,CAAC,EAC/D;UACM,GAAG;AACV,SAAO;GACL,SAAS,CAAC;IAAE,MAAM;IAAQ,MAAM,wBAAwB,OAAO,EAAE;IAAI,CAAC;GACtE,SAAS;GACV;;;;;;AC5HL,eAAsB,mBACpB,UACA,QACqB;AACrB,KAAI;EACF,MAAM,WAAW,MAAM,SAAS,eAAe,OAAO,OAAO,GAAG;AAEhE,MAAI,SAAS,WAAW,EACtB,QAAO,EACL,SAAS,CACP;GACE,MAAM;GACN,MAAM,gCAAgC,OAAO,MAAM;GACpD,CACF,EACF;EAGH,MAAM,QAAQ,SAAS,KAAK,MAAM,GAAG,EAAE,KAAK,KAAK,EAAE,OAAO,KAAK,EAAE,YAAY;AAE7E,SAAO,EACL,SAAS,CACP;GACE,MAAM;GACN,MAAM,GAAG,SAAS,OAAO,kBAAkB,OAAO,MAAM,QAAQ,MAAM,KAAK,KAAK;GACjF,CACF,EACF;UACM,GAAG;AACV,SAAO;GACL,SAAS,CACP;IAAE,MAAM;IAAQ,MAAM,0BAA0B,OAAO,EAAE;IAAI,CAC9D;GACD,SAAS;GACV;;;;;;AC7BL,eAAsB,kBACpB,SACA,QACqB;AACrB,KAAI;EACF,MAAM,EAAE,kBAAkB,MAAM,OAAO;EACvC,MAAM,SAAS,MAAM,cAAc,SAAS;GAC1C,WAAW,OAAO;GAClB,OAAO,OAAO;GACd,WAAW,OAAO;GAClB,MAAM,OAAO;GACd,CAAC;AACF,SAAO,EACL,SAAS,CAAC;GAAE,MAAM;GAAQ,MAAM,KAAK,UAAU,QAAQ,MAAM,EAAE;GAAE,CAAC,EACnE;UACM,GAAG;AACV,SAAO;GACL,SAAS,CAAC;IAAE,MAAM;IAAQ,MAAM,yBAAyB,OAAO,EAAE;IAAI,CAAC;GACvE,SAAS;GACV;;;AAeL,eAAsB,iBACpB,SACA,QACqB;AACrB,KAAI;EACF,MAAM,EAAE,iBAAiB,MAAM,OAAO;EACtC,MAAM,SAAS,MAAM,aAAa,SAAS;GACzC,OAAO,OAAO;GACd,aAAa,OAAO;GACpB,YAAY,OAAO;GACnB,SAAS,OAAO;GACjB,CAAC;AACF,SAAO,EACL,SAAS,CAAC;GAAE,MAAM;GAAQ,MAAM,KAAK,UAAU,QAAQ,MAAM,EAAE;GAAE,CAAC,EACnE;UACM,GAAG;AACV,SAAO;GACL,SAAS,CAAC;IAAE,MAAM;IAAQ,MAAM,wBAAwB,OAAO,EAAE;IAAI,CAAC;GACtE,SAAS;GACV;;;AAgBL,eAAsB,mBACpB,SACA,QACqB;AACrB,KAAI;EACF,MAAM,EAAE,mBAAmB,MAAM,OAAO;EACxC,MAAM,UAAU,MAAM,eAAe,SAAS;GAC5C,eAAe,OAAO;GACtB,gBAAgB,OAAO;GACvB,OAAO,OAAO;GACd,eAAe,OAAO;GACtB,kBAAkB,OAAO;GAC1B,CAAC;AACF,SAAO,EACL,SAAS,CAAC;GAAE,MAAM;GAAQ,MAAM,KAAK,UAAU,SAAS,MAAM,EAAE;GAAE,CAAC,EACpE;UACM,GAAG;AACV,SAAO;GACL,SAAS,CAAC;IAAE,MAAM;IAAQ,MAAM,0BAA0B,OAAO,EAAE;IAAI,CAAC;GACxE,SAAS;GACV;;;AAeL,eAAsB,kBACpB,SACA,QACqB;AACrB,KAAI;EACF,MAAM,EAAE,kBAAkB,MAAM,OAAO;EACvC,MAAM,UAAU,MAAM,cAAc,SAAS;GAC3C,UAAU,OAAO;GACjB,gBAAgB,OAAO;GACvB,OAAO,OAAO;GACd,eAAe,OAAO;GACvB,CAAC;AACF,SAAO,EACL,SAAS,CAAC;GAAE,MAAM;GAAQ,MAAM,KAAK,UAAU,SAAS,MAAM,EAAE;GAAE,CAAC,EACpE;UACM,GAAG;AACV,SAAO;GACL,SAAS,CAAC;IAAE,MAAM;IAAQ,MAAM,yBAAyB,OAAO,EAAE;IAAI,CAAC;GACvE,SAAS;GACV;;;AAeL,eAAsB,mBACpB,SACA,QACqB;AACrB,KAAI;EACF,MAAM,EAAE,mBAAmB,MAAM,OAAO;EACxC,MAAM,SAAS,MAAM,eAAe,SAAS;GAC3C,UAAU,OAAO;GACjB,gBAAgB,OAAO;GACvB,OAAO,OAAO;GACd,OAAO,OAAO;GACf,CAAC;AAGF,MAAI;GACF,MAAM,EAAE,oBAAoB,MAAM,OAAO;AACzC,mBAAgB;IACd,OAAO,OAAO;IACd,WAAW,KAAK,KAAK;IACrB,QAAQ;IACR,aAAa,OAAO,cAAc,MAAM,GAAG,EAAE,CAAC,KAAK,MAAM,EAAE,KAAK;IAChE,eAAe,OAAO,cAAc,MAAM,GAAG,EAAE,CAAC,KAAK,MAAM;AAEzD,YAAO,GADO,EAAE,SAAS,aACT,IAAI,EAAE,QAAQ,MAAM,CAAC,MAAM,GAAG,IAAI;MAClD,CAAC,KAAK,MAAM;IACd,aAAa,OAAO,cAAc;IACnC,CAAC;UACI;AAIR,SAAO,EACL,SAAS,CAAC;GAAE,MAAM;GAAQ,MAAM,KAAK,UAAU,QAAQ,MAAM,EAAE;GAAE,CAAC,EACnE;UACM,GAAG;AACV,SAAO;GACL,SAAS,CAAC;IAAE,MAAM;IAAQ,MAAM,0BAA0B,OAAO,EAAE;IAAI,CAAC;GACxE,SAAS;GACV;;;AAgBL,eAAsB,iBACpB,SACA,QACqB;AACrB,KAAI;EACF,MAAM,EAAE,iBAAiB,MAAM,OAAO;EACtC,MAAM,SAAS,MAAM,aAAa,SAAS;GACzC,gBAAgB,OAAO;GACvB,cAAc,OAAO;GACrB,gBAAgB,OAAO;GACvB,WAAW,OAAO;GAClB,qBAAqB,OAAO;GAC7B,CAAC;AACF,SAAO,EACL,SAAS,CAAC;GAAE,MAAM;GAAQ,MAAM,KAAK,UAAU,QAAQ,MAAM,EAAE;GAAE,CAAC,EACnE;UACM,GAAG;AACV,SAAO;GACL,SAAS,CAAC;IAAE,MAAM;IAAQ,MAAM,wBAAwB,OAAO,EAAE;IAAI,CAAC;GACtE,SAAS;GACV;;;AAaL,eAAsB,mBACpB,SACA,QACqB;AACrB,KAAI;EACF,MAAM,EAAE,mBAAmB,MAAM,OAAO;EACxC,MAAM,SAAS,MAAM,eAAe,SAAS;GAC3C,OAAO,OAAO;GACd,YAAY,OAAO;GACpB,CAAC;AACF,SAAO,EACL,SAAS,CAAC;GAAE,MAAM;GAAQ,MAAM,KAAK,UAAU,QAAQ,MAAM,EAAE;GAAE,CAAC,EACnE;UACM,GAAG;AACV,SAAO;GACL,SAAS,CAAC;IAAE,MAAM;IAAQ,MAAM,2BAA2B,OAAO,EAAE;IAAI,CAAC;GACzE,SAAS;GACV;;;AAcL,eAAsB,sBACpB,SACA,QACqB;AACrB,KAAI;EACF,MAAM,EAAE,sBAAsB,MAAM,OAAO;EAC3C,MAAM,SAAS,MAAM,kBAAkB,SAAS;GAC9C,SAAS,OAAO;GAChB,gBAAgB,OAAO;GACvB,YAAY,OAAO;GACpB,CAAC;AACF,SAAO,EACL,SAAS,CAAC;GAAE,MAAM;GAAQ,MAAM,KAAK,UAAU,QAAQ,MAAM,EAAE;GAAE,CAAC,EACnE;UACM,GAAG;AACV,SAAO;GACL,SAAS,CAAC;IAAE,MAAM;IAAQ,MAAM,6BAA6B,OAAO,EAAE;IAAI,CAAC;GAC3E,SAAS;GACV;;;;;;AC5QL,eAAsB,UACpB,SACA,QACqB;AACrB,KAAI;AACF,MAAI,CAAC,OAAO,WAAW,CAAC,OAAO,aAAa,CAAC,OAAO,OAClD,QAAO;GACL,SAAS,CAAC;IAAE,MAAM;IAAQ,MAAM;IAA6D,CAAC;GAC9F,SAAS;GACV;EAEH,MAAM,SAAS,MAAM,QAAQ,YAAY,OAAO;AAChD,SAAO,EACL,SAAS,CAAC;GAAE,MAAM;GAAQ,MAAM,KAAK,UAAU,QAAQ,MAAM,EAAE;GAAE,CAAC,EACnE;UACM,GAAG;AACV,SAAO;GACL,SAAS,CAAC;IAAE,MAAM;IAAQ,MAAM,iBAAiB,OAAO,EAAE;IAAI,CAAC;GAC/D,SAAS;GACV;;;AAiBL,eAAsB,YACpB,SACA,QACqB;AACrB,KAAI;EACF,MAAM,OAAO,OAAO,QAAQ,IAAI,KAAK,OAAO,MAAM,GAAG;AACrD,MAAI,QAAQ,MAAM,KAAK,SAAS,CAAC,CAC/B,QAAO;GACL,SAAS,CAAC;IAAE,MAAM;IAAQ,MAAM,uCAAuC,OAAO;IAAS,CAAC;GACxF,SAAS;GACV;EAEH,MAAM,UAAU,MAAM,QAAQ,eAAe;GAC3C,SAAS,OAAO;GAChB,WAAW,OAAO;GAClB,QAAQ,OAAO;GACf,YAAY,OAAO;GACnB,OAAO;GACP,qBAAqB,OAAO;GAC7B,CAAC;AACF,SAAO,EACL,SAAS,CAAC;GAAE,MAAM;GAAQ,MAAM,KAAK,UAAU,SAAS,MAAM,EAAE;GAAE,CAAC,EACpE;UACM,GAAG;AACV,SAAO;GACL,SAAS,CAAC;IAAE,MAAM;IAAQ,MAAM,mBAAmB,OAAO,EAAE;IAAI,CAAC;GACjE,SAAS;GACV;;;AAYL,eAAsB,iBACpB,SACA,QACqB;AACrB,KAAI;AACF,MAAI,OAAO,cAAc,UAAa,OAAO,cAAc,KACzD,QAAO;GACL,SAAS,CAAC;IAAE,MAAM;IAAQ,MAAM;IAA8C,CAAC;GAC/E,SAAS;GACV;AAEH,QAAM,QAAQ,mBAAmB,OAAO,UAAU;AAClD,SAAO,EACL,SAAS,CAAC;GAAE,MAAM;GAAQ,MAAM,KAAK,UAAU;IAAE,aAAa;IAAM,WAAW,OAAO;IAAW,CAAC;GAAE,CAAC,EACtG;UACM,GAAG;AACV,SAAO;GACL,SAAS,CAAC;IAAE,MAAM;IAAQ,MAAM,wBAAwB,OAAO,EAAE;IAAI,CAAC;GACtE,SAAS;GACV;;;AAYL,eAAsB,qBACpB,SACA,QACqB;AACrB,KAAI;AACF,MAAI,CAAC,OAAO,QACV,QAAO;GACL,SAAS,CAAC;IAAE,MAAM;IAAQ,MAAM;IAAgD,CAAC;GACjF,SAAS;GACV;EAEH,MAAM,iBAAiB,MAAM,QAAQ,oBAAoB,OAAO,QAAQ;AACxE,SAAO,EACL,SAAS,CAAC;GAAE,MAAM;GAAQ,MAAM,KAAK,UAAU,gBAAgB,MAAM,EAAE;GAAE,CAAC,EAC3E;UACM,GAAG;AACV,SAAO;GACL,SAAS,CAAC;IAAE,MAAM;IAAQ,MAAM,4BAA4B,OAAO,EAAE;IAAI,CAAC;GAC1E,SAAS;GACV;;;;;;ACtIL,MAAM,uBAAuB;AAiB7B,eAAsB,iBACpB,UACA,QACqB;AACrB,KAAI;EACF,MAAM,cAAc,OAAO,gBAAgB;EAG3C,IAAI;AAEJ,MAAI,OAAO,SAAS;GAElB,MAAM,SAAS,MAAM,SAAS,iBAAiB,OAAO,QAAQ;AAE9D,OAAI,OACF,YAAW,OAAO;QACb;IAEL,MAAM,WAAW,MAAM,sBAAsB,UAAU,OAAO,QAAQ;AACtE,QAAI,SAAU,YAAW,SAAS;;SAE/B;GAEL,MAAM,WAAW,MAAM,sBAAsB,UAAU,QAAQ,KAAK,CAAC;AACrE,OAAI,SAAU,YAAW,SAAS;;EAGpC,MAAM,cAAc,mBAAmB,UAAU,YAAY;AAE7D,MAAI,CAAC,YACH,QAAO,EACL,SAAS,CACP;GACE,MAAM;GACN,MAAM;GACP,CACF,EACF;AAGH,SAAO,EACL,SAAS,CACP;GACE,MAAM;GACN,MAAM,qBAAqB;GAC5B,CACF,EACF;UACM,GAAG;AACV,SAAO;GACL,SAAS,CAAC;IAAE,MAAM;IAAQ,MAAM,yBAAyB,OAAO,EAAE;IAAI,CAAC;GACvE,SAAS;GACV;;;;;;;;;;;;;ACpBL,eAAsB,YACpB,UACA,SACA,UAA2B,EAAE,EACJ;CACzB,MAAM,kBAAkB,QAAQ,oBAAoB;CACpD,MAAM,QAAQ,QAAQ,SAAS;CAM/B,MAAM,cAAc,kBAChB,MAAM,SAAS,aAAa;EAAE,SAAS;EAAgB;EAAO,CAAC,GAC/D,MAAM,SAAS,aAAa;EAAE,QAAQ;EAAU,SAAS;EAAgB;EAAO,CAAC;AAErF,KAAI,YAAY,WAAW,EACzB,QAAO;EACL,UAAU,EAAE;EACZ,QAAQ;GAAE,UAAU;GAAG,UAAU;GAAG,OAAO;GAAG,QAAQ;GAAG;EACzD,iBAAiB,EAAE;EACpB;CAGH,MAAM,aAAa,YAAY,KAAK,MAAM,EAAE,GAAG;CAC/C,MAAM,eAAe,IAAI,IAAI,WAAW;CAMxC,MAAM,yCAAyB,IAAI,KAAqB;CACxD,MAAM,2CAA2B,IAAI,KAA4B;AAEjE,MAAK,MAAM,aAAa,YAAY;AAClC,yBAAuB,IAAI,WAAW,MAAM,SAAS,wBAAwB,UAAU,CAAC;AACxF,2BAAyB,IAAI,WAAW,MAAM,SAAS,yBAAyB,UAAU,CAAC;;CAO7F,MAAM,gCAAgB,IAAI,KAAuB;AAEjD,MAAK,MAAM,aAAa,WACtB,eAAc,IAAI,WAAW,MAAM,SAAS,mBAAmB,UAAU,CAAC;CAS5E,MAAM,sCAAsB,IAAI,KAAqB;CACrD,MAAM,uCAAuB,IAAI,KAAqB;AAEtD,MAAK,MAAM,aAAa,YAAY;EAClC,MAAM,eAAe,MAAM,QAAQ,gBAAgB,UAAU;AAC7D,sBAAoB,IAAI,WAAW,aAAa,MAAM;AACtD,uBAAqB,IAAI,WAAW,aAAa,OAAO;;CAO1D,MAAM,QAAQ,MAAM,QAAQ,UAAU;CAEtC,MAAM,gBAAgB,kBAClB,MAAM,SAAS,eAAe,GAC9B,MAAM,SAAS,cAAc,EAAE,QAAQ,UAAU,CAAC;CAKtD,MAAM,gBAAgB,CAAC,GAAG,uBAAuB,QAAQ,CAAC,CAAC,QAAQ,GAAG,MAAM,IAAI,GAAG,EAAE;CAarF,MAAM,kBAJkB,MAAM,SAAS,aAAa,EAAE,OAAO,KAAK,IAAI,IAAI,MAAM,EAAE,CAAC,EAChF,QAAQ,MAAM,aAAa,IAAI,EAAE,WAAW,CAAC,CAC7C,MAAM,GAAG,GAAG,CAEiD,KAAK,SAAS;EAC5E,cAAc,IAAI;EAClB,QAAQ,YAAY,IAAI,SAAS;EACjC,WAAW,IAAI;EAChB,EAAE;AAeH,QAAO;EACL,UAVkC,YAAY,KAAK,SAAS;GAC5D,MAAM,IAAI;GACV,cAAc,IAAI;GAClB,eAAe,uBAAuB,IAAI,IAAI,GAAG,IAAI;GACrD,YAAY,oBAAoB,IAAI,IAAI,GAAG,IAAI;GAC/C,eAAe,yBAAyB,IAAI,IAAI,GAAG,IAAI;GACvD,UAAU,cAAc,IAAI,IAAI,GAAG,IAAI,EAAE;GAC1C,EAAE;EAID,QAAQ;GACN,UAAU;GACV,UAAU;GACV,OAAO,MAAM;GACb,QAAQ,MAAM;GACf;EACD,iBAAiB;EAClB;;;;;ACjKH,eAAsB,mBACpB,UACA,SACA,SAA+B,EAAE,EACZ;AACrB,KAAI;EACF,MAAM,SAAS,MAAM,YAAY,UAAU,SAAS;GAClD,kBAAkB,OAAO;GACzB,OAAO,OAAO;GACf,CAAC;EAMF,MAAM,QAAkB,EAAE;AAG1B,QAAM,KACJ,yBAAyB,OAAO,OAAO,SAAS,eAC3C,OAAO,OAAO,SAAS,eACvB,OAAO,OAAO,MAAM,oBACpB,OAAO,OAAO,OAAO,WAC3B;AACD,QAAM,KAAK,GAAG;AAGd,MAAI,OAAO,SAAS,WAAW,EAC7B,OAAM,KAAK,4BAA4B;OAClC;AACL,SAAM,KAAK,YAAY;AACvB,QAAK,MAAM,KAAK,OAAO,UAAU;IAC/B,MAAM,SAAS,EAAE,SAAS,SAAS,IAAI,KAAK,EAAE,SAAS,KAAK,KAAK,CAAC,KAAK;IACvE,MAAM,cAAc,EAAE,gBAAgB,UAAU,EAAE,kBAAkB;AACpE,UAAM,KACJ,KAAK,EAAE,KAAK,KAAK,EAAE,0BACH,EAAE,mBACf,EAAE,aAAa,IAAI,UAAU,EAAE,eAAe,MAC/C,cACA,OACH;;;AAKL,MAAI,OAAO,gBAAgB,SAAS,GAAG;AACrC,SAAM,KAAK,GAAG;AACd,SAAM,KAAK,0DAA0D;AACrE,QAAK,MAAM,KAAK,OAAO,gBACrB,OAAM,KAAK,KAAK,EAAE,UAAU,IAAI,EAAE,aAAa,IAAI,EAAE,SAAS;;AAIlE,SAAO,EACL,SAAS,CAAC;GAAE,MAAM;GAAQ,MAAM,MAAM,KAAK,KAAK;GAAE,CAAC,EACpD;UACM,GAAG;AACV,SAAO;GACL,SAAS,CAAC;IAAE,MAAM;IAAQ,MAAM,0BAA0B,OAAO,EAAE;IAAI,CAAC;GACxE,SAAS;GACV;;;;;;;;;;;;;;;;ACnBL,eAAsB,YACpB,SACA,UACA,SAC4B;AAC5B,QAAO,QAAQ,YAAY,UAAU,QAAQ;;;;;AC5C/C,eAAsB,kBACpB,UACA,SACA,QACqB;AACrB,KAAI;EACF,MAAM,SAAS,MAAM,YAAY,SAAS,UAAU;GAClD,cAAc,OAAO;GACrB,iBAAiB,OAAO;GACxB,OAAO,OAAO;GACf,CAAC;AAEF,SAAO,EACL,SAAS,CAAC;GAAE,MAAM;GAAQ,MAAM,KAAK,UAAU,QAAQ,MAAM,EAAE;GAAE,CAAC,EACnE;UACM,GAAG;AACV,SAAO;GACL,SAAS,CAAC;IAAE,MAAM;IAAQ,MAAM,yBAAyB,OAAO,EAAE;IAAI,CAAC;GACvE,SAAS;GACV;;;;;;ACtBL,MAAM,iBAAiB;;;;;;;;;AA2BvB,eAAsB,mBACpB,SACA,QACqB;AACrB,KAAI;AACF,MAAI,CAAC,MAAM,QAAQ,OAAO,UAAU,IAAI,OAAO,UAAU,WAAW,EAClE,QAAO;GACL,SAAS,CAAC;IAAE,MAAM;IAAQ,MAAM;IAA8D,CAAC;GAC/F,SAAS;GACV;EAGH,MAAM,SAAS,OAAO;AACtB,MAAI,OAAO,WAAW,YAAY,SAAS,KAAK,SAAS,EACvD,QAAO;GACL,SAAS,CAAC;IAAE,MAAM;IAAQ,MAAM;IAAkE,CAAC;GACnG,SAAS;GACV;EAIH,MAAM,oBAAoB,SAAS,KAAK;EAGxC,MAAM,SAAS,MAAM,QAAQ,qBAAqB,OAAO,UAAU;AAEnE,MAAI,OAAO,WAAW,EACpB,QAAO;GACL,SAAS,CAAC;IAAE,MAAM;IAAQ,MAAM;IAA6C,CAAC;GAC9E,SAAS;GACV;EAGH,IAAI,gBAAgB;EACpB,MAAM,eAAyB,EAAE;AAGjC,OAAK,MAAM,SAAS,QAAQ;GAC1B,MAAM,WAAW,MAAM,kBAAkB;GACzC,MAAM,WAAW,WAAW,kBAAkB,mBAAmB;AACjE,SAAM,QAAQ,0BAA0B,MAAM,IAAI,SAAS;AAC3D;AACA,gBAAa,KAAK,MAAM,KAAK;;EAI/B,MAAM,WAAW,OAAO,aAAa;EACrC,MAAM,WAAW,MAAM,QAAQ,eAAe,UAAU,QAAW,IAAI;EACvE,MAAM,OAAO,aAAa,KAAK,KAAK,CAAC,aAAa;EAElD,IAAI,kBAAkB;AACtB,OAAK,MAAM,UAAU,SACnB,KAAI,KAAK,SAAS,OAAO,KAAK,aAAa,CAAC,EAAE;AAC5C,SAAM,QAAQ,2BAA2B,OAAO,WAAW,kBAAkB,eAAe;AAC5F;;AAIJ,SAAO,EACL,SAAS,CACP;GACE,MAAM;GACN,MAAM,KAAK,UAAU;IACnB,gBAAgB;IAChB,kBAAkB;IAClB;IACA,mBAAmB;IACpB,CAAC;GACH,CACF,EACF;UACM,GAAG;AACV,SAAO;GACL,SAAS,CAAC;IAAE,MAAM;IAAQ,MAAM,0BAA0B,OAAO,EAAE;IAAI,CAAC;GACxE,SAAS;GACV;;;;;;;;;;;;;;;;;;;;;;;;;;;ACjEL,eAAe,sBACb,YACA,QACA,WAAW,WACQ;CAGnB,MAAM,WAAW,MAAM,WAAW,eAAe,UAAU,QAAW,IAAI;AAC1E,KAAI,SAAS,WAAW,EAAG,QAAO,EAAE;CAEpC,MAAM,eAAe,OAAO,KAAK,MAAM,EAAE,QAAQ,CAAC,KAAK,KAAK,CAAC,aAAa;CAC1E,MAAM,0BAAU,IAAI,KAAa;AAEjC,MAAK,MAAM,UAAU,SACnB,KAAI,aAAa,SAAS,OAAO,KAAK,aAAa,CAAC,CAClD,SAAQ,IAAI,OAAO,KAAK;AAI5B,QAAO,MAAM,KAAK,QAAQ;;;;;;;;;;AAe5B,eAAe,UACb,YACA,aACA,MACA,WACqB;CACrB,MAAM,uBAAO,IAAI,KAAa;CAC9B,MAAM,4BAAY,IAAI,KAAuB;CAC7C,MAAM,WAAW,IAAI,IAAI,YAAY;AAErC,MAAK,IAAI,MAAM,GAAG,MAAM,MAAM,OAAO;EACnC,MAAM,+BAAe,IAAI,KAAa;AAEtC,OAAK,MAAM,QAAQ,UAAU;GAE3B,MAAM,YAAY,MAAM,WAAW,eAAe;IAChD,SAAS;IACT,YAAY;IACb,CAAC;GAGF,MAAM,WAAW,MAAM,WAAW,eAAe;IAC/C,QAAQ;IACR,YAAY;IACb,CAAC;AAEF,QAAK,MAAM,UAAU,CAAC,GAAG,WAAW,GAAG,SAAS,CAC9C,KAAI,CAAC,KAAK,IAAI,OAAO,GAAG,EAAE;AACxB,SAAK,IAAI,OAAO,GAAG;AACnB,cAAU,IAAI,OAAO,IAAI,OAAO;AAEhC,iBAAa,IAAI,OAAO,QAAQ;AAChC,iBAAa,IAAI,OAAO,OAAO;;;AAMrC,WAAS,OAAO;AAChB,OAAK,MAAM,QAAQ,aACjB,KAAI,CAAC,YAAY,SAAS,KAAK,EAAE;AAC/B,YAAS,IAAI,KAAK;AAClB,eAAY,KAAK,KAAK;;;AAK5B,QAAO,MAAM,KAAK,UAAU,QAAQ,CAAC;;;;;;;;;;AAevC,eAAe,cACb,SACA,UACA,YACuD;AACvD,KAAI,QAAQ,WAAW,EAAG,QAAO,EAAE;CAEnC,MAAM,EAAE,sBAAsB,MAAM,OAAO;AAe3C,SAbe,MAAM,QAAQ,IAC3B,QAAQ,IAAI,OAAO,WAAW;EAC5B,MAAM,OAAO,GAAG,OAAO,QAAQ,GAAG,OAAO,UAAU,GAAG,OAAO;AAC7D,MAAI;GAEF,MAAM,QAAQ,iBAAiB,UADnB,MAAM,kBAAkB,MAAM,MAAM,CACH;AAC7C,UAAO;IAAE,GAAG;IAAQ,gBAAgB;IAAO;UACrC;AACN,UAAO;IAAE,GAAG;IAAQ,gBAAgB;IAAG;;GAEzC,CACH,EAGE,MAAM,GAAG,MAAM,EAAE,iBAAiB,EAAE,eAAe,CACnD,MAAM,GAAG,WAAW;;;;;;;;;AAczB,eAAsB,sBACpB,YACA,UACA,OAA+B,EAAE,EACD;CAChC,MAAM,EACJ,YAAY,IACZ,OAAO,GACP,aAAa,IACb,WACA,WAAW,WACX,eACE;CAGJ,MAAM,iBAAgC;EACpC,GAAG;EACH,YAAY;EACZ,YAAY,aAAa,OAAO,CAAC,UAAU,GAAG,YAAY;EAC3D;CAED,IAAI,aAA6B,EAAE;AACnC,KAAI;AACF,eAAa,MAAM,WAAW,eAAe,UAAU,eAAe;UAC/D,GAAG;AACV,UAAQ,OAAO,MAAM,0CAA0C,EAAE,IAAI;;AAGvE,KAAI,WAAW,WAAW,EACxB,QAAO;EAAE,SAAS,EAAE;EAAE,YAAY,EAAE;EAAE,kBAAkB,EAAE;EAAE;CAI9D,MAAM,mBAAmB,MAAM,sBAAsB,YAAY,YAAY,SAAS;AAEtF,KAAI,iBAAiB,WAAW,EAE9B,QAAO;EAAE,SAAS,EAAE;EAAE;EAAY,kBAAkB,EAAE;EAAE;CAI1D,IAAI,kBAA8B,EAAE;AACpC,KAAI;AACF,oBAAkB,MAAM,UAAU,YAAY,CAAC,GAAG,iBAAiB,EAAE,MAAM,UAAU;UAC9E,GAAG;AACV,UAAQ,OAAO,MAAM,4CAA4C,EAAE,IAAI;;AAGzE,KAAI,gBAAgB,WAAW,EAC7B,QAAO;EAAE,SAAS,EAAE;EAAE;EAAY;EAAkB;CAItD,IAAI,gBAA8D,EAAE;AACpE,KAAI;AACF,kBAAgB,MAAM,cAAc,iBAAiB,UAAU,WAAW;UACnE,GAAG;AACV,UAAQ,OAAO,MAAM,qCAAqC,EAAE,IAAI;AAEhE,kBAAgB,gBAAgB,MAAM,GAAG,WAAW,CAAC,KAAK,OAAO;GAC/D,GAAG;GACH,gBAAgB;GACjB,EAAE;;AAGL,QAAO;EAAE,SAAS;EAAe;EAAY;EAAkB;;;;;;;;;;;ACnNjE,eAAsB,mBACpB,YACA,QACqB;AACrB,KAAI;AACF,MAAI,CAAC,OAAO,SAAS,OAAO,OAAO,UAAU,YAAY,OAAO,MAAM,MAAM,KAAK,GAC/E,QAAO;GACL,SAAS,CAAC;IAAE,MAAM;IAAQ,MAAM;IAA6C,CAAC;GAC9E,SAAS;GACV;EAGH,MAAM,EAAE,sBAAsB,MAAM,OAAO;EAG3C,MAAM,SAAS,MAAM,sBAAsB,YAF1B,MAAM,kBAAkB,OAAO,OAAO,KAAK,EAEK;GAC/D,WAAW,OAAO,UAAU;GAC5B,MAAM,OAAO,sBAAsB;GACnC,YAAY,OAAO,SAAS;GAC5B,WAAW,OAAO;GAClB,UAAU,OAAO,aAAa;GAC/B,CAAC;AAEF,MAAI,OAAO,QAAQ,WAAW,EAC5B,QAAO,EACL,SAAS,CACP;GACE,MAAM;GACN,MAAM,mCAAmC,OAAO,MAAM,kBACpC,OAAO,WAAW,OAAO,uBACnB,OAAO,iBAAiB;GACjD,CACF,EACF;EAGH,MAAM,cAAc,OAAO,QACxB,KAAK,GAAG,MACP,IAAI,IAAI,EAAE,WAAW,EAAE,eAAe,QAAQ,EAAE,CAAC,IAAI,EAAE,QAAQ,KAAK,EAAE,UAAU,KAAK,EAAE,SACxF,CACA,KAAK,KAAK;EAEb,MAAM,aAAa,OAAO,iBAAiB,MAAM,GAAG,GAAG,CAAC,KAAK,KAAK;AASlE,SAAO,EACL,SAAS,CAAC;GAAE,MAAM;GAAQ,MATZ;IACd,SAAS,OAAO,QAAQ,OAAO,qBAAqB,OAAO,MAAM;IACjE,gBAAgB,OAAO,WAAW;IAClC,sBAAsB,OAAO,iBAAiB,OAAO,KAAK;IAC1D;IACA;IACD,CAAC,KAAK,KAAK;GAG+B,CAAC,EAC3C;UACM,GAAG;AACV,SAAO;GACL,SAAS,CAAC;IAAE,MAAM;IAAQ,MAAM,2BAA2B,OAAO,EAAE;IAAI,CAAC;GACzE,SAAS;GACV"}
1
+ {"version":3,"file":"tools-BcOKPBtg.mjs","names":[],"sources":["../src/mcp/tools/types.ts","../src/mcp/tools/memory.ts","../src/mcp/tools/projects.ts","../src/mcp/tools/sessions.ts","../src/mcp/tools/registry.ts","../src/mcp/tools/zettel.ts","../src/mcp/tools/kg.ts","../src/mcp/tools/wakeup.ts","../src/memory/taxonomy.ts","../src/mcp/tools/taxonomy.ts","../src/memory/tunnels.ts","../src/mcp/tools/tunnels.ts","../src/mcp/tools/feedback.ts","../src/memory/kg-search.ts","../src/mcp/tools/kg-search.ts"],"sourcesContent":["/**\n * Shared types and project-row helpers used across all MCP tool handler modules.\n */\n\nimport { resolve } from \"node:path\";\nimport type { RegistryBackend, Project } from \"../../storage/registry-interface.js\";\n\nexport interface ToolContent {\n type: \"text\";\n text: string;\n}\n\nexport interface ToolResult {\n content: ToolContent[];\n isError?: boolean;\n}\n\n// ---------------------------------------------------------------------------\n// Shared row type — mirrors the projects schema (RegistryBackend's Project)\n// ---------------------------------------------------------------------------\n\nexport type ProjectRow = Project;\n\n// ---------------------------------------------------------------------------\n// Helper: lookup project_id by slug (also checks aliases)\n// ---------------------------------------------------------------------------\n\nexport async function lookupProjectId(\n registry: RegistryBackend,\n slug: string\n): Promise<number | null> {\n const bySlug = await registry.getProjectBySlug(slug);\n if (bySlug) return bySlug.id;\n\n const projectId = await registry.resolveAlias(slug);\n return projectId ?? null;\n}\n\n// ---------------------------------------------------------------------------\n// Helper: detect project from a filesystem path\n// ---------------------------------------------------------------------------\n\nexport async function detectProjectFromPath(\n registry: RegistryBackend,\n fsPath: string\n): Promise<ProjectRow | null> {\n const resolved = resolve(fsPath);\n\n const exact = await registry.getProjectByRootPath(resolved);\n if (exact) return exact;\n\n const all = await registry.listProjectsByPathLengthDesc();\n\n for (const project of all) {\n if (\n resolved.startsWith(project.root_path + \"/\") ||\n resolved === project.root_path\n ) {\n return project;\n }\n }\n\n return null;\n}\n\n// ---------------------------------------------------------------------------\n// Helper: format project row for tool output\n// ---------------------------------------------------------------------------\n\nexport async function formatProject(registry: RegistryBackend, project: ProjectRow): Promise<string> {\n const sessionCount = await registry.countSessionsForProject(project.id);\n const lastSessionDate = await registry.getMostRecentSessionDate(project.id);\n const tags = await registry.listTagsForProject(project.id);\n const aliases = await registry.listAliasesForProject(project.id);\n\n const lines: string[] = [\n `slug: ${project.slug}`,\n `display_name: ${project.display_name}`,\n `root_path: ${project.root_path}`,\n `type: ${project.type}`,\n `status: ${project.status}`,\n `sessions: ${sessionCount}`,\n ];\n\n if (lastSessionDate) lines.push(`last_session: ${lastSessionDate}`);\n if (tags.length) lines.push(`tags: ${tags.join(\", \")}`);\n if (aliases.length) lines.push(`aliases: ${aliases.join(\", \")}`);\n if (project.obsidian_link) lines.push(`obsidian_link: ${project.obsidian_link}`);\n if (project.archived_at) {\n lines.push(\n `archived_at: ${new Date(project.archived_at).toISOString().slice(0, 10)}`\n );\n }\n\n return lines.join(\"\\n\");\n}\n","/**\n * MCP tool handlers: memory_search, memory_get, memory_outline\n */\n\nimport { readFileSync, existsSync, statSync } from \"node:fs\";\nimport { join, resolve, isAbsolute } from \"node:path\";\nimport { populateSlugs } from \"../../memory/search.js\";\nimport { buildOutline, type OutlineNode } from \"../../memory/chunker.js\";\nimport type { StorageBackend } from \"../../storage/interface.js\";\nimport type { RegistryBackend } from \"../../storage/registry-interface.js\";\nimport type { SearchConfig } from \"../../daemon/config.js\";\nimport type { SearchResult } from \"../../memory/search.js\";\nimport {\n lookupProjectId,\n type ToolResult,\n} from \"./types.js\";\n\n// ---------------------------------------------------------------------------\n// Tool: memory_search\n// ---------------------------------------------------------------------------\n\nexport interface MemorySearchParams {\n query: string;\n project?: string;\n all_projects?: boolean;\n sources?: Array<\"memory\" | \"notes\">;\n limit?: number;\n mode?: \"keyword\" | \"semantic\" | \"hybrid\";\n /** Rerank results using cross-encoder model for better relevance ordering. */\n rerank?: boolean;\n /** Apply recency boost — score decays by half every N days. 0 = off (default). */\n recencyBoost?: number;\n /** Maximum characters per result snippet. Default 200.\n * Limit context consumption — MCP results go into Claude's context window. */\n snippetLength?: number;\n /** Output format: \"full\" (default, includes snippets) or \"compact\" (IDs + metadata only, ~10x fewer tokens). */\n format?: \"full\" | \"compact\";\n}\n\nexport async function toolMemorySearch(\n registry: RegistryBackend,\n federation: StorageBackend,\n params: MemorySearchParams,\n searchDefaults?: SearchConfig,\n): Promise<ToolResult> {\n try {\n const projectIds: number[] | undefined = params.project\n ? await (async () => {\n const id = await lookupProjectId(registry, params.project!);\n return id != null ? [id] : [];\n })()\n : undefined;\n\n if (params.project && (!projectIds || projectIds.length === 0)) {\n return {\n content: [\n { type: \"text\", text: `Project not found: ${params.project}` },\n ],\n isError: true,\n };\n }\n\n // NOTE: No indexAll() here — indexing is handled by the daemon scheduler.\n // The daemon ensures the index stays fresh; the search hot path is read-only.\n\n const mode = params.mode ?? (searchDefaults?.mode ?? \"keyword\");\n // Limit context consumption — MCP results go into Claude's context window.\n // Default to 5 results and 200-char snippets to keep a single search call\n // within ~1-2K tokens rather than 5K+.\n const snippetLength = params.snippetLength ?? (searchDefaults?.snippetLength ?? 200);\n const searchOpts = {\n projectIds,\n sources: params.sources,\n maxResults: params.limit ?? (searchDefaults?.defaultLimit ?? 5),\n };\n\n let results: SearchResult[];\n\n if (mode === \"keyword\") {\n results = await federation.searchKeyword(params.query, searchOpts);\n } else if (mode === \"semantic\" || mode === \"hybrid\") {\n const { generateEmbedding } = await import(\"../../memory/embeddings.js\");\n const queryEmbedding = await generateEmbedding(params.query, true);\n\n if (mode === \"semantic\") {\n results = await federation.searchSemantic(queryEmbedding, searchOpts);\n } else {\n // Hybrid: combine keyword + semantic\n const [kwResults, semResults] = await Promise.all([\n federation.searchKeyword(params.query, { ...searchOpts, maxResults: 50 }),\n federation.searchSemantic(queryEmbedding, { ...searchOpts, maxResults: 50 }),\n ]); // 50 candidates is sufficient for min-max normalization\n // Reuse the existing hybrid scoring logic\n results = combineHybridResults(kwResults, semResults, searchOpts.maxResults ?? 10);\n }\n } else {\n results = await federation.searchKeyword(params.query, searchOpts);\n }\n\n // Re-rank by the corpus's own links before returning.\n //\n // Similarity answers \"what reads like the query\"; it cannot answer \"which\n // of these is the one the others refer back to\", which is usually the note\n // to read first. The wikilinks are facts a person wrote, they cost nothing\n // to maintain, and they are correct for chunks that have no embedding yet —\n // which matters while a large embedding backlog drains.\n //\n // Only EXTRACTED links count: INFERRED ones are themselves derived by\n // similarity, so folding them in would feed the ranking its own output.\n try {\n const { applyLinkBoost } = await import(\"../../memory/link-boost.js\");\n const paths = [...new Set(results.map((r) => r.path))];\n // Bounded: one lookup per distinct result path, not per chunk, and the\n // result set is already capped by maxResults.\n if (paths.length > 1) {\n const edges: Array<{ sourcePath: string; targetPath: string }> = [];\n for (const p of paths) {\n for (const l of await federation.getLinksToTarget(p)) {\n if (l.confidence && l.confidence !== \"EXTRACTED\") continue;\n edges.push({ sourcePath: l.sourcePath, targetPath: p });\n }\n }\n if (edges.length > 0) results = applyLinkBoost(results, edges);\n }\n } catch {\n // A ranking aid, never a reason to fail a search.\n }\n\n // QW2: Update last_accessed_at for returned chunks (best-effort, non-blocking)\n try {\n const chunkIds = results\n .map((r) => r.chunkId)\n .filter((id): id is string => id != null);\n if (chunkIds.length > 0) {\n await federation.touchChunksLastAccessed(chunkIds);\n }\n } catch {\n // non-critical — never block search results\n }\n\n // Cross-encoder reranking (on by default)\n const shouldRerank = params.rerank ?? (searchDefaults?.rerank ?? true);\n if (shouldRerank && results.length > 0) {\n const { rerankResults } = await import(\"../../memory/reranker.js\");\n results = await rerankResults(params.query, results, {\n topK: searchOpts.maxResults ?? 5,\n });\n }\n\n // Recency boost (off by default, applied after reranking)\n const recencyDays = params.recencyBoost ?? (searchDefaults?.recencyBoostDays ?? 0);\n if (recencyDays > 0 && results.length > 0) {\n const { applyRecencyBoost } = await import(\"../../memory/search.js\");\n results = applyRecencyBoost(results, recencyDays);\n }\n\n const withSlugs = await populateSlugs(results, registry);\n\n if (withSlugs.length === 0) {\n return {\n content: [\n {\n type: \"text\",\n text: `No results found for query: \"${params.query}\" (mode: ${mode})`,\n },\n ],\n };\n }\n\n const rerankLabel = shouldRerank ? \" +rerank\" : \"\";\n const useCompact = params.format === \"compact\";\n const formatted = withSlugs\n .map((r, i) => {\n const slug = r.projectSlug ?? `project:${r.projectId}`;\n const idPart = r.chunkId ? ` id=${r.chunkId}` : \"\";\n if (useCompact) {\n // Compact format: chunk ID, path, score — pass id to memory_feedback to rate this result\n return `[${i + 1}]${idPart} ${slug} — ${r.path} L${r.startLine}-${r.endLine} score=${r.score.toFixed(3)}`;\n }\n const header = `[${i + 1}]${idPart} ${slug} — ${r.path} (lines ${r.startLine}-${r.endLine}) score=${r.score.toFixed(4)} tier=${r.tier} source=${r.source}`;\n // Truncate snippet to snippetLength — limit context consumption.\n // MCP results go into Claude's context window; keep each result tight.\n const raw = r.snippet.trim();\n const snippet = raw.length > snippetLength\n ? raw.slice(0, snippetLength) + \"...\"\n : raw;\n return `${header}\\n${snippet}`;\n })\n .join(useCompact ? \"\\n\" : \"\\n\\n---\\n\\n\");\n\n // Query feedback loop: save query + result metadata for future indexing\n try {\n const { saveQueryResult } = await import(\"../../zettelkasten/query-feedback.js\");\n saveQueryResult({\n query: params.query,\n timestamp: Date.now(),\n source: \"memory_search\",\n sourceSlugs: withSlugs.slice(0, 5).map((r) => r.path),\n answerPreview: withSlugs.slice(0, 3).map((r) => r.snippet.trim().slice(0, 150)).join(\" | \"),\n resultCount: withSlugs.length,\n });\n } catch {\n // Non-critical — don't fail the search if feedback logging errors\n }\n\n return {\n content: [\n {\n type: \"text\",\n text: `Found ${withSlugs.length} result(s) for \"${params.query}\" (mode: ${mode}${rerankLabel}):\\n\\n${formatted}`,\n },\n ],\n };\n } catch (e) {\n return {\n content: [{ type: \"text\", text: `Search error: ${String(e)}` }],\n isError: true,\n };\n }\n}\n\n// ---------------------------------------------------------------------------\n// Tool: memory_get\n// ---------------------------------------------------------------------------\n\nexport interface MemoryGetParams {\n project: string;\n path: string;\n from?: number;\n lines?: number;\n}\n\n/**\n * Resolve `project` + `path` to a readable file inside the project root.\n * Shared by memory_get and memory_outline so both apply the same path and\n * access checks. Returns a ToolResult on any failure.\n */\nasync function resolveProjectFile(\n registry: RegistryBackend,\n projectSlug: string,\n requestedPath: string,\n): Promise<{ fullPath: string } | ToolResult> {\n const fail = (text: string): ToolResult => ({\n content: [{ type: \"text\", text }],\n isError: true,\n });\n\n const projectId = await lookupProjectId(registry, projectSlug);\n if (projectId == null) return fail(`Project not found: ${projectSlug}`);\n\n const project = await registry.getProjectById(projectId);\n if (!project) return fail(`Project not found: ${projectSlug}`);\n\n if (requestedPath.includes(\"..\") || isAbsolute(requestedPath)) {\n return fail(\n `Invalid path: ${requestedPath} (must be a relative path within the project root, no ../ allowed)`,\n );\n }\n\n const fullPath = join(project.root_path, requestedPath);\n const resolvedFull = resolve(fullPath);\n const resolvedRoot = resolve(project.root_path);\n\n if (!resolvedFull.startsWith(resolvedRoot + \"/\") && resolvedFull !== resolvedRoot) {\n return fail(`Path traversal blocked: ${requestedPath}`);\n }\n\n if (!existsSync(fullPath)) {\n return fail(`File not found: ${requestedPath} (project: ${projectSlug})`);\n }\n\n const stat = statSync(fullPath);\n if (stat.size > 5 * 1024 * 1024) {\n return {\n content: [\n {\n type: \"text\",\n text: `Error: file too large (${(stat.size / 1024 / 1024).toFixed(1)} MB). Maximum 5 MB.`,\n },\n ],\n };\n }\n\n return { fullPath };\n}\n\nexport async function toolMemoryGet(\n registry: RegistryBackend,\n params: MemoryGetParams\n): Promise<ToolResult> {\n try {\n const resolved = await resolveProjectFile(registry, params.project, params.path);\n if (\"content\" in resolved) return resolved;\n const { fullPath } = resolved;\n const requestedPath = params.path;\n\n const content = readFileSync(fullPath, \"utf8\");\n const allLines = content.split(\"\\n\");\n\n const fromLine = (params.from ?? 1) - 1;\n const toLine =\n params.lines != null\n ? Math.min(fromLine + params.lines, allLines.length)\n : allLines.length;\n\n const selectedLines = allLines.slice(fromLine, toLine);\n const text = selectedLines.join(\"\\n\");\n\n const header =\n params.from != null\n ? `${params.project}/${requestedPath} (lines ${fromLine + 1}-${toLine}):`\n : `${params.project}/${requestedPath}:`;\n\n return {\n content: [{ type: \"text\", text: `${header}\\n\\n${text}` }],\n };\n } catch (e) {\n return {\n content: [{ type: \"text\", text: `Read error: ${String(e)}` }],\n isError: true,\n };\n }\n}\n\n// ---------------------------------------------------------------------------\n// Tool: memory_outline\n// ---------------------------------------------------------------------------\n\nexport interface MemoryOutlineParams {\n project: string;\n path: string;\n max_depth?: number;\n}\n\nfunction renderOutline(nodes: OutlineNode[], maxDepth: number, depth = 0, out: string[] = []): string[] {\n for (const n of nodes) {\n out.push(\n `${\" \".repeat(depth)}${\"#\".repeat(n.level)} ${n.title} L${n.startLine}-${n.endLine} ~${n.tokens}t`,\n );\n if (depth + 1 < maxDepth) renderOutline(n.children, maxDepth, depth + 1, out);\n }\n return out;\n}\n\nexport async function toolMemoryOutline(\n registry: RegistryBackend,\n params: MemoryOutlineParams\n): Promise<ToolResult> {\n try {\n const resolved = await resolveProjectFile(registry, params.project, params.path);\n if (\"content\" in resolved) return resolved;\n\n const outline = buildOutline(readFileSync(resolved.fullPath, \"utf8\"));\n const body = outline.length\n ? renderOutline(outline, params.max_depth ?? Infinity).join(\"\\n\")\n : \"(no headings)\";\n\n return {\n content: [{ type: \"text\", text: `${params.project}/${params.path} outline:\\n\\n${body}` }],\n };\n } catch (e) {\n return {\n content: [{ type: \"text\", text: `Read error: ${String(e)}` }],\n isError: true,\n };\n }\n}\n\n// ---------------------------------------------------------------------------\n// Hybrid search helper (backend-agnostic)\n// ---------------------------------------------------------------------------\n\n/**\n * Combine keyword + semantic results using min-max normalized scoring.\n * Mirrors the logic in searchMemoryHybrid() from memory/search.ts,\n * but works on pre-computed result arrays so it works for any backend.\n */\nexport function combineHybridResults(\n keywordResults: SearchResult[],\n semanticResults: SearchResult[],\n maxResults: number,\n keywordWeight = 0.5,\n semanticWeight = 0.5\n): SearchResult[] {\n if (keywordResults.length === 0 && semanticResults.length === 0) return [];\n\n const keyFor = (r: SearchResult) =>\n `${r.projectId}:${r.path}:${r.startLine}:${r.endLine}`;\n\n function minMaxNormalize(items: SearchResult[]): Map<string, number> {\n if (items.length === 0) return new Map();\n const min = Math.min(...items.map((r) => r.score));\n const max = Math.max(...items.map((r) => r.score));\n const range = max - min;\n const m = new Map<string, number>();\n for (const r of items) {\n m.set(keyFor(r), range === 0 ? 1 : (r.score - min) / range);\n }\n return m;\n }\n\n const kwNorm = minMaxNormalize(keywordResults);\n const semNorm = minMaxNormalize(semanticResults);\n\n const allKeys = new Set<string>([\n ...keywordResults.map(keyFor),\n ...semanticResults.map(keyFor),\n ]);\n\n const metaMap = new Map<string, SearchResult>();\n for (const r of [...keywordResults, ...semanticResults]) {\n metaMap.set(keyFor(r), r);\n }\n\n const combined: Array<SearchResult & { combinedScore: number }> = [];\n for (const key of allKeys) {\n const meta = metaMap.get(key)!;\n const kwScore = kwNorm.get(key) ?? 0;\n const semScore = semNorm.get(key) ?? 0;\n const combinedScore = keywordWeight * kwScore + semanticWeight * semScore;\n combined.push({ ...meta, score: combinedScore, combinedScore });\n }\n\n return combined\n .sort((a, b) => b.score - a.score)\n .slice(0, maxResults)\n .map(({ combinedScore: _unused, ...r }) => r);\n}\n","/**\n * MCP tool handlers: project_info, project_list, project_detect,\n * project_health, project_todo\n */\n\nimport { readFileSync, existsSync } from \"node:fs\";\nimport { join } from \"node:path\";\nimport { detectProject, formatDetectionJson } from \"../../cli/commands/detect.js\";\nimport type { RegistryBackend } from \"../../storage/registry-interface.js\";\nimport {\n lookupProjectId,\n detectProjectFromPath,\n formatProject,\n type ProjectRow,\n type ToolResult,\n} from \"./types.js\";\n\n// ---------------------------------------------------------------------------\n// Tool: project_info\n// ---------------------------------------------------------------------------\n\nexport interface ProjectInfoParams {\n slug?: string;\n}\n\nexport async function toolProjectInfo(\n registry: RegistryBackend,\n params: ProjectInfoParams\n): Promise<ToolResult> {\n try {\n let project: ProjectRow | null = null;\n\n if (params.slug) {\n const projectId = await lookupProjectId(registry, params.slug);\n if (projectId != null) {\n project = await registry.getProjectById(projectId);\n }\n } else {\n const cwd = process.cwd();\n project = await detectProjectFromPath(registry, cwd);\n }\n\n if (!project) {\n const message = params.slug\n ? `Project not found: ${params.slug}`\n : `No PAI project found matching the current directory: ${process.cwd()}`;\n return {\n content: [{ type: \"text\", text: message }],\n isError: !params.slug,\n };\n }\n\n return {\n content: [{ type: \"text\", text: await formatProject(registry, project) }],\n };\n } catch (e) {\n return {\n content: [{ type: \"text\", text: `project_info error: ${String(e)}` }],\n isError: true,\n };\n }\n}\n\n// ---------------------------------------------------------------------------\n// Tool: project_list\n// ---------------------------------------------------------------------------\n\nexport interface ProjectListParams {\n status?: \"active\" | \"archived\" | \"migrating\";\n tag?: string;\n limit?: number;\n}\n\nexport async function toolProjectList(\n registry: RegistryBackend,\n params: ProjectListParams\n): Promise<ToolResult> {\n try {\n const limit = params.limit ?? 50;\n\n const projects = await registry.listProjects({\n status: params.status,\n tagName: params.tag,\n orderBy: \"updated_desc\",\n limit,\n });\n\n if (projects.length === 0) {\n return {\n content: [\n {\n type: \"text\",\n text: \"No projects found matching the given filters.\",\n },\n ],\n };\n }\n\n const lines = projects.map(\n (p) =>\n `${p.slug} [${p.status}] ${p.root_path} (updated: ${new Date(p.updated_at).toISOString().slice(0, 10)})`\n );\n\n return {\n content: [\n {\n type: \"text\",\n text: `${projects.length} project(s):\\n\\n${lines.join(\"\\n\")}`,\n },\n ],\n };\n } catch (e) {\n return {\n content: [{ type: \"text\", text: `project_list error: ${String(e)}` }],\n isError: true,\n };\n }\n}\n\n// ---------------------------------------------------------------------------\n// Tool: project_detect\n// ---------------------------------------------------------------------------\n\nexport interface ProjectDetectParams {\n cwd?: string;\n}\n\nexport async function toolProjectDetect(\n params: ProjectDetectParams\n): Promise<ToolResult> {\n try {\n const detection = await detectProject(params.cwd);\n\n if (!detection) {\n const target = params.cwd ?? process.cwd();\n return {\n content: [\n {\n type: \"text\",\n text: `No registered project found for path: ${target}\\n\\nRun 'pai project add .' to register this directory.`,\n },\n ],\n };\n }\n\n return {\n content: [{ type: \"text\", text: formatDetectionJson(detection) }],\n };\n } catch (e) {\n return {\n content: [\n { type: \"text\", text: `project_detect error: ${String(e)}` },\n ],\n isError: true,\n };\n }\n}\n\n// ---------------------------------------------------------------------------\n// Tool: project_health\n// ---------------------------------------------------------------------------\n\nexport interface ProjectHealthParams {\n category?: \"active\" | \"stale\" | \"dead\" | \"all\";\n}\n\nexport async function toolProjectHealth(\n registry: RegistryBackend,\n params: ProjectHealthParams\n): Promise<ToolResult> {\n try {\n const { existsSync: fsExists, readdirSync, statSync } = await import(\n \"node:fs\"\n );\n const {\n join: pathJoin,\n basename: pathBasename,\n } = await import(\"node:path\");\n const { homedir } = await import(\"node:os\");\n const { encodeDir: enc } = await import(\"../../cli/utils.js\");\n\n const rows = (await registry.listProjectsWithSessionStats())\n .slice()\n .sort((a, b) => a.slug.localeCompare(b.slug));\n\n const home = homedir();\n const claudeProjects = pathJoin(home, \".claude\", \"projects\");\n\n function suggestMoved(rootPath: string): string | undefined {\n const name = pathBasename(rootPath);\n const candidates = [\n pathJoin(home, \"dev\", name),\n pathJoin(home, \"dev\", \"ai\", name),\n pathJoin(home, \"Desktop\", name),\n pathJoin(home, \"Projects\", name),\n ];\n return candidates.find((c) => fsExists(c));\n }\n\n function hasClaudeNotes(encodedDir: string): boolean {\n if (!fsExists(claudeProjects)) return false;\n try {\n for (const entry of readdirSync(claudeProjects)) {\n if (entry !== encodedDir && !entry.startsWith(encodedDir)) continue;\n const full = pathJoin(claudeProjects, entry);\n try {\n if (!statSync(full).isDirectory()) continue;\n } catch {\n continue;\n }\n if (fsExists(pathJoin(full, \"Notes\"))) return true;\n }\n } catch {\n /* ignore */\n }\n return false;\n }\n\n interface HealthResult {\n slug: string;\n display_name: string;\n root_path: string;\n status: string;\n type: string;\n session_count: number;\n health: string;\n suggested_path: string | null;\n has_claude_notes: boolean;\n todo: {\n found: boolean;\n path: string | null;\n has_continue: boolean;\n };\n }\n\n function findTodoForProject(rootPath: string): {\n found: boolean;\n path: string | null;\n has_continue: boolean;\n } {\n const locs = [\n \"Notes/TODO.md\",\n \".claude/Notes/TODO.md\",\n \"tasks/todo.md\",\n \"TODO.md\",\n ];\n for (const rel of locs) {\n const full = pathJoin(rootPath, rel);\n if (fsExists(full)) {\n try {\n const raw = readFileSync(full, \"utf8\");\n const hasContinue = /^## Continue$/m.test(raw);\n return { found: true, path: rel, has_continue: hasContinue };\n } catch {\n return { found: true, path: rel, has_continue: false };\n }\n }\n }\n return { found: false, path: null, has_continue: false };\n }\n\n const results: HealthResult[] = rows.map((p) => {\n const pathExists = fsExists(p.root_path);\n let health: string;\n let suggestedPath: string | null = null;\n\n if (pathExists) {\n health = \"active\";\n } else {\n suggestedPath = suggestMoved(p.root_path) ?? null;\n health = suggestedPath ? \"stale\" : \"dead\";\n }\n\n const todo = pathExists\n ? findTodoForProject(p.root_path)\n : { found: false, path: null, has_continue: false };\n\n return {\n slug: p.slug,\n display_name: p.display_name,\n root_path: p.root_path,\n status: p.status,\n type: p.type,\n session_count: p.session_count,\n health,\n suggested_path: suggestedPath,\n has_claude_notes: hasClaudeNotes(p.encoded_dir),\n todo,\n };\n });\n\n const filtered =\n !params.category || params.category === \"all\"\n ? results\n : results.filter((r) => r.health === params.category);\n\n const summary = {\n total: rows.length,\n active: results.filter((r) => r.health === \"active\").length,\n stale: results.filter((r) => r.health === \"stale\").length,\n dead: results.filter((r) => r.health === \"dead\").length,\n };\n\n return {\n content: [\n {\n type: \"text\",\n text: JSON.stringify({ summary, projects: filtered }, null, 2),\n },\n ],\n };\n } catch (e) {\n return {\n content: [\n { type: \"text\", text: `project_health error: ${String(e)}` },\n ],\n isError: true,\n };\n }\n}\n\n// ---------------------------------------------------------------------------\n// Tool: project_todo\n// ---------------------------------------------------------------------------\n\nexport interface ProjectTodoParams {\n project?: string;\n}\n\n/**\n * TODO candidate locations searched in priority order.\n * Returns the first one that exists, along with its label.\n */\nconst TODO_LOCATIONS = [\n { rel: \"Notes/TODO.md\", label: \"Notes/TODO.md\" },\n { rel: \".claude/Notes/TODO.md\", label: \".claude/Notes/TODO.md\" },\n { rel: \"tasks/todo.md\", label: \"tasks/todo.md\" },\n { rel: \"TODO.md\", label: \"TODO.md\" },\n];\n\n/**\n * Given TODO file content, extract and surface the ## Continue section first,\n * then return the remaining content. Returns an object with:\n * continueSection: string | null\n * fullContent: string\n * hasContinue: boolean\n */\nfunction parseTodoContent(raw: string): {\n continueSection: string | null;\n fullContent: string;\n hasContinue: boolean;\n} {\n const lines = raw.split(\"\\n\");\n\n // Find the ## Continue heading\n const continueIdx = lines.findIndex(\n (l) => l.trim() === \"## Continue\"\n );\n\n if (continueIdx === -1) {\n return { continueSection: null, fullContent: raw, hasContinue: false };\n }\n\n // The section ends at the first `---` separator or next `##` heading after\n // the Continue heading (whichever comes first).\n let endIdx = lines.length;\n for (let i = continueIdx + 1; i < lines.length; i++) {\n const trimmed = lines[i].trim();\n if (trimmed === \"---\" || (trimmed.startsWith(\"##\") && trimmed !== \"## Continue\")) {\n endIdx = i;\n break;\n }\n }\n\n const continueLines = lines.slice(continueIdx, endIdx);\n const continueSection = continueLines.join(\"\\n\").trim();\n\n return { continueSection, fullContent: raw, hasContinue: true };\n}\n\nexport async function toolProjectTodo(\n registry: RegistryBackend,\n params: ProjectTodoParams\n): Promise<ToolResult> {\n try {\n let rootPath: string;\n let projectSlug: string;\n\n if (params.project) {\n const projectId = await lookupProjectId(registry, params.project);\n if (projectId == null) {\n return {\n content: [\n { type: \"text\", text: `Project not found: ${params.project}` },\n ],\n isError: true,\n };\n }\n\n const row = await registry.getProjectById(projectId);\n\n if (!row) {\n return {\n content: [\n { type: \"text\", text: `Project not found: ${params.project}` },\n ],\n isError: true,\n };\n }\n\n rootPath = row.root_path;\n projectSlug = row.slug;\n } else {\n // Auto-detect from cwd\n const project = await detectProjectFromPath(registry, process.cwd());\n if (!project) {\n return {\n content: [\n {\n type: \"text\",\n text: `No PAI project found matching the current directory: ${process.cwd()}\\n\\nProvide a project slug or run 'pai project add .' to register this directory.`,\n },\n ],\n };\n }\n rootPath = project.root_path;\n projectSlug = project.slug;\n }\n\n // Search for TODO in priority order\n for (const loc of TODO_LOCATIONS) {\n const fullPath = join(rootPath, loc.rel);\n if (existsSync(fullPath)) {\n const raw = readFileSync(fullPath, \"utf8\");\n const { continueSection, fullContent, hasContinue } = parseTodoContent(raw);\n\n let output: string;\n if (hasContinue && continueSection) {\n // Surface the ## Continue section first, then the full content\n output = [\n `TODO found: ${projectSlug}/${loc.label}`,\n \"\",\n \"=== CONTINUE SECTION (surfaced first) ===\",\n continueSection,\n \"\",\n \"=== FULL TODO CONTENT ===\",\n fullContent,\n ].join(\"\\n\");\n } else {\n output = [\n `TODO found: ${projectSlug}/${loc.label}`,\n \"\",\n fullContent,\n ].join(\"\\n\");\n }\n\n return {\n content: [{ type: \"text\", text: output }],\n };\n }\n }\n\n // No TODO found in any location\n const searched = TODO_LOCATIONS.map((l) => ` ${rootPath}/${l.rel}`).join(\"\\n\");\n return {\n content: [\n {\n type: \"text\",\n text: [\n `No TODO.md found for project: ${projectSlug}`,\n \"\",\n \"Searched locations (in order):\",\n searched,\n \"\",\n \"Create a TODO with: echo '## Tasks\\\\n- [ ] First task' > Notes/TODO.md\",\n ].join(\"\\n\"),\n },\n ],\n };\n } catch (e) {\n return {\n content: [{ type: \"text\", text: `project_todo error: ${String(e)}` }],\n isError: true,\n };\n }\n}\n","/**\n * MCP tool handlers: session_list, session_route\n */\n\nimport type { StorageBackend } from \"../../storage/interface.js\";\nimport type { RegistryBackend } from \"../../storage/registry-interface.js\";\nimport {\n lookupProjectId,\n type ToolResult,\n} from \"./types.js\";\n\n// ---------------------------------------------------------------------------\n// Tool: session_list\n// ---------------------------------------------------------------------------\n\nexport interface SessionListParams {\n project: string;\n limit?: number;\n status?: \"open\" | \"completed\" | \"compacted\";\n}\n\nexport async function toolSessionList(\n registry: RegistryBackend,\n params: SessionListParams\n): Promise<ToolResult> {\n try {\n const projectId = await lookupProjectId(registry, params.project);\n if (projectId == null) {\n return {\n content: [\n { type: \"text\", text: `Project not found: ${params.project}` },\n ],\n isError: true,\n };\n }\n\n const limit = params.limit ?? 10;\n const sessions = await registry.listSessions({\n projectId,\n status: params.status,\n limit,\n });\n\n if (sessions.length === 0) {\n return {\n content: [\n {\n type: \"text\",\n text: `No sessions found for project: ${params.project}`,\n },\n ],\n };\n }\n\n const lines = sessions.map(\n (s) =>\n `#${String(s.number).padStart(4, \"0\")} ${s.date} [${s.status}] ${s.title}\\n file: Notes/${s.filename}`\n );\n\n return {\n content: [\n {\n type: \"text\",\n text: `${sessions.length} session(s) for ${params.project}:\\n\\n${lines.join(\"\\n\\n\")}`,\n },\n ],\n };\n } catch (e) {\n return {\n content: [{ type: \"text\", text: `session_list error: ${String(e)}` }],\n isError: true,\n };\n }\n}\n\n// ---------------------------------------------------------------------------\n// Tool: session_route\n// ---------------------------------------------------------------------------\n\nexport interface SessionRouteParams {\n /** Working directory to route from (defaults to process.cwd()) */\n cwd?: string;\n /** Optional conversation context for topic-based fallback routing */\n context?: string;\n}\n\n/**\n * Automatically suggest which project a session belongs to.\n *\n * Strategy (in priority order):\n * 1. path — exact or parent-directory match in the project registry\n * 2. marker — walk up from cwd looking for Notes/PAI.md\n * 3. topic — BM25 keyword search against memory (requires context)\n *\n * Call this at session start (e.g., from CLAUDE.md or a session-start hook)\n * to automatically route the session to the correct project.\n */\nexport async function toolSessionRoute(\n registry: RegistryBackend,\n federation: StorageBackend,\n params: SessionRouteParams\n): Promise<ToolResult> {\n try {\n const { autoRoute, formatAutoRouteJson } = await import(\"../../session/auto-route.js\");\n\n const result = await autoRoute(\n registry,\n federation,\n params.cwd,\n params.context\n );\n\n if (!result) {\n const target = params.cwd ?? process.cwd();\n return {\n content: [\n {\n type: \"text\",\n text: [\n `No project match found for: ${target}`,\n \"\",\n \"Tried: path match, PAI.md marker walk\" +\n (params.context ? \", topic detection\" : \"\"),\n \"\",\n \"Run 'pai project add .' to register this directory,\",\n \"or provide conversation context for topic-based routing.\",\n ].join(\"\\n\"),\n },\n ],\n };\n }\n\n return {\n content: [{ type: \"text\", text: formatAutoRouteJson(result) }],\n };\n } catch (e) {\n return {\n content: [{ type: \"text\", text: `session_route error: ${String(e)}` }],\n isError: true,\n };\n }\n}\n","/**\n * MCP tool handler: registry_search\n */\n\nimport type { RegistryBackend } from \"../../storage/registry-interface.js\";\nimport type { ToolResult } from \"./types.js\";\n\n// ---------------------------------------------------------------------------\n// Tool: registry_search\n// ---------------------------------------------------------------------------\n\nexport interface RegistrySearchParams {\n query: string;\n}\n\nexport async function toolRegistrySearch(\n registry: RegistryBackend,\n params: RegistrySearchParams\n): Promise<ToolResult> {\n try {\n const projects = await registry.searchProjects(params.query, 20);\n\n if (projects.length === 0) {\n return {\n content: [\n {\n type: \"text\",\n text: `No projects found matching: \"${params.query}\"`,\n },\n ],\n };\n }\n\n const lines = projects.map((p) => `${p.slug} [${p.status}] ${p.root_path}`);\n\n return {\n content: [\n {\n type: \"text\",\n text: `${projects.length} match(es) for \"${params.query}\":\\n\\n${lines.join(\"\\n\")}`,\n },\n ],\n };\n } catch (e) {\n return {\n content: [\n { type: \"text\", text: `registry_search error: ${String(e)}` },\n ],\n isError: true,\n };\n }\n}\n","/**\n * MCP tool handlers: zettel_explore, zettel_health, zettel_surprise,\n * zettel_suggest, zettel_converse, zettel_themes,\n * zettel_god_notes, zettel_communities\n */\n\nimport type { StorageBackend } from \"../../storage/interface.js\";\nimport type { ToolResult } from \"./types.js\";\n\n// ---------------------------------------------------------------------------\n// Tool: zettel_explore\n// ---------------------------------------------------------------------------\n\nexport interface ZettelExploreParams {\n start_note: string;\n depth?: number;\n direction?: string;\n mode?: string;\n}\n\nexport async function toolZettelExplore(\n backend: StorageBackend,\n params: ZettelExploreParams\n): Promise<ToolResult> {\n try {\n const { zettelExplore } = await import(\"../../zettelkasten/index.js\");\n const result = await zettelExplore(backend, {\n startNote: params.start_note,\n depth: params.depth,\n direction: params.direction as \"forward\" | \"backward\" | \"both\" | undefined,\n mode: params.mode as \"sequential\" | \"associative\" | \"all\" | undefined,\n });\n return {\n content: [{ type: \"text\", text: JSON.stringify(result, null, 2) }],\n };\n } catch (e) {\n return {\n content: [{ type: \"text\", text: `zettel_explore error: ${String(e)}` }],\n isError: true,\n };\n }\n}\n\n// ---------------------------------------------------------------------------\n// Tool: zettel_health\n// ---------------------------------------------------------------------------\n\nexport interface ZettelHealthParams {\n scope?: string;\n project_path?: string;\n recent_days?: number;\n include?: string[];\n}\n\nexport async function toolZettelHealth(\n backend: StorageBackend,\n params: ZettelHealthParams\n): Promise<ToolResult> {\n try {\n const { zettelHealth } = await import(\"../../zettelkasten/index.js\");\n const result = await zettelHealth(backend, {\n scope: params.scope as \"full\" | \"recent\" | \"project\" | undefined,\n projectPath: params.project_path,\n recentDays: params.recent_days,\n include: params.include as Array<\"dead_links\" | \"orphans\" | \"disconnected\" | \"low_connectivity\"> | undefined,\n });\n return {\n content: [{ type: \"text\", text: JSON.stringify(result, null, 2) }],\n };\n } catch (e) {\n return {\n content: [{ type: \"text\", text: `zettel_health error: ${String(e)}` }],\n isError: true,\n };\n }\n}\n\n// ---------------------------------------------------------------------------\n// Tool: zettel_surprise\n// ---------------------------------------------------------------------------\n\nexport interface ZettelSurpriseParams {\n reference_path: string;\n vault_project_id: number;\n limit?: number;\n min_similarity?: number;\n min_graph_distance?: number;\n}\n\nexport async function toolZettelSurprise(\n backend: StorageBackend,\n params: ZettelSurpriseParams\n): Promise<ToolResult> {\n try {\n const { zettelSurprise } = await import(\"../../zettelkasten/index.js\");\n const results = await zettelSurprise(backend, {\n referencePath: params.reference_path,\n vaultProjectId: params.vault_project_id,\n limit: params.limit,\n minSimilarity: params.min_similarity,\n minGraphDistance: params.min_graph_distance,\n });\n return {\n content: [{ type: \"text\", text: JSON.stringify(results, null, 2) }],\n };\n } catch (e) {\n return {\n content: [{ type: \"text\", text: `zettel_surprise error: ${String(e)}` }],\n isError: true,\n };\n }\n}\n\n// ---------------------------------------------------------------------------\n// Tool: zettel_suggest\n// ---------------------------------------------------------------------------\n\nexport interface ZettelSuggestParams {\n note_path: string;\n vault_project_id: number;\n limit?: number;\n exclude_linked?: boolean;\n}\n\nexport async function toolZettelSuggest(\n backend: StorageBackend,\n params: ZettelSuggestParams\n): Promise<ToolResult> {\n try {\n const { zettelSuggest } = await import(\"../../zettelkasten/index.js\");\n const results = await zettelSuggest(backend, {\n notePath: params.note_path,\n vaultProjectId: params.vault_project_id,\n limit: params.limit,\n excludeLinked: params.exclude_linked,\n });\n return {\n content: [{ type: \"text\", text: JSON.stringify(results, null, 2) }],\n };\n } catch (e) {\n return {\n content: [{ type: \"text\", text: `zettel_suggest error: ${String(e)}` }],\n isError: true,\n };\n }\n}\n\n// ---------------------------------------------------------------------------\n// Tool: zettel_converse\n// ---------------------------------------------------------------------------\n\nexport interface ZettelConverseParams {\n question: string;\n vault_project_id: number;\n depth?: number;\n limit?: number;\n}\n\nexport async function toolZettelConverse(\n backend: StorageBackend,\n params: ZettelConverseParams\n): Promise<ToolResult> {\n try {\n const { zettelConverse } = await import(\"../../zettelkasten/index.js\");\n const result = await zettelConverse(backend, {\n question: params.question,\n vaultProjectId: params.vault_project_id,\n depth: params.depth,\n limit: params.limit,\n });\n\n // Query feedback loop: save query + result metadata for future indexing\n try {\n const { saveQueryResult } = await import(\"../../zettelkasten/query-feedback.js\");\n saveQueryResult({\n query: params.question,\n timestamp: Date.now(),\n source: \"zettel_converse\",\n sourceSlugs: result.relevantNotes.slice(0, 5).map((n) => n.path),\n answerPreview: result.relevantNotes.slice(0, 3).map((n) => {\n const title = n.title ?? \"(untitled)\";\n return `${title}: ${n.snippet.trim().slice(0, 100)}`;\n }).join(\" | \"),\n resultCount: result.relevantNotes.length,\n });\n } catch {\n // Non-critical\n }\n\n return {\n content: [{ type: \"text\", text: JSON.stringify(result, null, 2) }],\n };\n } catch (e) {\n return {\n content: [{ type: \"text\", text: `zettel_converse error: ${String(e)}` }],\n isError: true,\n };\n }\n}\n\n// ---------------------------------------------------------------------------\n// Tool: zettel_themes\n// ---------------------------------------------------------------------------\n\nexport interface ZettelThemesParams {\n vault_project_id: number;\n lookback_days?: number;\n min_cluster_size?: number;\n max_themes?: number;\n similarity_threshold?: number;\n}\n\nexport async function toolZettelThemes(\n backend: StorageBackend,\n params: ZettelThemesParams\n): Promise<ToolResult> {\n try {\n const { zettelThemes } = await import(\"../../zettelkasten/index.js\");\n const result = await zettelThemes(backend, {\n vaultProjectId: params.vault_project_id,\n lookbackDays: params.lookback_days,\n minClusterSize: params.min_cluster_size,\n maxThemes: params.max_themes,\n similarityThreshold: params.similarity_threshold,\n });\n return {\n content: [{ type: \"text\", text: JSON.stringify(result, null, 2) }],\n };\n } catch (e) {\n return {\n content: [{ type: \"text\", text: `zettel_themes error: ${String(e)}` }],\n isError: true,\n };\n }\n}\n\n// ---------------------------------------------------------------------------\n// Tool: zettel_god_notes\n// ---------------------------------------------------------------------------\n\nexport interface ZettelGodNotesParams {\n limit?: number;\n min_inbound?: number;\n}\n\nexport async function toolZettelGodNotes(\n backend: StorageBackend,\n params: ZettelGodNotesParams\n): Promise<ToolResult> {\n try {\n const { zettelGodNotes } = await import(\"../../zettelkasten/index.js\");\n const result = await zettelGodNotes(backend, {\n limit: params.limit,\n minInbound: params.min_inbound,\n });\n return {\n content: [{ type: \"text\", text: JSON.stringify(result, null, 2) }],\n };\n } catch (e) {\n return {\n content: [{ type: \"text\", text: `zettel_god_notes error: ${String(e)}` }],\n isError: true,\n };\n }\n}\n\n// ---------------------------------------------------------------------------\n// Tool: zettel_communities\n// ---------------------------------------------------------------------------\n\nexport interface ZettelCommunitiesParams {\n min_size?: number;\n max_communities?: number;\n resolution?: number;\n}\n\nexport async function toolZettelCommunities(\n backend: StorageBackend,\n params: ZettelCommunitiesParams\n): Promise<ToolResult> {\n try {\n const { zettelCommunities } = await import(\"../../zettelkasten/index.js\");\n const result = await zettelCommunities(backend, {\n minSize: params.min_size,\n maxCommunities: params.max_communities,\n resolution: params.resolution,\n });\n return {\n content: [{ type: \"text\", text: JSON.stringify(result, null, 2) }],\n };\n } catch (e) {\n return {\n content: [{ type: \"text\", text: `zettel_communities error: ${String(e)}` }],\n isError: true,\n };\n }\n}\n","/**\n * MCP tool handlers for the temporal knowledge graph.\n *\n * Tools:\n * kg_add — insert a new (subject, predicate, object) triple\n * kg_query — query triples with optional temporal filter\n * kg_invalidate — mark a triple as no longer valid (sets valid_to)\n * kg_contradictions — find (subject, predicate) pairs with multiple valid objects\n */\n\nimport type { StorageBackend } from \"../../storage/interface.js\";\nimport type { ToolResult } from \"./types.js\";\n\n// ---------------------------------------------------------------------------\n// Tool: kg_add\n// ---------------------------------------------------------------------------\n\nexport interface KgAddToolParams {\n subject: string;\n predicate: string;\n object: string;\n project_id?: number;\n source_session?: string;\n confidence?: \"EXTRACTED\" | \"INFERRED\" | \"AMBIGUOUS\";\n}\n\nexport async function toolKgAdd(\n backend: StorageBackend,\n params: KgAddToolParams\n): Promise<ToolResult> {\n try {\n if (!params.subject || !params.predicate || !params.object) {\n return {\n content: [{ type: \"text\", text: \"kg_add error: subject, predicate, and object are required\" }],\n isError: true,\n };\n }\n const triple = await backend.addKgTriple(params);\n return {\n content: [{ type: \"text\", text: JSON.stringify(triple, null, 2) }],\n };\n } catch (e) {\n return {\n content: [{ type: \"text\", text: `kg_add error: ${String(e)}` }],\n isError: true,\n };\n }\n}\n\n// ---------------------------------------------------------------------------\n// Tool: kg_query\n// ---------------------------------------------------------------------------\n\nexport interface KgQueryToolParams {\n subject?: string;\n predicate?: string;\n object?: string;\n project_id?: number;\n as_of?: string; // ISO 8601 string; converted to Date\n include_invalidated?: boolean;\n}\n\nexport async function toolKgQuery(\n backend: StorageBackend,\n params: KgQueryToolParams\n): Promise<ToolResult> {\n try {\n const asOf = params.as_of ? new Date(params.as_of) : undefined;\n if (asOf && isNaN(asOf.getTime())) {\n return {\n content: [{ type: \"text\", text: `kg_query error: invalid as_of date: ${params.as_of}` }],\n isError: true,\n };\n }\n const triples = await backend.queryKgTriples({\n subject: params.subject,\n predicate: params.predicate,\n object: params.object,\n project_id: params.project_id,\n as_of: asOf,\n include_invalidated: params.include_invalidated,\n });\n return {\n content: [{ type: \"text\", text: JSON.stringify(triples, null, 2) }],\n };\n } catch (e) {\n return {\n content: [{ type: \"text\", text: `kg_query error: ${String(e)}` }],\n isError: true,\n };\n }\n}\n\n// ---------------------------------------------------------------------------\n// Tool: kg_invalidate\n// ---------------------------------------------------------------------------\n\nexport interface KgInvalidateToolParams {\n triple_id: number;\n}\n\nexport async function toolKgInvalidate(\n backend: StorageBackend,\n params: KgInvalidateToolParams\n): Promise<ToolResult> {\n try {\n if (params.triple_id === undefined || params.triple_id === null) {\n return {\n content: [{ type: \"text\", text: \"kg_invalidate error: triple_id is required\" }],\n isError: true,\n };\n }\n await backend.invalidateKgTriple(params.triple_id);\n return {\n content: [{ type: \"text\", text: JSON.stringify({ invalidated: true, triple_id: params.triple_id }) }],\n };\n } catch (e) {\n return {\n content: [{ type: \"text\", text: `kg_invalidate error: ${String(e)}` }],\n isError: true,\n };\n }\n}\n\n// ---------------------------------------------------------------------------\n// Tool: kg_contradictions\n// ---------------------------------------------------------------------------\n\nexport interface KgContradictionsToolParams {\n subject: string;\n}\n\nexport async function toolKgContradictions(\n backend: StorageBackend,\n params: KgContradictionsToolParams\n): Promise<ToolResult> {\n try {\n if (!params.subject) {\n return {\n content: [{ type: \"text\", text: \"kg_contradictions error: subject is required\" }],\n isError: true,\n };\n }\n const contradictions = await backend.getKgContradictions(params.subject);\n return {\n content: [{ type: \"text\", text: JSON.stringify(contradictions, null, 2) }],\n };\n } catch (e) {\n return {\n content: [{ type: \"text\", text: `kg_contradictions error: ${String(e)}` }],\n isError: true,\n };\n }\n}\n","/**\n * MCP tool handler: memory_wakeup\n *\n * Returns the L0+L1 wake-up context block for a project:\n * L0 — user identity from ~/.pai/identity.txt\n * L1 — recent session note highlights (Work Done / Key Decisions / Next Steps)\n */\n\nimport type { RegistryBackend } from \"../../storage/registry-interface.js\";\nimport { buildWakeupContext } from \"../../memory/wakeup.js\";\nimport { detectProjectFromPath } from \"./types.js\";\nimport type { ToolResult } from \"./types.js\";\n\n// ---------------------------------------------------------------------------\n// Default token budget (constant, overridable per call)\n// ---------------------------------------------------------------------------\n\nconst DEFAULT_TOKEN_BUDGET = 800;\n\n// ---------------------------------------------------------------------------\n// Params\n// ---------------------------------------------------------------------------\n\nexport interface MemoryWakeupParams {\n /** Project slug or absolute path. Omit to auto-detect from process.cwd(). */\n project?: string;\n /** L1 token budget. Default 800. */\n token_budget?: number;\n}\n\n// ---------------------------------------------------------------------------\n// Tool handler\n// ---------------------------------------------------------------------------\n\nexport async function toolMemoryWakeup(\n registry: RegistryBackend,\n params: MemoryWakeupParams\n): Promise<ToolResult> {\n try {\n const tokenBudget = params.token_budget ?? DEFAULT_TOKEN_BUDGET;\n\n // Resolve the project root path\n let rootPath: string | undefined;\n\n if (params.project) {\n // Try slug lookup first\n const bySlug = await registry.getProjectBySlug(params.project);\n\n if (bySlug) {\n rootPath = bySlug.root_path;\n } else {\n // Maybe it's an absolute path — try path-based detect\n const detected = await detectProjectFromPath(registry, params.project);\n if (detected) rootPath = detected.root_path;\n }\n } else {\n // Auto-detect from cwd\n const detected = await detectProjectFromPath(registry, process.cwd());\n if (detected) rootPath = detected.root_path;\n }\n\n const wakeupBlock = buildWakeupContext(rootPath, tokenBudget);\n\n if (!wakeupBlock) {\n return {\n content: [\n {\n type: \"text\",\n text: \"No wake-up context available. Create ~/.pai/identity.txt for L0 identity, or ensure session notes exist for L1 story.\",\n },\n ],\n };\n }\n\n return {\n content: [\n {\n type: \"text\",\n text: `WAKEUP CONTEXT\\n\\n${wakeupBlock}`,\n },\n ],\n };\n } catch (e) {\n return {\n content: [{ type: \"text\", text: `Wakeup context error: ${String(e)}` }],\n isError: true,\n };\n }\n}\n","/**\n * getTaxonomy — returns the shape of stored memory without requiring a query.\n *\n * Answers \"what do I know about?\" not \"what do I know about X?\"\n *\n * Uses the registry DB (projects, sessions) and the storage backend\n * (memory_files, memory_chunks) to build a structural overview.\n */\n\nimport type { StorageBackend } from \"../storage/interface.js\";\nimport type { RegistryBackend } from \"../storage/registry-interface.js\";\n\n// ---------------------------------------------------------------------------\n// Return types\n// ---------------------------------------------------------------------------\n\nexport interface TaxonomyProject {\n slug: string;\n display_name: string;\n session_count: number;\n note_count: number;\n last_activity: string | null; // ISO date string, e.g. \"2026-04-07\"\n top_tags: string[]; // project tags from the registry\n}\n\nexport interface TaxonomyTotals {\n projects: number;\n sessions: number;\n notes: number;\n chunks: number;\n}\n\nexport interface TaxonomyRecentActivity {\n project_slug: string;\n action: string;\n timestamp: string; // ISO date string\n}\n\nexport interface TaxonomyResult {\n projects: TaxonomyProject[];\n totals: TaxonomyTotals;\n recent_activity: TaxonomyRecentActivity[];\n}\n\n// ---------------------------------------------------------------------------\n// Options\n// ---------------------------------------------------------------------------\n\nexport interface TaxonomyOptions {\n /** Include archived projects. Default: false. */\n include_archived?: boolean;\n /** Maximum projects to return. Default: 50. */\n limit?: number;\n}\n\n// ---------------------------------------------------------------------------\n// Main function\n// ---------------------------------------------------------------------------\n\n/**\n * Build a taxonomy of stored memory — what projects exist, how much is stored,\n * and what has been active recently.\n *\n * Registry queries (projects, sessions) go through RegistryBackend (async).\n * Storage backend queries (files, chunks) are async.\n */\nexport async function getTaxonomy(\n registry: RegistryBackend,\n storage: StorageBackend,\n options: TaxonomyOptions = {}\n): Promise<TaxonomyResult> {\n const includeArchived = options.include_archived ?? false;\n const limit = options.limit ?? 50;\n\n // -------------------------------------------------------------------------\n // 1. Load all (active) projects from the registry\n // -------------------------------------------------------------------------\n\n const projectRows = includeArchived\n ? await registry.listProjects({ orderBy: \"updated_desc\", limit })\n : await registry.listProjects({ status: \"active\", orderBy: \"updated_desc\", limit });\n\n if (projectRows.length === 0) {\n return {\n projects: [],\n totals: { projects: 0, sessions: 0, notes: 0, chunks: 0 },\n recent_activity: [],\n };\n }\n\n const projectIds = projectRows.map((p) => p.id);\n const projectIdSet = new Set(projectIds);\n\n // -------------------------------------------------------------------------\n // 2. Session counts per project (registry, async)\n // -------------------------------------------------------------------------\n\n const sessionCountsByProject = new Map<number, number>();\n const lastSessionDateByProject = new Map<number, string | null>();\n\n for (const projectId of projectIds) {\n sessionCountsByProject.set(projectId, await registry.countSessionsForProject(projectId));\n lastSessionDateByProject.set(projectId, await registry.getMostRecentSessionDate(projectId));\n }\n\n // -------------------------------------------------------------------------\n // 3. Tags per project (registry, async)\n // -------------------------------------------------------------------------\n\n const tagsByProject = new Map<number, string[]>();\n\n for (const projectId of projectIds) {\n tagsByProject.set(projectId, await registry.listTagsForProject(projectId));\n }\n\n // -------------------------------------------------------------------------\n // 4. Note and chunk counts per project (storage backend, async)\n // memory_files (one row per indexed file) and memory_chunks (many per\n // file), via StorageBackend.getProjectStats() — same on both backends.\n // -------------------------------------------------------------------------\n\n const noteCountsByProject = new Map<number, number>();\n const chunkCountsByProject = new Map<number, number>();\n\n for (const projectId of projectIds) {\n const projectStats = await storage.getProjectStats(projectId);\n noteCountsByProject.set(projectId, projectStats.files);\n chunkCountsByProject.set(projectId, projectStats.chunks);\n }\n\n // -------------------------------------------------------------------------\n // 5. Global totals\n // -------------------------------------------------------------------------\n\n const stats = await storage.getStats();\n\n const totalProjects = includeArchived\n ? await registry.countProjects()\n : await registry.countProjects({ status: \"active\" });\n\n // No RegistryBackend method for a global session count (flagged in the\n // report) — approximate as the sum over the projects already fetched\n // above (bounded by `limit`), the closest available without raw SQL.\n const totalSessions = [...sessionCountsByProject.values()].reduce((a, b) => a + b, 0);\n\n // -------------------------------------------------------------------------\n // 6. Recent activity — last 10 sessions across all (visible) projects\n // No RegistryBackend method filters sessions by their project's status\n // directly (flagged in the report) — over-fetch and filter client-side\n // against the project set already resolved above.\n // -------------------------------------------------------------------------\n\n const recentSessions = (await registry.listSessions({ limit: Math.max(50, limit) }))\n .filter((s) => projectIdSet.has(s.project_id))\n .slice(0, 10);\n\n const recentActivity: TaxonomyRecentActivity[] = recentSessions.map((row) => ({\n project_slug: row.project_slug,\n action: `session: ${row.title || \"(untitled)\"}`,\n timestamp: row.date,\n }));\n\n // -------------------------------------------------------------------------\n // 7. Assemble result\n // -------------------------------------------------------------------------\n\n const projects: TaxonomyProject[] = projectRows.map((row) => ({\n slug: row.slug,\n display_name: row.display_name,\n session_count: sessionCountsByProject.get(row.id) ?? 0,\n note_count: noteCountsByProject.get(row.id) ?? 0,\n last_activity: lastSessionDateByProject.get(row.id) ?? null,\n top_tags: tagsByProject.get(row.id) ?? [],\n }));\n\n return {\n projects,\n totals: {\n projects: totalProjects,\n sessions: totalSessions,\n notes: stats.files,\n chunks: stats.chunks,\n },\n recent_activity: recentActivity,\n };\n}\n","/**\n * MCP tool handler: memory_taxonomy\n *\n * Returns the SHAPE of stored memory without requiring a query.\n * Answers \"what do I know about?\" not \"what do I know about X?\"\n */\n\nimport type { StorageBackend } from \"../../storage/interface.js\";\nimport type { RegistryBackend } from \"../../storage/registry-interface.js\";\nimport { getTaxonomy } from \"../../memory/taxonomy.js\";\nimport type { ToolResult } from \"./types.js\";\n\n// ---------------------------------------------------------------------------\n// Tool: memory_taxonomy\n// ---------------------------------------------------------------------------\n\nexport interface MemoryTaxonomyParams {\n /** Include archived projects in the result. Default: false. */\n include_archived?: boolean;\n /** Maximum number of projects to return. Default: 50. */\n limit?: number;\n}\n\nexport async function toolMemoryTaxonomy(\n registry: RegistryBackend,\n storage: StorageBackend,\n params: MemoryTaxonomyParams = {}\n): Promise<ToolResult> {\n try {\n const result = await getTaxonomy(registry, storage, {\n include_archived: params.include_archived,\n limit: params.limit,\n });\n\n // -----------------------------------------------------------------------\n // Format output as human-readable text\n // -----------------------------------------------------------------------\n\n const lines: string[] = [];\n\n // Totals header\n lines.push(\n `PAI Memory Taxonomy — ${result.totals.projects} project(s), ` +\n `${result.totals.sessions} session(s), ` +\n `${result.totals.notes} indexed file(s), ` +\n `${result.totals.chunks} chunk(s)`\n );\n lines.push(\"\");\n\n // Per-project breakdown\n if (result.projects.length === 0) {\n lines.push(\"No active projects found.\");\n } else {\n lines.push(\"Projects:\");\n for (const p of result.projects) {\n const tagStr = p.top_tags.length > 0 ? ` [${p.top_tags.join(\", \")}]` : \"\";\n const activityStr = p.last_activity ? ` last: ${p.last_activity}` : \"\";\n lines.push(\n ` ${p.slug} — ${p.display_name}` +\n ` sessions=${p.session_count}` +\n (p.note_count > 0 ? ` files=${p.note_count}` : \"\") +\n activityStr +\n tagStr\n );\n }\n }\n\n // Recent activity\n if (result.recent_activity.length > 0) {\n lines.push(\"\");\n lines.push(\"Recent activity (last 10 sessions across all projects):\");\n for (const a of result.recent_activity) {\n lines.push(` ${a.timestamp} ${a.project_slug} ${a.action}`);\n }\n }\n\n return {\n content: [{ type: \"text\", text: lines.join(\"\\n\") }],\n };\n } catch (e) {\n return {\n content: [{ type: \"text\", text: `memory_taxonomy error: ${String(e)}` }],\n isError: true,\n };\n }\n}\n","/**\n * tunnels.ts — cross-project concept detection (\"palace graph / tunnel detection\")\n *\n * A \"tunnel\" is a concept (word or short phrase) that appears in chunks from\n * at least two distinct projects. These serendipitous cross-project connections\n * are surfaced so the user can discover unexpected relationships between their\n * work streams.\n *\n * The actual SQL (SQLite FTS5 vocab / Postgres ts_stat) lives under\n * src/storage/{sqlite,postgres}/tunnels.ts, reached through\n * StorageBackend.findTunnels() — this module just re-exports the public\n * types and forwards the call, so existing callers (MCP tools) don't have\n * to change their import path.\n */\n\nimport type { StorageBackend } from \"../storage/interface.js\";\nimport type { RegistryBackend } from \"../storage/registry-interface.js\";\n\n// ---------------------------------------------------------------------------\n// Public types\n// ---------------------------------------------------------------------------\n\nexport interface Tunnel {\n /** The shared term or phrase. */\n concept: string;\n /** Project slugs where this concept appears. */\n projects: string[];\n /** Total chunk occurrences across all projects. */\n occurrences: number;\n /** First time the concept appeared (Unix ms). */\n first_seen: number;\n /** Most recent time the concept appeared (Unix ms). */\n last_seen: number;\n}\n\nexport interface FindTunnelsOptions {\n /** Minimum distinct projects a concept must appear in. Default 2. */\n min_projects?: number;\n /** Minimum total chunk occurrences across all projects. Default 3. */\n min_occurrences?: number;\n /** Maximum number of tunnels to return. Default 20. */\n limit?: number;\n}\n\nexport interface FindTunnelsResult {\n tunnels: Tunnel[];\n projects_analyzed: number;\n total_concepts_evaluated: number;\n}\n\n// ---------------------------------------------------------------------------\n// Public entry point\n// ---------------------------------------------------------------------------\n\n/**\n * Find cross-project concept tunnels.\n *\n * Works with both SQLite and Postgres storage backends.\n * Requires the RegistryBackend for project slug resolution.\n *\n * @param backend Active PAI storage backend.\n * @param registry Registry backend for project slug resolution.\n * @param options Filter and limit options.\n */\nexport async function findTunnels(\n backend: StorageBackend,\n registry: RegistryBackend,\n options?: FindTunnelsOptions\n): Promise<FindTunnelsResult> {\n return backend.findTunnels(registry, options);\n}\n","/**\n * MCP tool handler: memory_tunnels\n *\n * Finds \"tunnels\" — concepts that appear across multiple projects —\n * surfacing cross-project serendipitous connections in the PAI memory graph.\n */\n\nimport type { StorageBackend } from \"../../storage/interface.js\";\nimport type { RegistryBackend } from \"../../storage/registry-interface.js\";\nimport type { ToolResult } from \"./types.js\";\nimport { findTunnels } from \"../../memory/tunnels.js\";\n\n// ---------------------------------------------------------------------------\n// Tool: memory_tunnels\n// ---------------------------------------------------------------------------\n\nexport interface MemoryTunnelsParams {\n /** Minimum distinct projects a concept must appear in. Default 2. */\n min_projects?: number;\n /** Minimum total chunk occurrences across all projects. Default 3. */\n min_occurrences?: number;\n /** Maximum number of tunnels to return. Default 20. */\n limit?: number;\n}\n\nexport async function toolMemoryTunnels(\n registry: RegistryBackend,\n backend: StorageBackend,\n params: MemoryTunnelsParams\n): Promise<ToolResult> {\n try {\n const result = await findTunnels(backend, registry, {\n min_projects: params.min_projects,\n min_occurrences: params.min_occurrences,\n limit: params.limit,\n });\n\n return {\n content: [{ type: \"text\", text: JSON.stringify(result, null, 2) }],\n };\n } catch (e) {\n return {\n content: [{ type: \"text\", text: `memory_tunnels error: ${String(e)}` }],\n isError: true,\n };\n }\n}\n","/**\n * MCP tool handler: memory_feedback\n *\n * Implements the MR2 feedback weight loop:\n * - Accepts chunk IDs and a rating (1-5)\n * - Applies EMA update to memory_chunks.relevance_score\n * - Finds entity names mentioned in those chunks and updates kg_entities.feedback_weight\n *\n * EMA formula: new = old + alpha * (normalized_rating - old)\n * where normalized_rating = (rating - 1) / 4 → maps [1,5] to [0,1]\n * and alpha = 0.1\n *\n * Integration into search scoring:\n * final_score *= (0.5 + relevance_score)\n * This multiplier ranges from 0.5 (relevance_score=0) to 1.5 (relevance_score=1),\n * giving a ±50% boost/penalty based on accumulated feedback.\n */\n\nimport type { ToolResult } from \"./types.js\";\nimport type { StorageBackend } from \"../../storage/interface.js\";\n\n// EMA learning rate\nconst FEEDBACK_ALPHA = 0.1;\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface MemoryFeedbackParams {\n /** Array of chunk IDs to apply feedback to */\n chunk_ids: string[];\n /** Rating from 1 (not relevant) to 5 (highly relevant) */\n rating: number;\n /** Optional tenant ID for entity feedback scoping. Default: \"default\" */\n tenant_id?: string;\n}\n\n// ---------------------------------------------------------------------------\n// Tool handler\n// ---------------------------------------------------------------------------\n\n/**\n * Apply relevance feedback to memory chunks and associated KG entities.\n * Both memory_chunks.relevance_score and kg_entities.feedback_weight go\n * through the passed StorageBackend, so this works on either backend.\n *\n * @param backend StorageBackend for both the chunk and kg_entities portions\n * @param params Feedback parameters\n */\nexport async function toolMemoryFeedback(\n backend: StorageBackend,\n params: MemoryFeedbackParams\n): Promise<ToolResult> {\n try {\n if (!Array.isArray(params.chunk_ids) || params.chunk_ids.length === 0) {\n return {\n content: [{ type: \"text\", text: \"memory_feedback error: chunk_ids must be a non-empty array\" }],\n isError: true,\n };\n }\n\n const rating = params.rating;\n if (typeof rating !== \"number\" || rating < 1 || rating > 5) {\n return {\n content: [{ type: \"text\", text: \"memory_feedback error: rating must be a number between 1 and 5\" }],\n isError: true,\n };\n }\n\n // Normalize rating [1,5] → [0,1]\n const normalizedRating = (rating - 1) / 4;\n\n // Fetch current relevance scores for the given chunk IDs\n const chunks = await backend.getChunksForFeedback(params.chunk_ids);\n\n if (chunks.length === 0) {\n return {\n content: [{ type: \"text\", text: \"memory_feedback: no matching chunks found\" }],\n isError: true,\n };\n }\n\n let updatedChunks = 0;\n const combinedText: string[] = [];\n\n // Apply EMA update to each chunk's relevance_score\n for (const chunk of chunks) {\n const oldScore = chunk.relevanceScore ?? 0.5;\n const newScore = oldScore + FEEDBACK_ALPHA * (normalizedRating - oldScore);\n await backend.updateChunkRelevanceScore(chunk.id, newScore);\n updatedChunks++;\n combinedText.push(chunk.text);\n }\n\n // Find entity mentions in updated chunks and apply EMA to kg_entities.feedback_weight\n const tenantId = params.tenant_id ?? \"default\";\n const entities = await backend.listKgEntities(tenantId, undefined, 500);\n const text = combinedText.join(\"\\n\").toLowerCase();\n\n let updatedEntities = 0;\n for (const entity of entities) {\n if (text.includes(entity.name.toLowerCase())) {\n await backend.updateEntityFeedbackWeight(entity.entity_id, normalizedRating, FEEDBACK_ALPHA);\n updatedEntities++;\n }\n }\n\n return {\n content: [\n {\n type: \"text\",\n text: JSON.stringify({\n updated_chunks: updatedChunks,\n updated_entities: updatedEntities,\n rating,\n normalized_rating: normalizedRating,\n }),\n },\n ],\n };\n } catch (e) {\n return {\n content: [{ type: \"text\", text: `memory_feedback error: ${String(e)}` }],\n isError: true,\n };\n }\n}\n","/**\n * kg-search.ts — Graph-completion retrieval (MR1).\n *\n * Implements graphCompletionSearch: a multi-phase retrieval strategy that\n * combines vector search with knowledge-graph neighborhood expansion.\n *\n * Algorithm:\n * Phase 1: Wide vector search (50 candidates) using queryVec\n * Phase 2: Extract entity mentions from retrieved chunks (substring match vs kg_entities)\n * Phase 3: BFS neighborhood expansion in kg_triples (1-2 hops from matched entities)\n * Phase 4: Re-rank all collected triples against query embedding using cosine similarity\n *\n * The result is a ranked list of KgTriple objects most relevant to the query,\n * augmented with graph-derived context that pure vector search would miss.\n */\n\nimport { cosineSimilarity, deserializeEmbedding } from \"./embeddings.js\";\nimport type { SearchResult, SearchOptions } from \"./search.js\";\nimport type { KgTriple } from \"./kg.js\";\nimport type { StorageBackend } from \"../storage/interface.js\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface GraphCompletionResult {\n /** Ranked KG triples relevant to the query */\n triples: Array<KgTriple & { relevanceScore: number }>;\n /** Chunks from the initial wide vector search (Phase 1) */\n seedChunks: SearchResult[];\n /** Entity names that were matched and used for BFS expansion */\n expandedEntities: string[];\n}\n\nexport interface GraphCompletionOptions {\n /** Number of seed chunks to fetch in Phase 1. Default: 50 */\n seedCount?: number;\n /** BFS hop depth for neighborhood expansion. Default: 1 */\n hops?: number;\n /** Maximum triples to return after re-ranking. Default: 20 */\n maxTriples?: number;\n /** Postgres project_id filter for triples. */\n projectId?: number;\n /** Tenant ID for entity lookup in the storage backend. Default: \"default\" */\n tenantId?: string;\n /** StorageBackend search options to pass to Phase 1 */\n searchOpts?: SearchOptions;\n}\n\n// ---------------------------------------------------------------------------\n// Phase 2: Entity mention extraction\n// ---------------------------------------------------------------------------\n\n/**\n * Find entity names (from the kg_entities table) that appear\n * as substrings in the given text chunks.\n *\n * Returns a deduplicated list of matching entity names.\n */\nasync function extractEntityMentions(\n federation: StorageBackend,\n chunks: SearchResult[],\n tenantId = \"default\"\n): Promise<string[]> {\n // Load all entity names for this tenant (sorted by length desc so longer\n // entity names are matched before substrings)\n const entities = await federation.listKgEntities(tenantId, undefined, 500);\n if (entities.length === 0) return [];\n\n const combinedText = chunks.map((c) => c.snippet).join(\"\\n\").toLowerCase();\n const matched = new Set<string>();\n\n for (const entity of entities) {\n if (combinedText.includes(entity.name.toLowerCase())) {\n matched.add(entity.name);\n }\n }\n\n return Array.from(matched);\n}\n\n// ---------------------------------------------------------------------------\n// Phase 3: BFS neighborhood expansion\n// ---------------------------------------------------------------------------\n\n/**\n * Perform BFS over kg_triples to find neighbors of the given entity names.\n *\n * For each entity name, fetches triples where the entity is the subject\n * or the object (bi-directional expansion). Supports 1 or 2 hop depths.\n *\n * Returns all unique triples found in the BFS expansion.\n */\nasync function bfsExpand(\n federation: StorageBackend,\n entityNames: string[],\n hops: number,\n projectId?: number\n): Promise<KgTriple[]> {\n const seen = new Set<number>(); // triple IDs already included\n const tripleMap = new Map<number, KgTriple>();\n const frontier = new Set(entityNames);\n\n for (let hop = 0; hop < hops; hop++) {\n const nextFrontier = new Set<string>();\n\n for (const name of frontier) {\n // Expand as subject\n const asSubject = await federation.queryKgTriples({\n subject: name,\n project_id: projectId,\n });\n\n // Expand as object\n const asObject = await federation.queryKgTriples({\n object: name,\n project_id: projectId,\n });\n\n for (const triple of [...asSubject, ...asObject]) {\n if (!seen.has(triple.id)) {\n seen.add(triple.id);\n tripleMap.set(triple.id, triple);\n // Add neighbors to next frontier for deeper hops\n nextFrontier.add(triple.subject);\n nextFrontier.add(triple.object);\n }\n }\n }\n\n // Next hop frontier: only new names not already processed\n frontier.clear();\n for (const name of nextFrontier) {\n if (!entityNames.includes(name)) {\n frontier.add(name);\n entityNames.push(name);\n }\n }\n }\n\n return Array.from(tripleMap.values());\n}\n\n// ---------------------------------------------------------------------------\n// Phase 4: Re-rank triples against query embedding\n// ---------------------------------------------------------------------------\n\n/**\n * Score each triple by cosine similarity to the query embedding.\n *\n * The triple text representation is: \"subject predicate object\"\n * We embed this on-the-fly using generateEmbedding(), which may use a cache.\n *\n * Returns triples sorted by relevance score descending.\n */\nasync function rerankTriples(\n triples: KgTriple[],\n queryVec: Float32Array,\n maxTriples: number\n): Promise<Array<KgTriple & { relevanceScore: number }>> {\n if (triples.length === 0) return [];\n\n const { generateEmbedding } = await import(\"./embeddings.js\");\n\n const scored = await Promise.all(\n triples.map(async (triple) => {\n const text = `${triple.subject} ${triple.predicate} ${triple.object}`;\n try {\n const vec = await generateEmbedding(text, false);\n const score = cosineSimilarity(queryVec, vec);\n return { ...triple, relevanceScore: score };\n } catch {\n return { ...triple, relevanceScore: 0 };\n }\n })\n );\n\n return scored\n .sort((a, b) => b.relevanceScore - a.relevanceScore)\n .slice(0, maxTriples);\n}\n\n// ---------------------------------------------------------------------------\n// Main: graphCompletionSearch\n// ---------------------------------------------------------------------------\n\n/**\n * Graph-completion retrieval combining vector search with KG neighborhood BFS.\n *\n * @param federation Active StorageBackend (for chunk/entity/kg_triples lookup)\n * @param queryVec Pre-computed embedding for the search query\n * @param opts Configuration options\n */\nexport async function graphCompletionSearch(\n federation: StorageBackend,\n queryVec: Float32Array,\n opts: GraphCompletionOptions = {}\n): Promise<GraphCompletionResult> {\n const {\n seedCount = 50,\n hops = 1,\n maxTriples = 20,\n projectId,\n tenantId = \"default\",\n searchOpts,\n } = opts;\n\n // Phase 1: Wide vector search for seed chunks\n const seedSearchOpts: SearchOptions = {\n ...searchOpts,\n maxResults: seedCount,\n projectIds: projectId != null ? [projectId] : searchOpts?.projectIds,\n };\n\n let seedChunks: SearchResult[] = [];\n try {\n seedChunks = await federation.searchSemantic(queryVec, seedSearchOpts);\n } catch (e) {\n process.stderr.write(`[kg-search] Phase 1 seed search error: ${e}\\n`);\n }\n\n if (seedChunks.length === 0) {\n return { triples: [], seedChunks: [], expandedEntities: [] };\n }\n\n // Phase 2: Extract entity mentions from seed chunks\n const expandedEntities = await extractEntityMentions(federation, seedChunks, tenantId);\n\n if (expandedEntities.length === 0) {\n // No entities matched — return empty graph results (seed chunks still useful)\n return { triples: [], seedChunks, expandedEntities: [] };\n }\n\n // Phase 3: BFS neighborhood expansion in kg_triples\n let expandedTriples: KgTriple[] = [];\n try {\n expandedTriples = await bfsExpand(federation, [...expandedEntities], hops, projectId);\n } catch (e) {\n process.stderr.write(`[kg-search] Phase 3 BFS expansion error: ${e}\\n`);\n }\n\n if (expandedTriples.length === 0) {\n return { triples: [], seedChunks, expandedEntities };\n }\n\n // Phase 4: Re-rank triples against query embedding\n let rankedTriples: Array<KgTriple & { relevanceScore: number }> = [];\n try {\n rankedTriples = await rerankTriples(expandedTriples, queryVec, maxTriples);\n } catch (e) {\n process.stderr.write(`[kg-search] Phase 4 rerank error: ${e}\\n`);\n // Fall back to unranked triples\n rankedTriples = expandedTriples.slice(0, maxTriples).map((t) => ({\n ...t,\n relevanceScore: 0,\n }));\n }\n\n return { triples: rankedTriples, seedChunks, expandedEntities };\n}\n","/**\n * MCP tool handler: memory_kg_search\n *\n * Exposes graphCompletionSearch as an MCP tool (MR1 — graph-completion retrieval).\n *\n * Algorithm: wide vector search → entity mention extraction → BFS neighborhood\n * expansion in kg_triples → re-rank triples by cosine similarity to query.\n *\n * Requires the storage backend for chunk/entity lookups and kg_triples BFS\n * expansion.\n *\n * Returns ranked KG triples with relevanceScore, plus seed chunk metadata.\n */\n\nimport type { ToolResult } from \"./types.js\";\nimport type { StorageBackend } from \"../../storage/interface.js\";\nimport { graphCompletionSearch } from \"../../memory/kg-search.js\";\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\nexport interface MemoryKgSearchParams {\n /** Free-text query — used to generate embedding for Phase 1 vector search. */\n query: string;\n /** Restrict seed vector search to a specific project ID. Optional. */\n project_id?: number;\n /** Number of seed chunks from Phase 1 vector search. Default: 50 */\n wide_k?: number;\n /** BFS hop depth for KG neighborhood expansion. Default: 1 */\n neighborhood_depth?: number;\n /** Maximum triples to return after Phase 4 re-ranking. Default: 20 */\n top_k?: number;\n /** Tenant ID for entity lookup in the storage backend. Default: \"default\" */\n tenant_id?: string;\n}\n\n// ---------------------------------------------------------------------------\n// Tool handler\n// ---------------------------------------------------------------------------\n\n/**\n * Graph-completion search: vector search + KG neighborhood BFS.\n *\n * @param federation Active StorageBackend for chunk/entity/kg_triples operations\n * @param params Tool parameters\n */\nexport async function toolMemoryKgSearch(\n federation: StorageBackend,\n params: MemoryKgSearchParams\n): Promise<ToolResult> {\n try {\n if (!params.query || typeof params.query !== \"string\" || params.query.trim() === \"\") {\n return {\n content: [{ type: \"text\", text: \"memory_kg_search error: query is required\" }],\n isError: true,\n };\n }\n\n const { generateEmbedding } = await import(\"../../memory/embeddings.js\");\n const queryVec = await generateEmbedding(params.query, true);\n\n const result = await graphCompletionSearch(federation, queryVec, {\n seedCount: params.wide_k ?? 50,\n hops: params.neighborhood_depth ?? 1,\n maxTriples: params.top_k ?? 20,\n projectId: params.project_id,\n tenantId: params.tenant_id ?? \"default\",\n });\n\n if (result.triples.length === 0) {\n return {\n content: [\n {\n type: \"text\",\n text: `No KG triples found for query: \"${params.query}\"\\n` +\n `Seed chunks: ${result.seedChunks.length}, ` +\n `Expanded entities: ${result.expandedEntities.length}`,\n },\n ],\n };\n }\n\n const tripleLines = result.triples\n .map((t, i) =>\n `[${i + 1}] (score=${t.relevanceScore.toFixed(4)}) ${t.subject} — ${t.predicate} — ${t.object}`\n )\n .join(\"\\n\");\n\n const entityList = result.expandedEntities.slice(0, 10).join(\", \");\n const summary = [\n `Found ${result.triples.length} KG triple(s) for \"${params.query}\"`,\n `Seed chunks: ${result.seedChunks.length}`,\n `Expanded entities (${result.expandedEntities.length}): ${entityList}`,\n \"\",\n tripleLines,\n ].join(\"\\n\");\n\n return {\n content: [{ type: \"text\", text: summary }],\n };\n } catch (e) {\n return {\n content: [{ type: \"text\", text: `memory_kg_search error: ${String(e)}` }],\n isError: true,\n };\n }\n}\n"],"mappings":"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"}
@@ -1,6 +1,6 @@
1
- import "./factory-y36qGegI.mjs";
2
- import "./wakeup-CZw88uXf.mjs";
3
- import "./embeddings-BbNVXa_0.mjs";
4
- import { A as detectProjectFromPath, C as toolProjectInfo, D as toolMemoryGet, E as combineHybridResults, M as lookupProjectId, O as toolMemoryOutline, S as toolProjectHealth, T as toolProjectTodo, _ as toolZettelThemes, a as toolMemoryWakeup, b as toolSessionRoute, c as toolKgInvalidate, d as toolZettelConverse, f as toolZettelExplore, g as toolZettelSurprise, h as toolZettelSuggest, i as toolMemoryTaxonomy, j as formatProject, k as toolMemorySearch, l as toolKgQuery, m as toolZettelHealth, n as toolMemoryFeedback, o as toolKgAdd, p as toolZettelGodNotes, r as toolMemoryTunnels, s as toolKgContradictions, t as toolMemoryKgSearch, u as toolZettelCommunities, v as toolRegistrySearch, w as toolProjectList, x as toolProjectDetect, y as toolSessionList } from "./tools-gMGDIpJ9.mjs";
1
+ import "./factory-CNpPQQ-5.mjs";
2
+ import "./wakeup-D8n33hYV.mjs";
3
+ import "./embeddings-Bx3q0QOY.mjs";
4
+ import { A as detectProjectFromPath, C as toolProjectInfo, D as toolMemoryGet, E as combineHybridResults, M as lookupProjectId, O as toolMemoryOutline, S as toolProjectHealth, T as toolProjectTodo, _ as toolZettelThemes, a as toolMemoryWakeup, b as toolSessionRoute, c as toolKgInvalidate, d as toolZettelConverse, f as toolZettelExplore, g as toolZettelSurprise, h as toolZettelSuggest, i as toolMemoryTaxonomy, j as formatProject, k as toolMemorySearch, l as toolKgQuery, m as toolZettelHealth, n as toolMemoryFeedback, o as toolKgAdd, p as toolZettelGodNotes, r as toolMemoryTunnels, s as toolKgContradictions, t as toolMemoryKgSearch, u as toolZettelCommunities, v as toolRegistrySearch, w as toolProjectList, x as toolProjectDetect, y as toolSessionList } from "./tools-BcOKPBtg.mjs";
5
5
 
6
6
  export { toolKgAdd, toolKgContradictions, toolKgInvalidate, toolKgQuery, toolMemoryTunnels, toolZettelCommunities, toolZettelConverse, toolZettelExplore, toolZettelGodNotes, toolZettelHealth, toolZettelSuggest, toolZettelSurprise, toolZettelThemes };
@@ -134,4 +134,4 @@ async function handleGraphTrace(backend, params) {
134
134
 
135
135
  //#endregion
136
136
  export { handleGraphTrace };
137
- //# sourceMappingURL=trace-CmAB7iJZ.mjs.map
137
+ //# sourceMappingURL=trace-CtV0RO6n.mjs.map