@tekmidian/pai 0.63.0 → 0.65.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 (206) hide show
  1. package/ARCHITECTURE.md +3 -0
  2. package/README.md +38 -5
  3. package/dist/{aibroker-client-BYG-GtAg.mjs → aibroker-client-B8c42Lh8.mjs} +1 -1
  4. package/dist/{aibroker-client-BK1h--W_.mjs → aibroker-client-C5Fw7DNz.mjs} +10 -4
  5. package/dist/{aibroker-client-BK1h--W_.mjs.map → aibroker-client-C5Fw7DNz.mjs.map} +1 -1
  6. package/dist/{async-DNVWtQl2.mjs → async-CNn36gm4.mjs} +5 -4
  7. package/dist/async-CNn36gm4.mjs.map +1 -0
  8. package/dist/{auto-route-CF8WNaoy.mjs → auto-route-sLMU-NnM.mjs} +2 -2
  9. package/dist/{auto-route-CF8WNaoy.mjs.map → auto-route-sLMU-NnM.mjs.map} +1 -1
  10. package/dist/{chain-CUfE6WYK.mjs → chain-C8QO8gwj.mjs} +4 -4
  11. package/dist/{chain-CUfE6WYK.mjs.map → chain-C8QO8gwj.mjs.map} +1 -1
  12. package/dist/chunker-BH4i-F2b.mjs +300 -0
  13. package/dist/chunker-BH4i-F2b.mjs.map +1 -0
  14. package/dist/cli/index.mjs +20 -20
  15. package/dist/cli/program.d.mts.map +1 -1
  16. package/dist/cli/program.mjs +20 -20
  17. package/dist/{clusters-Ci1mseh2.mjs → clusters-PgIUYT_v.mjs} +2 -2
  18. package/dist/{clusters-Ci1mseh2.mjs.map → clusters-PgIUYT_v.mjs.map} +1 -1
  19. package/dist/{config-CQCmH_gV.mjs → config-BbLFD7Uf.mjs} +3 -3
  20. package/dist/{config-CQCmH_gV.mjs.map → config-BbLFD7Uf.mjs.map} +1 -1
  21. package/dist/{config-DjXq_W42.mjs → config-YinjgXEJ.mjs} +1 -1
  22. package/dist/{context-handover-cache-DFNnlWv2.mjs → context-handover-cache-mGxq2-8f.mjs} +11 -29
  23. package/dist/context-handover-cache-mGxq2-8f.mjs.map +1 -0
  24. package/dist/daemon/index.mjs +16 -16
  25. package/dist/daemon-CX9JomIJ.mjs +19 -0
  26. package/dist/{daemon-Cex3dT9f.mjs → daemon-D2r1AQqE.mjs} +31 -30
  27. package/dist/{daemon-Cex3dT9f.mjs.map → daemon-D2r1AQqE.mjs.map} +1 -1
  28. package/dist/daemon-mcp/index.mjs +29 -20
  29. package/dist/daemon-mcp/index.mjs.map +1 -1
  30. package/dist/detector-C3Q7mxQU.mjs +3 -0
  31. package/dist/{detector-3g7XuU1r.mjs → detector-CIHEXKsV.mjs} +2 -2
  32. package/dist/{detector-3g7XuU1r.mjs.map → detector-CIHEXKsV.mjs.map} +1 -1
  33. package/dist/{embeddings-DfrYNIng.mjs → embeddings-BbNVXa_0.mjs} +1 -1
  34. package/dist/{embeddings-DfrYNIng.mjs.map → embeddings-BbNVXa_0.mjs.map} +1 -1
  35. package/dist/{embeddings-BNv3TQC5.mjs → embeddings-CDfM63uC.mjs} +1 -1
  36. package/dist/{factory-UO9_DFMN.mjs → factory-CaswxuJ0.mjs} +1 -1
  37. package/dist/{factory-BozZa81q.mjs → factory-y36qGegI.mjs} +13 -13
  38. package/dist/{factory-BozZa81q.mjs.map → factory-y36qGegI.mjs.map} +1 -1
  39. package/dist/{fallback-ktbR0aR7.mjs → fallback-CWDQYmJi.mjs} +1205 -1204
  40. package/dist/fallback-CWDQYmJi.mjs.map +1 -0
  41. package/dist/{federation-db-w8e-sKjp.mjs → federation-db-BTyoufBh.mjs} +2 -2
  42. package/dist/{federation-db-w8e-sKjp.mjs.map → federation-db-BTyoufBh.mjs.map} +1 -1
  43. package/dist/federation-db-HfIFc7FG.mjs +3 -0
  44. package/dist/{helpers-Dj-B314s.mjs → helpers-BRCJg0G3.mjs} +2 -208
  45. package/dist/helpers-BRCJg0G3.mjs.map +1 -0
  46. package/dist/hooks/block-sleep-poll.mjs +36 -11
  47. package/dist/hooks/block-sleep-poll.mjs.map +2 -2
  48. package/dist/hooks/capture-all-events.mjs +3 -17
  49. package/dist/hooks/capture-all-events.mjs.map +3 -3
  50. package/dist/hooks/capture-session-summary.mjs +3 -17
  51. package/dist/hooks/capture-session-summary.mjs.map +3 -3
  52. package/dist/hooks/capture-tool-output.mjs +3 -17
  53. package/dist/hooks/capture-tool-output.mjs.map +3 -3
  54. package/dist/hooks/cleanup-session-files.mjs +3 -17
  55. package/dist/hooks/cleanup-session-files.mjs.map +2 -2
  56. package/dist/hooks/context-compression-hook.mjs +3 -17
  57. package/dist/hooks/context-compression-hook.mjs.map +3 -3
  58. package/dist/hooks/initialize-session.mjs +3 -17
  59. package/dist/hooks/initialize-session.mjs.map +2 -2
  60. package/dist/hooks/inject-observations.mjs +3 -17
  61. package/dist/hooks/inject-observations.mjs.map +2 -2
  62. package/dist/hooks/load-core-context.mjs +4 -22
  63. package/dist/hooks/load-core-context.mjs.map +2 -2
  64. package/dist/hooks/load-project-context.mjs +3 -17
  65. package/dist/hooks/load-project-context.mjs.map +3 -3
  66. package/dist/hooks/mcp-deferred-gate.mjs +3 -17
  67. package/dist/hooks/mcp-deferred-gate.mjs.map +2 -2
  68. package/dist/hooks/observe.mjs +3 -17
  69. package/dist/hooks/observe.mjs.map +2 -2
  70. package/dist/hooks/security-validator.mjs +3 -17
  71. package/dist/hooks/security-validator.mjs.map +3 -3
  72. package/dist/hooks/stop-hook.mjs +3 -17
  73. package/dist/hooks/stop-hook.mjs.map +3 -3
  74. package/dist/hooks/sync-todo-to-md.mjs +3 -17
  75. package/dist/hooks/sync-todo-to-md.mjs.map +2 -2
  76. package/dist/hooks/worker-guard.mjs.map +2 -2
  77. package/dist/hooks/worker-status-line.mjs +4 -4
  78. package/dist/hooks/worker-status-line.mjs.map +2 -2
  79. package/dist/index.d.mts +26 -8
  80. package/dist/index.d.mts.map +1 -1
  81. package/dist/index.mjs +5 -4
  82. package/dist/indexer-backend-Cnc7Tf5C.mjs +5 -0
  83. package/dist/{ipc-client-Dasm515c.mjs → ipc-client-D16Xw6Uo.mjs} +2 -2
  84. package/dist/{ipc-client-Dasm515c.mjs.map → ipc-client-D16Xw6Uo.mjs.map} +1 -1
  85. package/dist/{kg-entity-Cj8Q6YmU.mjs → kg-entity-COTUj1ZC.mjs} +1 -1
  86. package/dist/{kg-entity-Cj8Q6YmU.mjs.map → kg-entity-COTUj1ZC.mjs.map} +1 -1
  87. package/dist/{latent-ideas-BRy6HNwg.mjs → latent-ideas-ByvMjVcb.mjs} +4 -4
  88. package/dist/{latent-ideas-BRy6HNwg.mjs.map → latent-ideas-ByvMjVcb.mjs.map} +1 -1
  89. package/dist/{link-boost-BptbAPuv.mjs → link-boost-UiE-uooT.mjs} +1 -1
  90. package/dist/{link-boost-BptbAPuv.mjs.map → link-boost-UiE-uooT.mjs.map} +1 -1
  91. package/dist/{main-resolver-BIft_FVF.mjs → main-resolver-CDe7DCso.mjs} +6 -6
  92. package/dist/{main-resolver-BIft_FVF.mjs.map → main-resolver-CDe7DCso.mjs.map} +1 -1
  93. package/dist/main-resolver-DI-A7lSO.mjs +7 -0
  94. package/dist/merge-2gqRPFu2.mjs +3 -0
  95. package/dist/{merge-xoyfZQ4W.mjs → merge-DgU9OgZy.mjs} +1 -1
  96. package/dist/{merge-xoyfZQ4W.mjs.map → merge-DgU9OgZy.mjs.map} +1 -1
  97. package/dist/module-paths-DdRzbkUI.mjs +44 -0
  98. package/dist/module-paths-DdRzbkUI.mjs.map +1 -0
  99. package/dist/{neighborhood-D8uscnGy.mjs → neighborhood-CvRHlqdR.mjs} +2 -2
  100. package/dist/{neighborhood-D8uscnGy.mjs.map → neighborhood-CvRHlqdR.mjs.map} +1 -1
  101. package/dist/{note-context-CgffaUMh.mjs → note-context-BrbfUIoP.mjs} +1 -1
  102. package/dist/{note-context-CgffaUMh.mjs.map → note-context-BrbfUIoP.mjs.map} +1 -1
  103. package/dist/{pai-home-CeOIYltV.mjs → pai-home-Cm9rcJgX.mjs} +1 -1
  104. package/dist/{pai-home-CeOIYltV.mjs.map → pai-home-Cm9rcJgX.mjs.map} +1 -1
  105. package/dist/{planner-DAQMTMjP.mjs → planner-8-shIa8t.mjs} +7 -7
  106. package/dist/{planner-DAQMTMjP.mjs.map → planner-8-shIa8t.mjs.map} +1 -1
  107. package/dist/{postgres-CxD6_Dca.mjs → postgres-DYtZg7J7.mjs} +8 -15
  108. package/dist/{postgres-CxD6_Dca.mjs.map → postgres-DYtZg7J7.mjs.map} +1 -1
  109. package/dist/{program-De1GVVM4.mjs → program-DiktbiWa.mjs} +229 -249
  110. package/dist/program-DiktbiWa.mjs.map +1 -0
  111. package/dist/{query-feedback-xz5axTj3.mjs → query-feedback-BIaZTTFO.mjs} +2 -2
  112. package/dist/{query-feedback-xz5axTj3.mjs.map → query-feedback-BIaZTTFO.mjs.map} +1 -1
  113. package/dist/query-feedback-B_iigYj-.mjs +3 -0
  114. package/dist/{registry-db-Bkpbp7Gf.mjs → registry-db-C7voqML9.mjs} +2 -2
  115. package/dist/{registry-db-Bkpbp7Gf.mjs.map → registry-db-C7voqML9.mjs.map} +1 -1
  116. package/dist/registry-db-JHPhA8vF.mjs +3 -0
  117. package/dist/{registry-postgres-B07-hmn8.mjs → registry-postgres-DSrkxqfF.mjs} +2 -2
  118. package/dist/{registry-postgres-B07-hmn8.mjs.map → registry-postgres-DSrkxqfF.mjs.map} +1 -1
  119. package/dist/{registry-sqlite-CkGND_tm.mjs → registry-sqlite-B2436JgX.mjs} +2 -2
  120. package/dist/{registry-sqlite-CkGND_tm.mjs.map → registry-sqlite-B2436JgX.mjs.map} +1 -1
  121. package/dist/{reranker-BS5gqGeF.mjs → reranker-CFiEzHQu.mjs} +1 -1
  122. package/dist/{reranker-CRVawoEh.mjs → reranker-DKv80KO5.mjs} +1 -1
  123. package/dist/{reranker-CRVawoEh.mjs.map → reranker-DKv80KO5.mjs.map} +1 -1
  124. package/dist/router-S6C5BxzZ.mjs +3 -0
  125. package/dist/{router-DjFZIoDl.mjs → router-aqLMvNMg.mjs} +1 -1
  126. package/dist/{router-DjFZIoDl.mjs.map → router-aqLMvNMg.mjs.map} +1 -1
  127. package/dist/{run-CZVCCvXE.mjs → run-DUZRSnIT.mjs} +120 -42
  128. package/dist/run-DUZRSnIT.mjs.map +1 -0
  129. package/dist/{run-env-BE5xyCwy.mjs → run-env-BHWnXqle.mjs} +3 -3
  130. package/dist/{run-env-BE5xyCwy.mjs.map → run-env-BHWnXqle.mjs.map} +1 -1
  131. package/dist/{runtime-paths-BRwDeBEg.mjs → runtime-paths-CHTg3ywb.mjs} +1 -1
  132. package/dist/{runtime-paths-BRwDeBEg.mjs.map → runtime-paths-CHTg3ywb.mjs.map} +1 -1
  133. package/dist/{search-BaTbyWjK.mjs → search-BOSphCJ1.mjs} +2 -2
  134. package/dist/{search-BaTbyWjK.mjs.map → search-BOSphCJ1.mjs.map} +1 -1
  135. package/dist/search-Bf3Kub3F.mjs +3 -0
  136. package/dist/{server-BY6AedmC.mjs → server-DlL1QI2k.mjs} +4 -5
  137. package/dist/server-DlL1QI2k.mjs.map +1 -0
  138. package/dist/{session-keepalive-CWGzLBVU.mjs → session-keepalive-CZUwp5IQ.mjs} +7 -7
  139. package/dist/{session-keepalive-CWGzLBVU.mjs.map → session-keepalive-CZUwp5IQ.mjs.map} +1 -1
  140. package/dist/skills/Art/SKILL.md +1 -1
  141. package/dist/skills/Observability/SKILL.md +4 -4
  142. package/dist/skills/Research/SKILL.md +1 -1
  143. package/dist/{sources-BBe3AZ-W.mjs → sources-DX4ElmrE.mjs} +1 -1
  144. package/dist/{sources-BBe3AZ-W.mjs.map → sources-DX4ElmrE.mjs.map} +1 -1
  145. package/dist/{sqlite-CfKfuggn.mjs → sqlite-BrEu3avy.mjs} +8 -7
  146. package/dist/sqlite-BrEu3avy.mjs.map +1 -0
  147. package/dist/{state-CwlGAKgF.mjs → state-DW8zdweW.mjs} +1 -1
  148. package/dist/{state-n13uPK5B.mjs → state-HyjqTihC.mjs} +1 -1
  149. package/dist/{state-n13uPK5B.mjs.map → state-HyjqTihC.mjs.map} +1 -1
  150. package/dist/{stop-words-ClCwO8mZ.mjs → stop-words-BdQuaE9K.mjs} +1 -1
  151. package/dist/{stop-words-ClCwO8mZ.mjs.map → stop-words-BdQuaE9K.mjs.map} +1 -1
  152. package/dist/{themes-B2v7RNnA.mjs → themes-BgqahYVM.mjs} +3 -3
  153. package/dist/{themes-B2v7RNnA.mjs.map → themes-BgqahYVM.mjs.map} +1 -1
  154. package/dist/tools-BbqQIHPe.mjs +6 -0
  155. package/dist/{tools-DFdPbX-X.mjs → tools-gMGDIpJ9.mjs} +84 -68
  156. package/dist/tools-gMGDIpJ9.mjs.map +1 -0
  157. package/dist/{trace-BRflVyBi.mjs → trace-CmAB7iJZ.mjs} +1 -1
  158. package/dist/{trace-BRflVyBi.mjs.map → trace-CmAB7iJZ.mjs.map} +1 -1
  159. package/dist/{vault-indexer-mCFus_AM.mjs → vault-indexer-Ddq51X20.mjs} +4 -3
  160. package/dist/vault-indexer-Ddq51X20.mjs.map +1 -0
  161. package/dist/{wakeup-DO1O4udw.mjs → wakeup-CZw88uXf.mjs} +3 -3
  162. package/dist/{wakeup-DO1O4udw.mjs.map → wakeup-CZw88uXf.mjs.map} +1 -1
  163. package/dist/{work-queue-worker-BbrC3I_R.mjs → work-queue-worker-DCzfH0d1.mjs} +4 -4
  164. package/dist/{work-queue-worker-BbrC3I_R.mjs.map → work-queue-worker-DCzfH0d1.mjs.map} +1 -1
  165. package/dist/work-queue-worker-Taba_vcA.mjs +11 -0
  166. package/dist/{zettelkasten-BPxSOalU.mjs → zettelkasten-Dwj67e4p.mjs} +4 -4
  167. package/dist/{zettelkasten-BPxSOalU.mjs.map → zettelkasten-Dwj67e4p.mjs.map} +1 -1
  168. package/docker/docker-compose.yml +39 -0
  169. package/docs/commands/README.md +3 -3
  170. package/docs/commands/config.md +3 -3
  171. package/docs/commands/setup.md +9 -2
  172. package/docs/commands/worker.md +2 -2
  173. package/gemini-extension.json +1 -1
  174. package/package.json +3 -2
  175. package/pai-plugin.json +1 -0
  176. package/scripts/build-hooks.mjs +6 -6
  177. package/src/hooks/ts/lib/pai-paths-case.test.ts +12 -0
  178. package/src/hooks/ts/lib/pai-paths-import.test.ts +25 -0
  179. package/src/hooks/ts/lib/pai-paths.ts +5 -28
  180. package/src/hooks/ts/lib/sleep-poll-gate.test.ts +17 -1
  181. package/src/hooks/ts/lib/sleep-poll-gate.ts +21 -9
  182. package/src/hooks/ts/session-start/load-core-context.ts +2 -6
  183. package/src/hooks/ts/session-start/session-start-worker-guard.test.ts +24 -1
  184. package/dist/async-DNVWtQl2.mjs.map +0 -1
  185. package/dist/context-handover-cache-DFNnlWv2.mjs.map +0 -1
  186. package/dist/daemon-C8-uzOuV.mjs +0 -19
  187. package/dist/detector-DgDn56j-.mjs +0 -3
  188. package/dist/fallback-ktbR0aR7.mjs.map +0 -1
  189. package/dist/federation-db-CJDw_Pwq.mjs +0 -3
  190. package/dist/helpers-Dj-B314s.mjs.map +0 -1
  191. package/dist/indexer-backend-BN9rpp9O.mjs +0 -5
  192. package/dist/main-resolver-C9-poMdi.mjs +0 -7
  193. package/dist/merge-BncjJTfb.mjs +0 -3
  194. package/dist/program-De1GVVM4.mjs.map +0 -1
  195. package/dist/query-feedback-COYCnvHQ.mjs +0 -3
  196. package/dist/registry-db-DwXpf7yF.mjs +0 -3
  197. package/dist/router-C5hPk9YP.mjs +0 -3
  198. package/dist/run-CZVCCvXE.mjs.map +0 -1
  199. package/dist/search-BQHL5wDr.mjs +0 -3
  200. package/dist/server-BY6AedmC.mjs.map +0 -1
  201. package/dist/sqlite-CfKfuggn.mjs.map +0 -1
  202. package/dist/tools-B52IvaU6.mjs +0 -6
  203. package/dist/tools-DFdPbX-X.mjs.map +0 -1
  204. package/dist/vault-indexer-mCFus_AM.mjs.map +0 -1
  205. package/dist/work-queue-worker-z-AATZ2t.mjs +0 -11
  206. /package/dist/{indexer-backend-Cc-_NK3q.mjs → indexer-backend-isSLg6yE.mjs} +0 -0
@@ -1,6 +1,7 @@
1
- import { i as populateSlugs } from "./search-BaTbyWjK.mjs";
2
- import { a as formatDetectionJson, r as detectProject, t as buildWakeupContext } from "./wakeup-DO1O4udw.mjs";
3
- import { n as cosineSimilarity } from "./embeddings-DfrYNIng.mjs";
1
+ import { t as buildOutline } from "./chunker-BH4i-F2b.mjs";
2
+ import { i as populateSlugs } from "./search-BOSphCJ1.mjs";
3
+ import { a as formatDetectionJson, r as detectProject, t as buildWakeupContext } from "./wakeup-CZw88uXf.mjs";
4
+ import { n as cosineSimilarity } from "./embeddings-BbNVXa_0.mjs";
4
5
  import { existsSync, readFileSync, statSync } from "node:fs";
5
6
  import { isAbsolute, join, resolve } from "node:path";
6
7
 
@@ -45,7 +46,7 @@ async function formatProject(registry, project) {
45
46
  //#endregion
46
47
  //#region src/mcp/tools/memory.ts
47
48
  /**
48
- * MCP tool handlers: memory_search, memory_get
49
+ * MCP tool handlers: memory_search, memory_get, memory_outline
49
50
  */
50
51
  async function toolMemorySearch(registry, federation, params, searchDefaults) {
51
52
  try {
@@ -70,7 +71,7 @@ async function toolMemorySearch(registry, federation, params, searchDefaults) {
70
71
  let results;
71
72
  if (mode === "keyword") results = await federation.searchKeyword(params.query, searchOpts);
72
73
  else if (mode === "semantic" || mode === "hybrid") {
73
- const { generateEmbedding } = await import("./embeddings-BNv3TQC5.mjs");
74
+ const { generateEmbedding } = await import("./embeddings-CDfM63uC.mjs");
74
75
  const queryEmbedding = await generateEmbedding(params.query, true);
75
76
  if (mode === "semantic") results = await federation.searchSemantic(queryEmbedding, searchOpts);
76
77
  else {
@@ -85,7 +86,7 @@ async function toolMemorySearch(registry, federation, params, searchDefaults) {
85
86
  }
86
87
  } else results = await federation.searchKeyword(params.query, searchOpts);
87
88
  try {
88
- const { applyLinkBoost } = await import("./link-boost-BptbAPuv.mjs");
89
+ const { applyLinkBoost } = await import("./link-boost-UiE-uooT.mjs");
89
90
  const paths = [...new Set(results.map((r) => r.path))];
90
91
  if (paths.length > 1) {
91
92
  const edges = [];
@@ -105,12 +106,12 @@ async function toolMemorySearch(registry, federation, params, searchDefaults) {
105
106
  } catch {}
106
107
  const shouldRerank = params.rerank ?? searchDefaults?.rerank ?? true;
107
108
  if (shouldRerank && results.length > 0) {
108
- const { rerankResults } = await import("./reranker-BS5gqGeF.mjs");
109
+ const { rerankResults } = await import("./reranker-CFiEzHQu.mjs");
109
110
  results = await rerankResults(params.query, results, { topK: searchOpts.maxResults ?? 5 });
110
111
  }
111
112
  const recencyDays = params.recencyBoost ?? searchDefaults?.recencyBoostDays ?? 0;
112
113
  if (recencyDays > 0 && results.length > 0) {
113
- const { applyRecencyBoost } = await import("./search-BQHL5wDr.mjs");
114
+ const { applyRecencyBoost } = await import("./search-Bf3Kub3F.mjs");
114
115
  results = applyRecencyBoost(results, recencyDays);
115
116
  }
116
117
  const withSlugs = await populateSlugs(results, registry);
@@ -129,7 +130,7 @@ async function toolMemorySearch(registry, federation, params, searchDefaults) {
129
130
  return `${header}\n${raw.length > snippetLength ? raw.slice(0, snippetLength) + "..." : raw}`;
130
131
  }).join(useCompact ? "\n" : "\n\n---\n\n");
131
132
  try {
132
- const { saveQueryResult } = await import("./query-feedback-COYCnvHQ.mjs");
133
+ const { saveQueryResult } = await import("./query-feedback-B_iigYj-.mjs");
133
134
  saveQueryResult({
134
135
  query: params.query,
135
136
  timestamp: Date.now(),
@@ -153,54 +154,42 @@ async function toolMemorySearch(registry, federation, params, searchDefaults) {
153
154
  };
154
155
  }
155
156
  }
157
+ /**
158
+ * Resolve `project` + `path` to a readable file inside the project root.
159
+ * Shared by memory_get and memory_outline so both apply the same path and
160
+ * access checks. Returns a ToolResult on any failure.
161
+ */
162
+ async function resolveProjectFile(registry, projectSlug, requestedPath) {
163
+ const fail = (text) => ({
164
+ content: [{
165
+ type: "text",
166
+ text
167
+ }],
168
+ isError: true
169
+ });
170
+ const projectId = await lookupProjectId(registry, projectSlug);
171
+ if (projectId == null) return fail(`Project not found: ${projectSlug}`);
172
+ const project = await registry.getProjectById(projectId);
173
+ if (!project) return fail(`Project not found: ${projectSlug}`);
174
+ if (requestedPath.includes("..") || isAbsolute(requestedPath)) return fail(`Invalid path: ${requestedPath} (must be a relative path within the project root, no ../ allowed)`);
175
+ const fullPath = join(project.root_path, requestedPath);
176
+ const resolvedFull = resolve(fullPath);
177
+ const resolvedRoot = resolve(project.root_path);
178
+ if (!resolvedFull.startsWith(resolvedRoot + "/") && resolvedFull !== resolvedRoot) return fail(`Path traversal blocked: ${requestedPath}`);
179
+ if (!existsSync(fullPath)) return fail(`File not found: ${requestedPath} (project: ${projectSlug})`);
180
+ const stat = statSync(fullPath);
181
+ if (stat.size > 5 * 1024 * 1024) return { content: [{
182
+ type: "text",
183
+ text: `Error: file too large (${(stat.size / 1024 / 1024).toFixed(1)} MB). Maximum 5 MB.`
184
+ }] };
185
+ return { fullPath };
186
+ }
156
187
  async function toolMemoryGet(registry, params) {
157
188
  try {
158
- const projectId = await lookupProjectId(registry, params.project);
159
- if (projectId == null) return {
160
- content: [{
161
- type: "text",
162
- text: `Project not found: ${params.project}`
163
- }],
164
- isError: true
165
- };
166
- const project = await registry.getProjectById(projectId);
167
- if (!project) return {
168
- content: [{
169
- type: "text",
170
- text: `Project not found: ${params.project}`
171
- }],
172
- isError: true
173
- };
189
+ const resolved = await resolveProjectFile(registry, params.project, params.path);
190
+ if ("content" in resolved) return resolved;
191
+ const { fullPath } = resolved;
174
192
  const requestedPath = params.path;
175
- if (requestedPath.includes("..") || isAbsolute(requestedPath)) return {
176
- content: [{
177
- type: "text",
178
- text: `Invalid path: ${params.path} (must be a relative path within the project root, no ../ allowed)`
179
- }],
180
- isError: true
181
- };
182
- const fullPath = join(project.root_path, requestedPath);
183
- const resolvedFull = resolve(fullPath);
184
- const resolvedRoot = resolve(project.root_path);
185
- if (!resolvedFull.startsWith(resolvedRoot + "/") && resolvedFull !== resolvedRoot) return {
186
- content: [{
187
- type: "text",
188
- text: `Path traversal blocked: ${params.path}`
189
- }],
190
- isError: true
191
- };
192
- if (!existsSync(fullPath)) return {
193
- content: [{
194
- type: "text",
195
- text: `File not found: ${requestedPath} (project: ${params.project})`
196
- }],
197
- isError: true
198
- };
199
- const stat = statSync(fullPath);
200
- if (stat.size > 5 * 1024 * 1024) return { content: [{
201
- type: "text",
202
- text: `Error: file too large (${(stat.size / 1024 / 1024).toFixed(1)} MB). Maximum 5 MB.`
203
- }] };
204
193
  const allLines = readFileSync(fullPath, "utf8").split("\n");
205
194
  const fromLine = (params.from ?? 1) - 1;
206
195
  const toLine = params.lines != null ? Math.min(fromLine + params.lines, allLines.length) : allLines.length;
@@ -219,6 +208,33 @@ async function toolMemoryGet(registry, params) {
219
208
  };
220
209
  }
221
210
  }
211
+ function renderOutline(nodes, maxDepth, depth = 0, out = []) {
212
+ for (const n of nodes) {
213
+ out.push(`${" ".repeat(depth)}${"#".repeat(n.level)} ${n.title} L${n.startLine}-${n.endLine} ~${n.tokens}t`);
214
+ if (depth + 1 < maxDepth) renderOutline(n.children, maxDepth, depth + 1, out);
215
+ }
216
+ return out;
217
+ }
218
+ async function toolMemoryOutline(registry, params) {
219
+ try {
220
+ const resolved = await resolveProjectFile(registry, params.project, params.path);
221
+ if ("content" in resolved) return resolved;
222
+ const outline = buildOutline(readFileSync(resolved.fullPath, "utf8"));
223
+ const body = outline.length ? renderOutline(outline, params.max_depth ?? Infinity).join("\n") : "(no headings)";
224
+ return { content: [{
225
+ type: "text",
226
+ text: `${params.project}/${params.path} outline:\n\n${body}`
227
+ }] };
228
+ } catch (e) {
229
+ return {
230
+ content: [{
231
+ type: "text",
232
+ text: `Read error: ${String(e)}`
233
+ }],
234
+ isError: true
235
+ };
236
+ }
237
+ }
222
238
  /**
223
239
  * Combine keyword + semantic results using min-max normalized scoring.
224
240
  * Mirrors the logic in searchMemoryHybrid() from memory/search.ts,
@@ -633,7 +649,7 @@ async function toolSessionList(registry, params) {
633
649
  */
634
650
  async function toolSessionRoute(registry, federation, params) {
635
651
  try {
636
- const { autoRoute, formatAutoRouteJson } = await import("./auto-route-CF8WNaoy.mjs");
652
+ const { autoRoute, formatAutoRouteJson } = await import("./auto-route-sLMU-NnM.mjs");
637
653
  const result = await autoRoute(registry, federation, params.cwd, params.context);
638
654
  if (!result) return { content: [{
639
655
  type: "text",
@@ -690,7 +706,7 @@ async function toolRegistrySearch(registry, params) {
690
706
  //#region src/mcp/tools/zettel.ts
691
707
  async function toolZettelExplore(backend, params) {
692
708
  try {
693
- const { zettelExplore } = await import("./zettelkasten-BPxSOalU.mjs");
709
+ const { zettelExplore } = await import("./zettelkasten-Dwj67e4p.mjs");
694
710
  const result = await zettelExplore(backend, {
695
711
  startNote: params.start_note,
696
712
  depth: params.depth,
@@ -713,7 +729,7 @@ async function toolZettelExplore(backend, params) {
713
729
  }
714
730
  async function toolZettelHealth(backend, params) {
715
731
  try {
716
- const { zettelHealth } = await import("./zettelkasten-BPxSOalU.mjs");
732
+ const { zettelHealth } = await import("./zettelkasten-Dwj67e4p.mjs");
717
733
  const result = await zettelHealth(backend, {
718
734
  scope: params.scope,
719
735
  projectPath: params.project_path,
@@ -736,7 +752,7 @@ async function toolZettelHealth(backend, params) {
736
752
  }
737
753
  async function toolZettelSurprise(backend, params) {
738
754
  try {
739
- const { zettelSurprise } = await import("./zettelkasten-BPxSOalU.mjs");
755
+ const { zettelSurprise } = await import("./zettelkasten-Dwj67e4p.mjs");
740
756
  const results = await zettelSurprise(backend, {
741
757
  referencePath: params.reference_path,
742
758
  vaultProjectId: params.vault_project_id,
@@ -760,7 +776,7 @@ async function toolZettelSurprise(backend, params) {
760
776
  }
761
777
  async function toolZettelSuggest(backend, params) {
762
778
  try {
763
- const { zettelSuggest } = await import("./zettelkasten-BPxSOalU.mjs");
779
+ const { zettelSuggest } = await import("./zettelkasten-Dwj67e4p.mjs");
764
780
  const results = await zettelSuggest(backend, {
765
781
  notePath: params.note_path,
766
782
  vaultProjectId: params.vault_project_id,
@@ -783,7 +799,7 @@ async function toolZettelSuggest(backend, params) {
783
799
  }
784
800
  async function toolZettelConverse(backend, params) {
785
801
  try {
786
- const { zettelConverse } = await import("./zettelkasten-BPxSOalU.mjs");
802
+ const { zettelConverse } = await import("./zettelkasten-Dwj67e4p.mjs");
787
803
  const result = await zettelConverse(backend, {
788
804
  question: params.question,
789
805
  vaultProjectId: params.vault_project_id,
@@ -791,7 +807,7 @@ async function toolZettelConverse(backend, params) {
791
807
  limit: params.limit
792
808
  });
793
809
  try {
794
- const { saveQueryResult } = await import("./query-feedback-COYCnvHQ.mjs");
810
+ const { saveQueryResult } = await import("./query-feedback-B_iigYj-.mjs");
795
811
  saveQueryResult({
796
812
  query: params.question,
797
813
  timestamp: Date.now(),
@@ -819,7 +835,7 @@ async function toolZettelConverse(backend, params) {
819
835
  }
820
836
  async function toolZettelThemes(backend, params) {
821
837
  try {
822
- const { zettelThemes } = await import("./zettelkasten-BPxSOalU.mjs");
838
+ const { zettelThemes } = await import("./zettelkasten-Dwj67e4p.mjs");
823
839
  const result = await zettelThemes(backend, {
824
840
  vaultProjectId: params.vault_project_id,
825
841
  lookbackDays: params.lookback_days,
@@ -843,7 +859,7 @@ async function toolZettelThemes(backend, params) {
843
859
  }
844
860
  async function toolZettelGodNotes(backend, params) {
845
861
  try {
846
- const { zettelGodNotes } = await import("./zettelkasten-BPxSOalU.mjs");
862
+ const { zettelGodNotes } = await import("./zettelkasten-Dwj67e4p.mjs");
847
863
  const result = await zettelGodNotes(backend, {
848
864
  limit: params.limit,
849
865
  minInbound: params.min_inbound
@@ -864,7 +880,7 @@ async function toolZettelGodNotes(backend, params) {
864
880
  }
865
881
  async function toolZettelCommunities(backend, params) {
866
882
  try {
867
- const { zettelCommunities } = await import("./zettelkasten-BPxSOalU.mjs");
883
+ const { zettelCommunities } = await import("./zettelkasten-Dwj67e4p.mjs");
868
884
  const result = await zettelCommunities(backend, {
869
885
  minSize: params.min_size,
870
886
  maxCommunities: params.max_communities,
@@ -1340,7 +1356,7 @@ async function bfsExpand(federation, entityNames, hops, projectId) {
1340
1356
  */
1341
1357
  async function rerankTriples(triples, queryVec, maxTriples) {
1342
1358
  if (triples.length === 0) return [];
1343
- const { generateEmbedding } = await import("./embeddings-BNv3TQC5.mjs");
1359
+ const { generateEmbedding } = await import("./embeddings-CDfM63uC.mjs");
1344
1360
  return (await Promise.all(triples.map(async (triple) => {
1345
1361
  const text = `${triple.subject} ${triple.predicate} ${triple.object}`;
1346
1362
  try {
@@ -1433,7 +1449,7 @@ async function toolMemoryKgSearch(federation, params) {
1433
1449
  }],
1434
1450
  isError: true
1435
1451
  };
1436
- const { generateEmbedding } = await import("./embeddings-BNv3TQC5.mjs");
1452
+ const { generateEmbedding } = await import("./embeddings-CDfM63uC.mjs");
1437
1453
  const result = await graphCompletionSearch(federation, await generateEmbedding(params.query, true), {
1438
1454
  seedCount: params.wide_k ?? 50,
1439
1455
  hops: params.neighborhood_depth ?? 1,
@@ -1469,5 +1485,5 @@ async function toolMemoryKgSearch(federation, params) {
1469
1485
  }
1470
1486
 
1471
1487
  //#endregion
1472
- export { formatProject as A, toolProjectInfo as C, toolMemoryGet as D, combineHybridResults as E, toolMemorySearch as O, toolProjectHealth as S, toolProjectTodo as T, toolZettelThemes as _, toolMemoryWakeup as a, toolSessionRoute as b, toolKgInvalidate as c, toolZettelConverse as d, toolZettelExplore as f, toolZettelSurprise as g, toolZettelSuggest as h, toolMemoryTaxonomy as i, lookupProjectId as j, detectProjectFromPath as k, toolKgQuery as l, toolZettelHealth as m, toolMemoryFeedback as n, toolKgAdd as o, toolZettelGodNotes as p, toolMemoryTunnels as r, toolKgContradictions as s, toolMemoryKgSearch as t, toolZettelCommunities as u, toolRegistrySearch as v, toolProjectList as w, toolProjectDetect as x, toolSessionList as y };
1473
- //# sourceMappingURL=tools-DFdPbX-X.mjs.map
1488
+ export { detectProjectFromPath as A, toolProjectInfo as C, toolMemoryGet as D, combineHybridResults as E, lookupProjectId as M, toolMemoryOutline as O, toolProjectHealth as S, toolProjectTodo as T, toolZettelThemes as _, toolMemoryWakeup as a, toolSessionRoute as b, toolKgInvalidate as c, toolZettelConverse as d, toolZettelExplore as f, toolZettelSurprise as g, toolZettelSuggest as h, toolMemoryTaxonomy as i, formatProject as j, toolMemorySearch as k, toolKgQuery as l, toolZettelHealth as m, toolMemoryFeedback as n, toolKgAdd as o, toolZettelGodNotes as p, toolMemoryTunnels as r, toolKgContradictions as s, toolMemoryKgSearch as t, toolZettelCommunities as u, toolRegistrySearch as v, toolProjectList as w, toolProjectDetect as x, toolSessionList as y };
1489
+ //# sourceMappingURL=tools-gMGDIpJ9.mjs.map
@@ -0,0 +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"}
@@ -134,4 +134,4 @@ async function handleGraphTrace(backend, params) {
134
134
 
135
135
  //#endregion
136
136
  export { handleGraphTrace };
137
- //# sourceMappingURL=trace-BRflVyBi.mjs.map
137
+ //# sourceMappingURL=trace-CmAB7iJZ.mjs.map