laminark 2.21.9 → 2.22.1

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 (1231) hide show
  1. package/package.json +1 -1
  2. package/plugin/.claude-plugin/plugin.json +3 -2
  3. package/plugin/.repair-log +6 -0
  4. package/plugin/CLAUDE.md +57 -8
  5. package/plugin/dist/hooks/handler.d.ts.map +1 -1
  6. package/plugin/dist/hooks/handler.js +49 -5
  7. package/plugin/dist/hooks/handler.js.map +1 -1
  8. package/plugin/dist/index.js +27 -25
  9. package/plugin/dist/index.js.map +1 -1
  10. package/plugin/dist/{tool-registry-D8un_AcG.mjs → tool-registry-Bi1Zdqkm.mjs} +62 -31
  11. package/plugin/dist/tool-registry-Bi1Zdqkm.mjs.map +1 -0
  12. package/plugin/node_modules/.bin/node-which +52 -0
  13. package/plugin/node_modules/.bin/prebuild-install +78 -0
  14. package/plugin/node_modules/.bin/rc +4 -0
  15. package/plugin/node_modules/.bin/semver +191 -0
  16. package/plugin/node_modules/.package-lock.json +2153 -0
  17. package/plugin/node_modules/@anthropic-ai/claude-agent-sdk/LICENSE.md +1 -0
  18. package/plugin/node_modules/@anthropic-ai/claude-agent-sdk/README.md +44 -0
  19. package/plugin/node_modules/@anthropic-ai/claude-agent-sdk/bun.lock +21 -0
  20. package/plugin/node_modules/@anthropic-ai/claude-agent-sdk/cli.js +12421 -0
  21. package/plugin/node_modules/@anthropic-ai/claude-agent-sdk/manifest.json +46 -0
  22. package/plugin/node_modules/@anthropic-ai/claude-agent-sdk/manifest.zst.json +46 -0
  23. package/plugin/node_modules/@anthropic-ai/claude-agent-sdk/package.json +42 -0
  24. package/plugin/node_modules/@anthropic-ai/claude-agent-sdk/resvg.wasm +0 -0
  25. package/plugin/node_modules/@anthropic-ai/claude-agent-sdk/sdk-tools.d.ts +2367 -0
  26. package/plugin/node_modules/@anthropic-ai/claude-agent-sdk/sdk.d.ts +2153 -0
  27. package/plugin/node_modules/@anthropic-ai/claude-agent-sdk/sdk.mjs +59 -0
  28. package/plugin/node_modules/@anthropic-ai/claude-agent-sdk/tree-sitter-bash.wasm +0 -0
  29. package/plugin/node_modules/@anthropic-ai/claude-agent-sdk/tree-sitter.wasm +0 -0
  30. package/plugin/node_modules/@hono/node-server/README.md +357 -0
  31. package/plugin/node_modules/@hono/node-server/package.json +103 -0
  32. package/plugin/node_modules/@huggingface/jinja/LICENSE +21 -0
  33. package/plugin/node_modules/@huggingface/jinja/README.md +78 -0
  34. package/plugin/node_modules/@huggingface/jinja/package.json +54 -0
  35. package/plugin/node_modules/@huggingface/jinja/tsconfig.json +20 -0
  36. package/plugin/node_modules/@huggingface/transformers/LICENSE +202 -0
  37. package/plugin/node_modules/@huggingface/transformers/README.md +467 -0
  38. package/plugin/node_modules/@huggingface/transformers/package.json +90 -0
  39. package/plugin/node_modules/@img/colour/LICENSE.md +82 -0
  40. package/plugin/node_modules/@img/colour/README.md +15 -0
  41. package/plugin/node_modules/@img/colour/color.cjs +1594 -0
  42. package/plugin/node_modules/@img/colour/index.cjs +1 -0
  43. package/plugin/node_modules/@img/colour/package.json +45 -0
  44. package/plugin/node_modules/@img/sharp-libvips-linux-x64/README.md +46 -0
  45. package/plugin/node_modules/@img/sharp-libvips-linux-x64/package.json +42 -0
  46. package/plugin/node_modules/@img/sharp-libvips-linux-x64/versions.json +30 -0
  47. package/plugin/node_modules/@img/sharp-linux-x64/LICENSE +191 -0
  48. package/plugin/node_modules/@img/sharp-linux-x64/README.md +18 -0
  49. package/plugin/node_modules/@img/sharp-linux-x64/package.json +46 -0
  50. package/plugin/node_modules/@isaacs/fs-minipass/LICENSE +15 -0
  51. package/plugin/node_modules/@isaacs/fs-minipass/README.md +71 -0
  52. package/plugin/node_modules/@isaacs/fs-minipass/package.json +72 -0
  53. package/plugin/node_modules/@modelcontextprotocol/sdk/LICENSE +21 -0
  54. package/plugin/node_modules/@modelcontextprotocol/sdk/README.md +170 -0
  55. package/plugin/node_modules/@modelcontextprotocol/sdk/package.json +155 -0
  56. package/plugin/node_modules/@protobufjs/aspromise/LICENSE +26 -0
  57. package/plugin/node_modules/@protobufjs/aspromise/README.md +13 -0
  58. package/plugin/node_modules/@protobufjs/aspromise/index.d.ts +13 -0
  59. package/plugin/node_modules/@protobufjs/aspromise/index.js +52 -0
  60. package/plugin/node_modules/@protobufjs/aspromise/package.json +21 -0
  61. package/plugin/node_modules/@protobufjs/base64/LICENSE +26 -0
  62. package/plugin/node_modules/@protobufjs/base64/README.md +19 -0
  63. package/plugin/node_modules/@protobufjs/base64/index.d.ts +32 -0
  64. package/plugin/node_modules/@protobufjs/base64/index.js +139 -0
  65. package/plugin/node_modules/@protobufjs/base64/package.json +21 -0
  66. package/plugin/node_modules/@protobufjs/codegen/LICENSE +26 -0
  67. package/plugin/node_modules/@protobufjs/codegen/README.md +49 -0
  68. package/plugin/node_modules/@protobufjs/codegen/index.d.ts +31 -0
  69. package/plugin/node_modules/@protobufjs/codegen/index.js +99 -0
  70. package/plugin/node_modules/@protobufjs/codegen/package.json +13 -0
  71. package/plugin/node_modules/@protobufjs/eventemitter/LICENSE +26 -0
  72. package/plugin/node_modules/@protobufjs/eventemitter/README.md +22 -0
  73. package/plugin/node_modules/@protobufjs/eventemitter/index.d.ts +43 -0
  74. package/plugin/node_modules/@protobufjs/eventemitter/index.js +76 -0
  75. package/plugin/node_modules/@protobufjs/eventemitter/package.json +21 -0
  76. package/plugin/node_modules/@protobufjs/fetch/LICENSE +26 -0
  77. package/plugin/node_modules/@protobufjs/fetch/README.md +13 -0
  78. package/plugin/node_modules/@protobufjs/fetch/index.d.ts +56 -0
  79. package/plugin/node_modules/@protobufjs/fetch/index.js +115 -0
  80. package/plugin/node_modules/@protobufjs/fetch/package.json +25 -0
  81. package/plugin/node_modules/@protobufjs/float/LICENSE +26 -0
  82. package/plugin/node_modules/@protobufjs/float/README.md +102 -0
  83. package/plugin/node_modules/@protobufjs/float/index.d.ts +83 -0
  84. package/plugin/node_modules/@protobufjs/float/index.js +335 -0
  85. package/plugin/node_modules/@protobufjs/float/package.json +26 -0
  86. package/plugin/node_modules/@protobufjs/inquire/LICENSE +26 -0
  87. package/plugin/node_modules/@protobufjs/inquire/README.md +13 -0
  88. package/plugin/node_modules/@protobufjs/inquire/index.d.ts +9 -0
  89. package/plugin/node_modules/@protobufjs/inquire/index.js +17 -0
  90. package/plugin/node_modules/@protobufjs/inquire/package.json +21 -0
  91. package/plugin/node_modules/@protobufjs/path/LICENSE +26 -0
  92. package/plugin/node_modules/@protobufjs/path/README.md +19 -0
  93. package/plugin/node_modules/@protobufjs/path/index.d.ts +22 -0
  94. package/plugin/node_modules/@protobufjs/path/index.js +65 -0
  95. package/plugin/node_modules/@protobufjs/path/package.json +21 -0
  96. package/plugin/node_modules/@protobufjs/pool/LICENSE +26 -0
  97. package/plugin/node_modules/@protobufjs/pool/README.md +13 -0
  98. package/plugin/node_modules/@protobufjs/pool/index.d.ts +32 -0
  99. package/plugin/node_modules/@protobufjs/pool/index.js +48 -0
  100. package/plugin/node_modules/@protobufjs/pool/package.json +21 -0
  101. package/plugin/node_modules/@protobufjs/utf8/LICENSE +26 -0
  102. package/plugin/node_modules/@protobufjs/utf8/README.md +20 -0
  103. package/plugin/node_modules/@protobufjs/utf8/index.d.ts +24 -0
  104. package/plugin/node_modules/@protobufjs/utf8/index.js +105 -0
  105. package/plugin/node_modules/@protobufjs/utf8/package.json +21 -0
  106. package/plugin/node_modules/@types/node/LICENSE +21 -0
  107. package/plugin/node_modules/@types/node/README.md +15 -0
  108. package/plugin/node_modules/@types/node/assert.d.ts +955 -0
  109. package/plugin/node_modules/@types/node/async_hooks.d.ts +623 -0
  110. package/plugin/node_modules/@types/node/buffer.buffer.d.ts +466 -0
  111. package/plugin/node_modules/@types/node/buffer.d.ts +1810 -0
  112. package/plugin/node_modules/@types/node/child_process.d.ts +1428 -0
  113. package/plugin/node_modules/@types/node/cluster.d.ts +486 -0
  114. package/plugin/node_modules/@types/node/console.d.ts +151 -0
  115. package/plugin/node_modules/@types/node/constants.d.ts +20 -0
  116. package/plugin/node_modules/@types/node/crypto.d.ts +4065 -0
  117. package/plugin/node_modules/@types/node/dgram.d.ts +564 -0
  118. package/plugin/node_modules/@types/node/diagnostics_channel.d.ts +576 -0
  119. package/plugin/node_modules/@types/node/dns.d.ts +922 -0
  120. package/plugin/node_modules/@types/node/domain.d.ts +166 -0
  121. package/plugin/node_modules/@types/node/events.d.ts +1054 -0
  122. package/plugin/node_modules/@types/node/fs.d.ts +4676 -0
  123. package/plugin/node_modules/@types/node/globals.d.ts +150 -0
  124. package/plugin/node_modules/@types/node/globals.typedarray.d.ts +101 -0
  125. package/plugin/node_modules/@types/node/http.d.ts +2167 -0
  126. package/plugin/node_modules/@types/node/http2.d.ts +2480 -0
  127. package/plugin/node_modules/@types/node/https.d.ts +405 -0
  128. package/plugin/node_modules/@types/node/index.d.ts +115 -0
  129. package/plugin/node_modules/@types/node/inspector.d.ts +224 -0
  130. package/plugin/node_modules/@types/node/inspector.generated.d.ts +4226 -0
  131. package/plugin/node_modules/@types/node/module.d.ts +819 -0
  132. package/plugin/node_modules/@types/node/net.d.ts +933 -0
  133. package/plugin/node_modules/@types/node/os.d.ts +507 -0
  134. package/plugin/node_modules/@types/node/package.json +155 -0
  135. package/plugin/node_modules/@types/node/path.d.ts +187 -0
  136. package/plugin/node_modules/@types/node/perf_hooks.d.ts +643 -0
  137. package/plugin/node_modules/@types/node/process.d.ts +2161 -0
  138. package/plugin/node_modules/@types/node/punycode.d.ts +117 -0
  139. package/plugin/node_modules/@types/node/querystring.d.ts +152 -0
  140. package/plugin/node_modules/@types/node/quic.d.ts +910 -0
  141. package/plugin/node_modules/@types/node/readline.d.ts +541 -0
  142. package/plugin/node_modules/@types/node/repl.d.ts +415 -0
  143. package/plugin/node_modules/@types/node/sea.d.ts +162 -0
  144. package/plugin/node_modules/@types/node/sqlite.d.ts +955 -0
  145. package/plugin/node_modules/@types/node/stream.d.ts +1760 -0
  146. package/plugin/node_modules/@types/node/string_decoder.d.ts +67 -0
  147. package/plugin/node_modules/@types/node/test.d.ts +2240 -0
  148. package/plugin/node_modules/@types/node/timers.d.ts +159 -0
  149. package/plugin/node_modules/@types/node/tls.d.ts +1198 -0
  150. package/plugin/node_modules/@types/node/trace_events.d.ts +197 -0
  151. package/plugin/node_modules/@types/node/tty.d.ts +250 -0
  152. package/plugin/node_modules/@types/node/url.d.ts +519 -0
  153. package/plugin/node_modules/@types/node/util.d.ts +1662 -0
  154. package/plugin/node_modules/@types/node/v8.d.ts +983 -0
  155. package/plugin/node_modules/@types/node/vm.d.ts +1208 -0
  156. package/plugin/node_modules/@types/node/wasi.d.ts +202 -0
  157. package/plugin/node_modules/@types/node/worker_threads.d.ts +717 -0
  158. package/plugin/node_modules/@types/node/zlib.d.ts +618 -0
  159. package/plugin/node_modules/accepts/HISTORY.md +250 -0
  160. package/plugin/node_modules/accepts/LICENSE +23 -0
  161. package/plugin/node_modules/accepts/README.md +140 -0
  162. package/plugin/node_modules/accepts/index.js +238 -0
  163. package/plugin/node_modules/accepts/package.json +47 -0
  164. package/plugin/node_modules/ajv/.runkit_example.js +23 -0
  165. package/plugin/node_modules/ajv/LICENSE +22 -0
  166. package/plugin/node_modules/ajv/README.md +207 -0
  167. package/plugin/node_modules/ajv/dist/2019.js +61 -0
  168. package/plugin/node_modules/ajv/dist/2020.js +55 -0
  169. package/plugin/node_modules/ajv/dist/ajv.js +50 -0
  170. package/plugin/node_modules/ajv/dist/core.js +618 -0
  171. package/plugin/node_modules/ajv/lib/2019.ts +81 -0
  172. package/plugin/node_modules/ajv/lib/2020.ts +75 -0
  173. package/plugin/node_modules/ajv/lib/ajv.ts +70 -0
  174. package/plugin/node_modules/ajv/lib/core.ts +892 -0
  175. package/plugin/node_modules/ajv/lib/jtd.ts +132 -0
  176. package/plugin/node_modules/ajv/package.json +127 -0
  177. package/plugin/node_modules/ajv-formats/LICENSE +21 -0
  178. package/plugin/node_modules/ajv-formats/README.md +125 -0
  179. package/plugin/node_modules/ajv-formats/dist/formats.d.ts +9 -0
  180. package/plugin/node_modules/ajv-formats/dist/formats.js +208 -0
  181. package/plugin/node_modules/ajv-formats/dist/formats.js.map +1 -0
  182. package/plugin/node_modules/ajv-formats/dist/index.d.ts +15 -0
  183. package/plugin/node_modules/ajv-formats/dist/index.js +37 -0
  184. package/plugin/node_modules/ajv-formats/dist/index.js.map +1 -0
  185. package/plugin/node_modules/ajv-formats/dist/limit.d.ts +10 -0
  186. package/plugin/node_modules/ajv-formats/dist/limit.js +69 -0
  187. package/plugin/node_modules/ajv-formats/dist/limit.js.map +1 -0
  188. package/plugin/node_modules/ajv-formats/package.json +74 -0
  189. package/plugin/node_modules/ajv-formats/src/formats.ts +269 -0
  190. package/plugin/node_modules/ajv-formats/src/index.ts +62 -0
  191. package/plugin/node_modules/ajv-formats/src/limit.ts +99 -0
  192. package/plugin/node_modules/base64-js/LICENSE +21 -0
  193. package/plugin/node_modules/base64-js/README.md +34 -0
  194. package/plugin/node_modules/base64-js/base64js.min.js +1 -0
  195. package/plugin/node_modules/base64-js/index.d.ts +3 -0
  196. package/plugin/node_modules/base64-js/index.js +150 -0
  197. package/plugin/node_modules/base64-js/package.json +47 -0
  198. package/plugin/node_modules/better-sqlite3/LICENSE +21 -0
  199. package/plugin/node_modules/better-sqlite3/README.md +99 -0
  200. package/plugin/node_modules/better-sqlite3/binding.gyp +38 -0
  201. package/plugin/node_modules/better-sqlite3/deps/common.gypi +68 -0
  202. package/plugin/node_modules/better-sqlite3/deps/copy.js +31 -0
  203. package/plugin/node_modules/better-sqlite3/deps/defines.gypi +41 -0
  204. package/plugin/node_modules/better-sqlite3/deps/download.sh +122 -0
  205. package/plugin/node_modules/better-sqlite3/deps/sqlite3.gyp +80 -0
  206. package/plugin/node_modules/better-sqlite3/deps/test_extension.c +21 -0
  207. package/plugin/node_modules/better-sqlite3/lib/database.js +90 -0
  208. package/plugin/node_modules/better-sqlite3/lib/index.js +3 -0
  209. package/plugin/node_modules/better-sqlite3/lib/sqlite-error.js +20 -0
  210. package/plugin/node_modules/better-sqlite3/lib/util.js +12 -0
  211. package/plugin/node_modules/better-sqlite3/package.json +59 -0
  212. package/plugin/node_modules/better-sqlite3/src/addon.cpp +47 -0
  213. package/plugin/node_modules/better-sqlite3/src/better_sqlite3.cpp +74 -0
  214. package/plugin/node_modules/bindings/LICENSE.md +22 -0
  215. package/plugin/node_modules/bindings/README.md +98 -0
  216. package/plugin/node_modules/bindings/bindings.js +221 -0
  217. package/plugin/node_modules/bindings/package.json +28 -0
  218. package/plugin/node_modules/bl/.travis.yml +17 -0
  219. package/plugin/node_modules/bl/BufferList.js +396 -0
  220. package/plugin/node_modules/bl/LICENSE.md +13 -0
  221. package/plugin/node_modules/bl/README.md +247 -0
  222. package/plugin/node_modules/bl/bl.js +84 -0
  223. package/plugin/node_modules/bl/package.json +37 -0
  224. package/plugin/node_modules/body-parser/LICENSE +23 -0
  225. package/plugin/node_modules/body-parser/README.md +494 -0
  226. package/plugin/node_modules/body-parser/index.js +71 -0
  227. package/plugin/node_modules/body-parser/package.json +52 -0
  228. package/plugin/node_modules/boolean/.eslintrc.json +3 -0
  229. package/plugin/node_modules/boolean/.npmpackagejsonlintrc.json +3 -0
  230. package/plugin/node_modules/boolean/.releaserc.json +3 -0
  231. package/plugin/node_modules/boolean/CHANGELOG.md +70 -0
  232. package/plugin/node_modules/boolean/LICENSE.txt +8 -0
  233. package/plugin/node_modules/boolean/README.md +95 -0
  234. package/plugin/node_modules/boolean/licenseCheck.json +16 -0
  235. package/plugin/node_modules/boolean/package.json +46 -0
  236. package/plugin/node_modules/boolean/tsconfig.json +19 -0
  237. package/plugin/node_modules/buffer/AUTHORS.md +70 -0
  238. package/plugin/node_modules/buffer/LICENSE +21 -0
  239. package/plugin/node_modules/buffer/README.md +410 -0
  240. package/plugin/node_modules/buffer/index.d.ts +186 -0
  241. package/plugin/node_modules/buffer/index.js +1817 -0
  242. package/plugin/node_modules/buffer/package.json +96 -0
  243. package/plugin/node_modules/bytes/History.md +97 -0
  244. package/plugin/node_modules/bytes/LICENSE +23 -0
  245. package/plugin/node_modules/bytes/Readme.md +152 -0
  246. package/plugin/node_modules/bytes/index.js +170 -0
  247. package/plugin/node_modules/bytes/package.json +42 -0
  248. package/plugin/node_modules/call-bind-apply-helpers/.eslintrc +17 -0
  249. package/plugin/node_modules/call-bind-apply-helpers/.nycrc +9 -0
  250. package/plugin/node_modules/call-bind-apply-helpers/CHANGELOG.md +30 -0
  251. package/plugin/node_modules/call-bind-apply-helpers/LICENSE +21 -0
  252. package/plugin/node_modules/call-bind-apply-helpers/README.md +62 -0
  253. package/plugin/node_modules/call-bind-apply-helpers/actualApply.d.ts +1 -0
  254. package/plugin/node_modules/call-bind-apply-helpers/actualApply.js +10 -0
  255. package/plugin/node_modules/call-bind-apply-helpers/applyBind.d.ts +19 -0
  256. package/plugin/node_modules/call-bind-apply-helpers/applyBind.js +10 -0
  257. package/plugin/node_modules/call-bind-apply-helpers/functionApply.d.ts +1 -0
  258. package/plugin/node_modules/call-bind-apply-helpers/functionApply.js +4 -0
  259. package/plugin/node_modules/call-bind-apply-helpers/functionCall.d.ts +1 -0
  260. package/plugin/node_modules/call-bind-apply-helpers/functionCall.js +4 -0
  261. package/plugin/node_modules/call-bind-apply-helpers/index.d.ts +64 -0
  262. package/plugin/node_modules/call-bind-apply-helpers/index.js +15 -0
  263. package/plugin/node_modules/call-bind-apply-helpers/package.json +85 -0
  264. package/plugin/node_modules/call-bind-apply-helpers/reflectApply.d.ts +3 -0
  265. package/plugin/node_modules/call-bind-apply-helpers/reflectApply.js +4 -0
  266. package/plugin/node_modules/call-bind-apply-helpers/tsconfig.json +9 -0
  267. package/plugin/node_modules/call-bound/.eslintrc +13 -0
  268. package/plugin/node_modules/call-bound/.nycrc +9 -0
  269. package/plugin/node_modules/call-bound/CHANGELOG.md +42 -0
  270. package/plugin/node_modules/call-bound/LICENSE +21 -0
  271. package/plugin/node_modules/call-bound/README.md +53 -0
  272. package/plugin/node_modules/call-bound/index.d.ts +94 -0
  273. package/plugin/node_modules/call-bound/index.js +19 -0
  274. package/plugin/node_modules/call-bound/package.json +99 -0
  275. package/plugin/node_modules/call-bound/tsconfig.json +10 -0
  276. package/plugin/node_modules/chownr/LICENSE.md +63 -0
  277. package/plugin/node_modules/chownr/README.md +3 -0
  278. package/plugin/node_modules/chownr/package.json +69 -0
  279. package/plugin/node_modules/content-disposition/HISTORY.md +72 -0
  280. package/plugin/node_modules/content-disposition/LICENSE +22 -0
  281. package/plugin/node_modules/content-disposition/README.md +142 -0
  282. package/plugin/node_modules/content-disposition/index.js +458 -0
  283. package/plugin/node_modules/content-disposition/package.json +43 -0
  284. package/plugin/node_modules/content-type/HISTORY.md +29 -0
  285. package/plugin/node_modules/content-type/LICENSE +22 -0
  286. package/plugin/node_modules/content-type/README.md +94 -0
  287. package/plugin/node_modules/content-type/index.js +225 -0
  288. package/plugin/node_modules/content-type/package.json +42 -0
  289. package/plugin/node_modules/cookie/LICENSE +24 -0
  290. package/plugin/node_modules/cookie/README.md +317 -0
  291. package/plugin/node_modules/cookie/SECURITY.md +25 -0
  292. package/plugin/node_modules/cookie/index.js +335 -0
  293. package/plugin/node_modules/cookie/package.json +44 -0
  294. package/plugin/node_modules/cookie-signature/History.md +70 -0
  295. package/plugin/node_modules/cookie-signature/LICENSE +22 -0
  296. package/plugin/node_modules/cookie-signature/Readme.md +23 -0
  297. package/plugin/node_modules/cookie-signature/index.js +47 -0
  298. package/plugin/node_modules/cookie-signature/package.json +24 -0
  299. package/plugin/node_modules/cors/LICENSE +22 -0
  300. package/plugin/node_modules/cors/README.md +277 -0
  301. package/plugin/node_modules/cors/package.json +42 -0
  302. package/plugin/node_modules/cross-spawn/LICENSE +21 -0
  303. package/plugin/node_modules/cross-spawn/README.md +89 -0
  304. package/plugin/node_modules/cross-spawn/index.js +39 -0
  305. package/plugin/node_modules/cross-spawn/package.json +73 -0
  306. package/plugin/node_modules/debug/LICENSE +20 -0
  307. package/plugin/node_modules/debug/README.md +481 -0
  308. package/plugin/node_modules/debug/package.json +64 -0
  309. package/plugin/node_modules/decompress-response/index.d.ts +22 -0
  310. package/plugin/node_modules/decompress-response/index.js +58 -0
  311. package/plugin/node_modules/decompress-response/license +9 -0
  312. package/plugin/node_modules/decompress-response/package.json +56 -0
  313. package/plugin/node_modules/decompress-response/readme.md +48 -0
  314. package/plugin/node_modules/deep-extend/CHANGELOG.md +46 -0
  315. package/plugin/node_modules/deep-extend/LICENSE +20 -0
  316. package/plugin/node_modules/deep-extend/README.md +91 -0
  317. package/plugin/node_modules/deep-extend/index.js +1 -0
  318. package/plugin/node_modules/deep-extend/package.json +62 -0
  319. package/plugin/node_modules/define-data-property/.eslintrc +24 -0
  320. package/plugin/node_modules/define-data-property/.nycrc +13 -0
  321. package/plugin/node_modules/define-data-property/CHANGELOG.md +70 -0
  322. package/plugin/node_modules/define-data-property/LICENSE +21 -0
  323. package/plugin/node_modules/define-data-property/README.md +67 -0
  324. package/plugin/node_modules/define-data-property/index.d.ts +12 -0
  325. package/plugin/node_modules/define-data-property/index.js +56 -0
  326. package/plugin/node_modules/define-data-property/package.json +106 -0
  327. package/plugin/node_modules/define-data-property/tsconfig.json +59 -0
  328. package/plugin/node_modules/define-properties/.editorconfig +13 -0
  329. package/plugin/node_modules/define-properties/.eslintrc +19 -0
  330. package/plugin/node_modules/define-properties/.nycrc +9 -0
  331. package/plugin/node_modules/define-properties/CHANGELOG.md +91 -0
  332. package/plugin/node_modules/define-properties/LICENSE +21 -0
  333. package/plugin/node_modules/define-properties/README.md +84 -0
  334. package/plugin/node_modules/define-properties/index.js +47 -0
  335. package/plugin/node_modules/define-properties/package.json +88 -0
  336. package/plugin/node_modules/depd/History.md +103 -0
  337. package/plugin/node_modules/depd/LICENSE +22 -0
  338. package/plugin/node_modules/depd/Readme.md +280 -0
  339. package/plugin/node_modules/depd/index.js +538 -0
  340. package/plugin/node_modules/depd/package.json +45 -0
  341. package/plugin/node_modules/detect-libc/LICENSE +201 -0
  342. package/plugin/node_modules/detect-libc/README.md +163 -0
  343. package/plugin/node_modules/detect-libc/index.d.ts +14 -0
  344. package/plugin/node_modules/detect-libc/package.json +44 -0
  345. package/plugin/node_modules/detect-node/LICENSE +21 -0
  346. package/plugin/node_modules/detect-node/Readme.md +30 -0
  347. package/plugin/node_modules/detect-node/browser.js +2 -0
  348. package/plugin/node_modules/detect-node/index.esm.js +2 -0
  349. package/plugin/node_modules/detect-node/index.js +2 -0
  350. package/plugin/node_modules/detect-node/package.json +25 -0
  351. package/plugin/node_modules/dunder-proto/.eslintrc +5 -0
  352. package/plugin/node_modules/dunder-proto/.nycrc +13 -0
  353. package/plugin/node_modules/dunder-proto/CHANGELOG.md +24 -0
  354. package/plugin/node_modules/dunder-proto/LICENSE +21 -0
  355. package/plugin/node_modules/dunder-proto/README.md +54 -0
  356. package/plugin/node_modules/dunder-proto/get.d.ts +5 -0
  357. package/plugin/node_modules/dunder-proto/get.js +30 -0
  358. package/plugin/node_modules/dunder-proto/package.json +76 -0
  359. package/plugin/node_modules/dunder-proto/set.d.ts +5 -0
  360. package/plugin/node_modules/dunder-proto/set.js +35 -0
  361. package/plugin/node_modules/dunder-proto/tsconfig.json +9 -0
  362. package/plugin/node_modules/ee-first/LICENSE +22 -0
  363. package/plugin/node_modules/ee-first/README.md +80 -0
  364. package/plugin/node_modules/ee-first/index.js +95 -0
  365. package/plugin/node_modules/ee-first/package.json +29 -0
  366. package/plugin/node_modules/encodeurl/LICENSE +22 -0
  367. package/plugin/node_modules/encodeurl/README.md +109 -0
  368. package/plugin/node_modules/encodeurl/index.js +60 -0
  369. package/plugin/node_modules/encodeurl/package.json +40 -0
  370. package/plugin/node_modules/end-of-stream/LICENSE +21 -0
  371. package/plugin/node_modules/end-of-stream/README.md +54 -0
  372. package/plugin/node_modules/end-of-stream/index.js +96 -0
  373. package/plugin/node_modules/end-of-stream/package.json +37 -0
  374. package/plugin/node_modules/es-define-property/.eslintrc +13 -0
  375. package/plugin/node_modules/es-define-property/.nycrc +9 -0
  376. package/plugin/node_modules/es-define-property/CHANGELOG.md +29 -0
  377. package/plugin/node_modules/es-define-property/LICENSE +21 -0
  378. package/plugin/node_modules/es-define-property/README.md +49 -0
  379. package/plugin/node_modules/es-define-property/index.d.ts +3 -0
  380. package/plugin/node_modules/es-define-property/index.js +14 -0
  381. package/plugin/node_modules/es-define-property/package.json +81 -0
  382. package/plugin/node_modules/es-define-property/tsconfig.json +10 -0
  383. package/plugin/node_modules/es-errors/.eslintrc +5 -0
  384. package/plugin/node_modules/es-errors/CHANGELOG.md +40 -0
  385. package/plugin/node_modules/es-errors/LICENSE +21 -0
  386. package/plugin/node_modules/es-errors/README.md +55 -0
  387. package/plugin/node_modules/es-errors/eval.d.ts +3 -0
  388. package/plugin/node_modules/es-errors/eval.js +4 -0
  389. package/plugin/node_modules/es-errors/index.d.ts +3 -0
  390. package/plugin/node_modules/es-errors/index.js +4 -0
  391. package/plugin/node_modules/es-errors/package.json +80 -0
  392. package/plugin/node_modules/es-errors/range.d.ts +3 -0
  393. package/plugin/node_modules/es-errors/range.js +4 -0
  394. package/plugin/node_modules/es-errors/ref.d.ts +3 -0
  395. package/plugin/node_modules/es-errors/ref.js +4 -0
  396. package/plugin/node_modules/es-errors/syntax.d.ts +3 -0
  397. package/plugin/node_modules/es-errors/syntax.js +4 -0
  398. package/plugin/node_modules/es-errors/tsconfig.json +49 -0
  399. package/plugin/node_modules/es-errors/type.d.ts +3 -0
  400. package/plugin/node_modules/es-errors/type.js +4 -0
  401. package/plugin/node_modules/es-errors/uri.d.ts +3 -0
  402. package/plugin/node_modules/es-errors/uri.js +4 -0
  403. package/plugin/node_modules/es-object-atoms/.eslintrc +16 -0
  404. package/plugin/node_modules/es-object-atoms/CHANGELOG.md +37 -0
  405. package/plugin/node_modules/es-object-atoms/LICENSE +21 -0
  406. package/plugin/node_modules/es-object-atoms/README.md +63 -0
  407. package/plugin/node_modules/es-object-atoms/RequireObjectCoercible.d.ts +3 -0
  408. package/plugin/node_modules/es-object-atoms/RequireObjectCoercible.js +11 -0
  409. package/plugin/node_modules/es-object-atoms/ToObject.d.ts +7 -0
  410. package/plugin/node_modules/es-object-atoms/ToObject.js +10 -0
  411. package/plugin/node_modules/es-object-atoms/index.d.ts +3 -0
  412. package/plugin/node_modules/es-object-atoms/index.js +4 -0
  413. package/plugin/node_modules/es-object-atoms/isObject.d.ts +3 -0
  414. package/plugin/node_modules/es-object-atoms/isObject.js +6 -0
  415. package/plugin/node_modules/es-object-atoms/package.json +80 -0
  416. package/plugin/node_modules/es-object-atoms/tsconfig.json +6 -0
  417. package/plugin/node_modules/es6-error/CHANGELOG.md +26 -0
  418. package/plugin/node_modules/es6-error/LICENSE.md +21 -0
  419. package/plugin/node_modules/es6-error/README.md +59 -0
  420. package/plugin/node_modules/es6-error/package.json +48 -0
  421. package/plugin/node_modules/escape-html/LICENSE +24 -0
  422. package/plugin/node_modules/escape-html/Readme.md +43 -0
  423. package/plugin/node_modules/escape-html/index.js +78 -0
  424. package/plugin/node_modules/escape-html/package.json +24 -0
  425. package/plugin/node_modules/escape-string-regexp/index.d.ts +18 -0
  426. package/plugin/node_modules/escape-string-regexp/index.js +13 -0
  427. package/plugin/node_modules/escape-string-regexp/license +9 -0
  428. package/plugin/node_modules/escape-string-regexp/package.json +38 -0
  429. package/plugin/node_modules/escape-string-regexp/readme.md +34 -0
  430. package/plugin/node_modules/etag/HISTORY.md +83 -0
  431. package/plugin/node_modules/etag/LICENSE +22 -0
  432. package/plugin/node_modules/etag/README.md +159 -0
  433. package/plugin/node_modules/etag/index.js +131 -0
  434. package/plugin/node_modules/etag/package.json +47 -0
  435. package/plugin/node_modules/eventsource/LICENSE +22 -0
  436. package/plugin/node_modules/eventsource/README.md +167 -0
  437. package/plugin/node_modules/eventsource/package.json +130 -0
  438. package/plugin/node_modules/eventsource-parser/LICENSE +21 -0
  439. package/plugin/node_modules/eventsource-parser/README.md +126 -0
  440. package/plugin/node_modules/eventsource-parser/package.json +115 -0
  441. package/plugin/node_modules/eventsource-parser/stream.js +2 -0
  442. package/plugin/node_modules/expand-template/.travis.yml +6 -0
  443. package/plugin/node_modules/expand-template/LICENSE +21 -0
  444. package/plugin/node_modules/expand-template/README.md +43 -0
  445. package/plugin/node_modules/expand-template/index.js +26 -0
  446. package/plugin/node_modules/expand-template/package.json +29 -0
  447. package/plugin/node_modules/expand-template/test.js +67 -0
  448. package/plugin/node_modules/express/LICENSE +24 -0
  449. package/plugin/node_modules/express/Readme.md +276 -0
  450. package/plugin/node_modules/express/index.js +11 -0
  451. package/plugin/node_modules/express/package.json +99 -0
  452. package/plugin/node_modules/express-rate-limit/license.md +20 -0
  453. package/plugin/node_modules/express-rate-limit/package.json +112 -0
  454. package/plugin/node_modules/express-rate-limit/readme.md +151 -0
  455. package/plugin/node_modules/express-rate-limit/tsconfig.json +8 -0
  456. package/plugin/node_modules/fast-deep-equal/LICENSE +21 -0
  457. package/plugin/node_modules/fast-deep-equal/README.md +96 -0
  458. package/plugin/node_modules/fast-deep-equal/index.d.ts +4 -0
  459. package/plugin/node_modules/fast-deep-equal/index.js +46 -0
  460. package/plugin/node_modules/fast-deep-equal/package.json +61 -0
  461. package/plugin/node_modules/fast-deep-equal/react.d.ts +2 -0
  462. package/plugin/node_modules/fast-deep-equal/react.js +53 -0
  463. package/plugin/node_modules/fast-uri/.gitattributes +2 -0
  464. package/plugin/node_modules/fast-uri/LICENSE +32 -0
  465. package/plugin/node_modules/fast-uri/README.md +143 -0
  466. package/plugin/node_modules/fast-uri/eslint.config.js +6 -0
  467. package/plugin/node_modules/fast-uri/index.js +340 -0
  468. package/plugin/node_modules/fast-uri/package.json +69 -0
  469. package/plugin/node_modules/fast-uri/tsconfig.json +9 -0
  470. package/plugin/node_modules/file-uri-to-path/.travis.yml +30 -0
  471. package/plugin/node_modules/file-uri-to-path/History.md +21 -0
  472. package/plugin/node_modules/file-uri-to-path/LICENSE +20 -0
  473. package/plugin/node_modules/file-uri-to-path/README.md +74 -0
  474. package/plugin/node_modules/file-uri-to-path/index.d.ts +2 -0
  475. package/plugin/node_modules/file-uri-to-path/index.js +66 -0
  476. package/plugin/node_modules/file-uri-to-path/package.json +32 -0
  477. package/plugin/node_modules/finalhandler/HISTORY.md +239 -0
  478. package/plugin/node_modules/finalhandler/LICENSE +22 -0
  479. package/plugin/node_modules/finalhandler/README.md +150 -0
  480. package/plugin/node_modules/finalhandler/index.js +293 -0
  481. package/plugin/node_modules/finalhandler/package.json +47 -0
  482. package/plugin/node_modules/flatbuffers/LICENSE +202 -0
  483. package/plugin/node_modules/flatbuffers/README.md +116 -0
  484. package/plugin/node_modules/flatbuffers/package.json +53 -0
  485. package/plugin/node_modules/forwarded/HISTORY.md +21 -0
  486. package/plugin/node_modules/forwarded/LICENSE +22 -0
  487. package/plugin/node_modules/forwarded/README.md +57 -0
  488. package/plugin/node_modules/forwarded/index.js +90 -0
  489. package/plugin/node_modules/forwarded/package.json +45 -0
  490. package/plugin/node_modules/fresh/HISTORY.md +80 -0
  491. package/plugin/node_modules/fresh/LICENSE +23 -0
  492. package/plugin/node_modules/fresh/README.md +117 -0
  493. package/plugin/node_modules/fresh/index.js +136 -0
  494. package/plugin/node_modules/fresh/package.json +46 -0
  495. package/plugin/node_modules/fs-constants/LICENSE +21 -0
  496. package/plugin/node_modules/fs-constants/README.md +26 -0
  497. package/plugin/node_modules/fs-constants/browser.js +1 -0
  498. package/plugin/node_modules/fs-constants/index.js +1 -0
  499. package/plugin/node_modules/fs-constants/package.json +19 -0
  500. package/plugin/node_modules/function-bind/.eslintrc +21 -0
  501. package/plugin/node_modules/function-bind/.nycrc +13 -0
  502. package/plugin/node_modules/function-bind/CHANGELOG.md +136 -0
  503. package/plugin/node_modules/function-bind/LICENSE +20 -0
  504. package/plugin/node_modules/function-bind/README.md +46 -0
  505. package/plugin/node_modules/function-bind/implementation.js +84 -0
  506. package/plugin/node_modules/function-bind/index.js +5 -0
  507. package/plugin/node_modules/function-bind/package.json +87 -0
  508. package/plugin/node_modules/get-intrinsic/.eslintrc +42 -0
  509. package/plugin/node_modules/get-intrinsic/.nycrc +9 -0
  510. package/plugin/node_modules/get-intrinsic/CHANGELOG.md +186 -0
  511. package/plugin/node_modules/get-intrinsic/LICENSE +21 -0
  512. package/plugin/node_modules/get-intrinsic/README.md +71 -0
  513. package/plugin/node_modules/get-intrinsic/index.js +378 -0
  514. package/plugin/node_modules/get-intrinsic/package.json +97 -0
  515. package/plugin/node_modules/get-proto/.eslintrc +10 -0
  516. package/plugin/node_modules/get-proto/.nycrc +9 -0
  517. package/plugin/node_modules/get-proto/CHANGELOG.md +21 -0
  518. package/plugin/node_modules/get-proto/LICENSE +21 -0
  519. package/plugin/node_modules/get-proto/Object.getPrototypeOf.d.ts +5 -0
  520. package/plugin/node_modules/get-proto/Object.getPrototypeOf.js +6 -0
  521. package/plugin/node_modules/get-proto/README.md +50 -0
  522. package/plugin/node_modules/get-proto/Reflect.getPrototypeOf.d.ts +3 -0
  523. package/plugin/node_modules/get-proto/Reflect.getPrototypeOf.js +4 -0
  524. package/plugin/node_modules/get-proto/index.d.ts +5 -0
  525. package/plugin/node_modules/get-proto/index.js +27 -0
  526. package/plugin/node_modules/get-proto/package.json +81 -0
  527. package/plugin/node_modules/get-proto/tsconfig.json +9 -0
  528. package/plugin/node_modules/github-from-package/.travis.yml +4 -0
  529. package/plugin/node_modules/github-from-package/LICENSE +18 -0
  530. package/plugin/node_modules/github-from-package/index.js +17 -0
  531. package/plugin/node_modules/github-from-package/package.json +30 -0
  532. package/plugin/node_modules/github-from-package/readme.markdown +53 -0
  533. package/plugin/node_modules/global-agent/.flowconfig +3 -0
  534. package/plugin/node_modules/global-agent/LICENSE +24 -0
  535. package/plugin/node_modules/global-agent/README.md +239 -0
  536. package/plugin/node_modules/global-agent/bootstrap.js +1 -0
  537. package/plugin/node_modules/global-agent/package.json +105 -0
  538. package/plugin/node_modules/globalthis/.eslintrc +18 -0
  539. package/plugin/node_modules/globalthis/.nycrc +10 -0
  540. package/plugin/node_modules/globalthis/CHANGELOG.md +109 -0
  541. package/plugin/node_modules/globalthis/LICENSE +21 -0
  542. package/plugin/node_modules/globalthis/README.md +70 -0
  543. package/plugin/node_modules/globalthis/auto.js +3 -0
  544. package/plugin/node_modules/globalthis/implementation.browser.js +11 -0
  545. package/plugin/node_modules/globalthis/implementation.js +3 -0
  546. package/plugin/node_modules/globalthis/index.js +19 -0
  547. package/plugin/node_modules/globalthis/package.json +99 -0
  548. package/plugin/node_modules/globalthis/polyfill.js +10 -0
  549. package/plugin/node_modules/globalthis/shim.js +29 -0
  550. package/plugin/node_modules/gopd/.eslintrc +16 -0
  551. package/plugin/node_modules/gopd/CHANGELOG.md +45 -0
  552. package/plugin/node_modules/gopd/LICENSE +21 -0
  553. package/plugin/node_modules/gopd/README.md +40 -0
  554. package/plugin/node_modules/gopd/gOPD.d.ts +1 -0
  555. package/plugin/node_modules/gopd/gOPD.js +4 -0
  556. package/plugin/node_modules/gopd/index.d.ts +5 -0
  557. package/plugin/node_modules/gopd/index.js +15 -0
  558. package/plugin/node_modules/gopd/package.json +77 -0
  559. package/plugin/node_modules/gopd/tsconfig.json +9 -0
  560. package/plugin/node_modules/guid-typescript/README.md +39 -0
  561. package/plugin/node_modules/guid-typescript/package.json +37 -0
  562. package/plugin/node_modules/has-property-descriptors/.eslintrc +13 -0
  563. package/plugin/node_modules/has-property-descriptors/.nycrc +9 -0
  564. package/plugin/node_modules/has-property-descriptors/CHANGELOG.md +35 -0
  565. package/plugin/node_modules/has-property-descriptors/LICENSE +21 -0
  566. package/plugin/node_modules/has-property-descriptors/README.md +43 -0
  567. package/plugin/node_modules/has-property-descriptors/index.js +22 -0
  568. package/plugin/node_modules/has-property-descriptors/package.json +77 -0
  569. package/plugin/node_modules/has-symbols/.eslintrc +11 -0
  570. package/plugin/node_modules/has-symbols/.nycrc +9 -0
  571. package/plugin/node_modules/has-symbols/CHANGELOG.md +91 -0
  572. package/plugin/node_modules/has-symbols/LICENSE +21 -0
  573. package/plugin/node_modules/has-symbols/README.md +46 -0
  574. package/plugin/node_modules/has-symbols/index.d.ts +3 -0
  575. package/plugin/node_modules/has-symbols/index.js +14 -0
  576. package/plugin/node_modules/has-symbols/package.json +111 -0
  577. package/plugin/node_modules/has-symbols/shams.d.ts +3 -0
  578. package/plugin/node_modules/has-symbols/shams.js +45 -0
  579. package/plugin/node_modules/has-symbols/tsconfig.json +10 -0
  580. package/plugin/node_modules/hasown/.eslintrc +5 -0
  581. package/plugin/node_modules/hasown/.nycrc +13 -0
  582. package/plugin/node_modules/hasown/CHANGELOG.md +40 -0
  583. package/plugin/node_modules/hasown/LICENSE +21 -0
  584. package/plugin/node_modules/hasown/README.md +40 -0
  585. package/plugin/node_modules/hasown/index.d.ts +3 -0
  586. package/plugin/node_modules/hasown/index.js +8 -0
  587. package/plugin/node_modules/hasown/package.json +92 -0
  588. package/plugin/node_modules/hasown/tsconfig.json +6 -0
  589. package/plugin/node_modules/hono/LICENSE +21 -0
  590. package/plugin/node_modules/hono/README.md +85 -0
  591. package/plugin/node_modules/hono/dist/compose.js +46 -0
  592. package/plugin/node_modules/hono/dist/context.js +412 -0
  593. package/plugin/node_modules/hono/dist/hono-base.js +378 -0
  594. package/plugin/node_modules/hono/dist/hono.js +21 -0
  595. package/plugin/node_modules/hono/dist/http-exception.js +35 -0
  596. package/plugin/node_modules/hono/dist/index.js +5 -0
  597. package/plugin/node_modules/hono/dist/request.js +301 -0
  598. package/plugin/node_modules/hono/dist/router.js +14 -0
  599. package/plugin/node_modules/hono/dist/types.js +6 -0
  600. package/plugin/node_modules/hono/package.json +691 -0
  601. package/plugin/node_modules/http-errors/HISTORY.md +186 -0
  602. package/plugin/node_modules/http-errors/LICENSE +23 -0
  603. package/plugin/node_modules/http-errors/README.md +169 -0
  604. package/plugin/node_modules/http-errors/index.js +290 -0
  605. package/plugin/node_modules/http-errors/package.json +54 -0
  606. package/plugin/node_modules/iconv-lite/LICENSE +21 -0
  607. package/plugin/node_modules/iconv-lite/README.md +138 -0
  608. package/plugin/node_modules/iconv-lite/package.json +70 -0
  609. package/plugin/node_modules/ieee754/LICENSE +11 -0
  610. package/plugin/node_modules/ieee754/README.md +51 -0
  611. package/plugin/node_modules/ieee754/index.d.ts +10 -0
  612. package/plugin/node_modules/ieee754/index.js +85 -0
  613. package/plugin/node_modules/ieee754/package.json +52 -0
  614. package/plugin/node_modules/inherits/LICENSE +16 -0
  615. package/plugin/node_modules/inherits/README.md +42 -0
  616. package/plugin/node_modules/inherits/inherits.js +9 -0
  617. package/plugin/node_modules/inherits/inherits_browser.js +27 -0
  618. package/plugin/node_modules/inherits/package.json +29 -0
  619. package/plugin/node_modules/ini/LICENSE +15 -0
  620. package/plugin/node_modules/ini/README.md +102 -0
  621. package/plugin/node_modules/ini/ini.js +206 -0
  622. package/plugin/node_modules/ini/package.json +33 -0
  623. package/plugin/node_modules/ip-address/LICENSE +19 -0
  624. package/plugin/node_modules/ip-address/README.md +105 -0
  625. package/plugin/node_modules/ip-address/package.json +78 -0
  626. package/plugin/node_modules/ipaddr.js/LICENSE +19 -0
  627. package/plugin/node_modules/ipaddr.js/README.md +233 -0
  628. package/plugin/node_modules/ipaddr.js/ipaddr.min.js +1 -0
  629. package/plugin/node_modules/ipaddr.js/package.json +35 -0
  630. package/plugin/node_modules/is-promise/LICENSE +19 -0
  631. package/plugin/node_modules/is-promise/index.d.ts +2 -0
  632. package/plugin/node_modules/is-promise/index.js +6 -0
  633. package/plugin/node_modules/is-promise/index.mjs +3 -0
  634. package/plugin/node_modules/is-promise/package.json +30 -0
  635. package/plugin/node_modules/is-promise/readme.md +33 -0
  636. package/plugin/node_modules/isexe/LICENSE +15 -0
  637. package/plugin/node_modules/isexe/README.md +51 -0
  638. package/plugin/node_modules/isexe/index.js +57 -0
  639. package/plugin/node_modules/isexe/mode.js +41 -0
  640. package/plugin/node_modules/isexe/package.json +31 -0
  641. package/plugin/node_modules/isexe/windows.js +42 -0
  642. package/plugin/node_modules/jose/LICENSE.md +21 -0
  643. package/plugin/node_modules/jose/README.md +153 -0
  644. package/plugin/node_modules/jose/package.json +200 -0
  645. package/plugin/node_modules/json-schema-traverse/.eslintrc.yml +27 -0
  646. package/plugin/node_modules/json-schema-traverse/LICENSE +21 -0
  647. package/plugin/node_modules/json-schema-traverse/README.md +95 -0
  648. package/plugin/node_modules/json-schema-traverse/index.d.ts +40 -0
  649. package/plugin/node_modules/json-schema-traverse/index.js +93 -0
  650. package/plugin/node_modules/json-schema-traverse/package.json +43 -0
  651. package/plugin/node_modules/json-schema-typed/LICENSE.md +57 -0
  652. package/plugin/node_modules/json-schema-typed/README.md +108 -0
  653. package/plugin/node_modules/json-schema-typed/draft_07.d.ts +882 -0
  654. package/plugin/node_modules/json-schema-typed/draft_07.js +328 -0
  655. package/plugin/node_modules/json-schema-typed/draft_2019_09.d.ts +1247 -0
  656. package/plugin/node_modules/json-schema-typed/draft_2019_09.js +349 -0
  657. package/plugin/node_modules/json-schema-typed/draft_2020_12.d.ts +1239 -0
  658. package/plugin/node_modules/json-schema-typed/draft_2020_12.js +352 -0
  659. package/plugin/node_modules/json-schema-typed/package.json +44 -0
  660. package/plugin/node_modules/json-stringify-safe/CHANGELOG.md +14 -0
  661. package/plugin/node_modules/json-stringify-safe/LICENSE +15 -0
  662. package/plugin/node_modules/json-stringify-safe/Makefile +35 -0
  663. package/plugin/node_modules/json-stringify-safe/README.md +52 -0
  664. package/plugin/node_modules/json-stringify-safe/package.json +31 -0
  665. package/plugin/node_modules/json-stringify-safe/stringify.js +27 -0
  666. package/plugin/node_modules/long/LICENSE +202 -0
  667. package/plugin/node_modules/long/README.md +286 -0
  668. package/plugin/node_modules/long/index.d.ts +2 -0
  669. package/plugin/node_modules/long/index.js +1581 -0
  670. package/plugin/node_modules/long/package.json +58 -0
  671. package/plugin/node_modules/long/types.d.ts +474 -0
  672. package/plugin/node_modules/matcher/index.d.ts +85 -0
  673. package/plugin/node_modules/matcher/index.js +77 -0
  674. package/plugin/node_modules/matcher/license +9 -0
  675. package/plugin/node_modules/matcher/package.json +54 -0
  676. package/plugin/node_modules/matcher/readme.md +120 -0
  677. package/plugin/node_modules/math-intrinsics/.eslintrc +16 -0
  678. package/plugin/node_modules/math-intrinsics/CHANGELOG.md +24 -0
  679. package/plugin/node_modules/math-intrinsics/LICENSE +21 -0
  680. package/plugin/node_modules/math-intrinsics/README.md +50 -0
  681. package/plugin/node_modules/math-intrinsics/abs.d.ts +1 -0
  682. package/plugin/node_modules/math-intrinsics/abs.js +4 -0
  683. package/plugin/node_modules/math-intrinsics/floor.d.ts +1 -0
  684. package/plugin/node_modules/math-intrinsics/floor.js +4 -0
  685. package/plugin/node_modules/math-intrinsics/isFinite.d.ts +3 -0
  686. package/plugin/node_modules/math-intrinsics/isFinite.js +12 -0
  687. package/plugin/node_modules/math-intrinsics/isInteger.d.ts +3 -0
  688. package/plugin/node_modules/math-intrinsics/isInteger.js +16 -0
  689. package/plugin/node_modules/math-intrinsics/isNaN.d.ts +1 -0
  690. package/plugin/node_modules/math-intrinsics/isNaN.js +6 -0
  691. package/plugin/node_modules/math-intrinsics/isNegativeZero.d.ts +3 -0
  692. package/plugin/node_modules/math-intrinsics/isNegativeZero.js +6 -0
  693. package/plugin/node_modules/math-intrinsics/max.d.ts +1 -0
  694. package/plugin/node_modules/math-intrinsics/max.js +4 -0
  695. package/plugin/node_modules/math-intrinsics/min.d.ts +1 -0
  696. package/plugin/node_modules/math-intrinsics/min.js +4 -0
  697. package/plugin/node_modules/math-intrinsics/mod.d.ts +3 -0
  698. package/plugin/node_modules/math-intrinsics/mod.js +9 -0
  699. package/plugin/node_modules/math-intrinsics/package.json +86 -0
  700. package/plugin/node_modules/math-intrinsics/pow.d.ts +1 -0
  701. package/plugin/node_modules/math-intrinsics/pow.js +4 -0
  702. package/plugin/node_modules/math-intrinsics/round.d.ts +1 -0
  703. package/plugin/node_modules/math-intrinsics/round.js +4 -0
  704. package/plugin/node_modules/math-intrinsics/sign.d.ts +3 -0
  705. package/plugin/node_modules/math-intrinsics/sign.js +11 -0
  706. package/plugin/node_modules/math-intrinsics/tsconfig.json +3 -0
  707. package/plugin/node_modules/media-typer/HISTORY.md +50 -0
  708. package/plugin/node_modules/media-typer/LICENSE +22 -0
  709. package/plugin/node_modules/media-typer/README.md +93 -0
  710. package/plugin/node_modules/media-typer/index.js +143 -0
  711. package/plugin/node_modules/media-typer/package.json +33 -0
  712. package/plugin/node_modules/merge-descriptors/index.d.ts +11 -0
  713. package/plugin/node_modules/merge-descriptors/index.js +26 -0
  714. package/plugin/node_modules/merge-descriptors/license +11 -0
  715. package/plugin/node_modules/merge-descriptors/package.json +50 -0
  716. package/plugin/node_modules/merge-descriptors/readme.md +55 -0
  717. package/plugin/node_modules/mime-db/HISTORY.md +541 -0
  718. package/plugin/node_modules/mime-db/LICENSE +23 -0
  719. package/plugin/node_modules/mime-db/README.md +109 -0
  720. package/plugin/node_modules/mime-db/db.json +9342 -0
  721. package/plugin/node_modules/mime-db/index.js +12 -0
  722. package/plugin/node_modules/mime-db/package.json +56 -0
  723. package/plugin/node_modules/mime-types/HISTORY.md +428 -0
  724. package/plugin/node_modules/mime-types/LICENSE +23 -0
  725. package/plugin/node_modules/mime-types/README.md +126 -0
  726. package/plugin/node_modules/mime-types/index.js +211 -0
  727. package/plugin/node_modules/mime-types/mimeScore.js +57 -0
  728. package/plugin/node_modules/mime-types/package.json +49 -0
  729. package/plugin/node_modules/mimic-response/index.d.ts +17 -0
  730. package/plugin/node_modules/mimic-response/index.js +77 -0
  731. package/plugin/node_modules/mimic-response/license +9 -0
  732. package/plugin/node_modules/mimic-response/package.json +42 -0
  733. package/plugin/node_modules/mimic-response/readme.md +78 -0
  734. package/plugin/node_modules/minimist/.eslintrc +29 -0
  735. package/plugin/node_modules/minimist/.nycrc +14 -0
  736. package/plugin/node_modules/minimist/CHANGELOG.md +298 -0
  737. package/plugin/node_modules/minimist/LICENSE +18 -0
  738. package/plugin/node_modules/minimist/README.md +121 -0
  739. package/plugin/node_modules/minimist/index.js +263 -0
  740. package/plugin/node_modules/minimist/package.json +75 -0
  741. package/plugin/node_modules/minipass/LICENSE.md +55 -0
  742. package/plugin/node_modules/minipass/README.md +825 -0
  743. package/plugin/node_modules/minipass/package.json +77 -0
  744. package/plugin/node_modules/minizlib/LICENSE +26 -0
  745. package/plugin/node_modules/minizlib/README.md +64 -0
  746. package/plugin/node_modules/minizlib/package.json +80 -0
  747. package/plugin/node_modules/mkdirp-classic/LICENSE +21 -0
  748. package/plugin/node_modules/mkdirp-classic/README.md +18 -0
  749. package/plugin/node_modules/mkdirp-classic/index.js +98 -0
  750. package/plugin/node_modules/mkdirp-classic/package.json +18 -0
  751. package/plugin/node_modules/ms/index.js +162 -0
  752. package/plugin/node_modules/ms/license.md +21 -0
  753. package/plugin/node_modules/ms/package.json +38 -0
  754. package/plugin/node_modules/ms/readme.md +59 -0
  755. package/plugin/node_modules/napi-build-utils/LICENSE +21 -0
  756. package/plugin/node_modules/napi-build-utils/README.md +52 -0
  757. package/plugin/node_modules/napi-build-utils/index.js +214 -0
  758. package/plugin/node_modules/napi-build-utils/index.md +0 -0
  759. package/plugin/node_modules/napi-build-utils/package.json +42 -0
  760. package/plugin/node_modules/negotiator/HISTORY.md +114 -0
  761. package/plugin/node_modules/negotiator/LICENSE +24 -0
  762. package/plugin/node_modules/negotiator/README.md +212 -0
  763. package/plugin/node_modules/negotiator/index.js +83 -0
  764. package/plugin/node_modules/negotiator/package.json +43 -0
  765. package/plugin/node_modules/node-abi/LICENSE +21 -0
  766. package/plugin/node_modules/node-abi/README.md +54 -0
  767. package/plugin/node_modules/node-abi/abi_registry.json +432 -0
  768. package/plugin/node_modules/node-abi/index.js +179 -0
  769. package/plugin/node_modules/node-abi/package.json +45 -0
  770. package/plugin/node_modules/object-assign/index.js +90 -0
  771. package/plugin/node_modules/object-assign/license +21 -0
  772. package/plugin/node_modules/object-assign/package.json +42 -0
  773. package/plugin/node_modules/object-assign/readme.md +61 -0
  774. package/plugin/node_modules/object-inspect/.eslintrc +53 -0
  775. package/plugin/node_modules/object-inspect/.nycrc +13 -0
  776. package/plugin/node_modules/object-inspect/CHANGELOG.md +424 -0
  777. package/plugin/node_modules/object-inspect/LICENSE +21 -0
  778. package/plugin/node_modules/object-inspect/index.js +544 -0
  779. package/plugin/node_modules/object-inspect/package-support.json +20 -0
  780. package/plugin/node_modules/object-inspect/package.json +105 -0
  781. package/plugin/node_modules/object-inspect/readme.markdown +84 -0
  782. package/plugin/node_modules/object-inspect/test-core-js.js +26 -0
  783. package/plugin/node_modules/object-inspect/util.inspect.js +1 -0
  784. package/plugin/node_modules/object-keys/.editorconfig +13 -0
  785. package/plugin/node_modules/object-keys/.eslintrc +17 -0
  786. package/plugin/node_modules/object-keys/.travis.yml +277 -0
  787. package/plugin/node_modules/object-keys/CHANGELOG.md +232 -0
  788. package/plugin/node_modules/object-keys/LICENSE +21 -0
  789. package/plugin/node_modules/object-keys/README.md +76 -0
  790. package/plugin/node_modules/object-keys/implementation.js +122 -0
  791. package/plugin/node_modules/object-keys/index.js +32 -0
  792. package/plugin/node_modules/object-keys/isArguments.js +17 -0
  793. package/plugin/node_modules/object-keys/package.json +88 -0
  794. package/plugin/node_modules/on-finished/HISTORY.md +98 -0
  795. package/plugin/node_modules/on-finished/LICENSE +23 -0
  796. package/plugin/node_modules/on-finished/README.md +162 -0
  797. package/plugin/node_modules/on-finished/index.js +234 -0
  798. package/plugin/node_modules/on-finished/package.json +39 -0
  799. package/plugin/node_modules/once/LICENSE +15 -0
  800. package/plugin/node_modules/once/README.md +79 -0
  801. package/plugin/node_modules/once/once.js +42 -0
  802. package/plugin/node_modules/once/package.json +33 -0
  803. package/plugin/node_modules/onnxruntime-common/README.md +13 -0
  804. package/plugin/node_modules/onnxruntime-common/package.json +35 -0
  805. package/plugin/node_modules/onnxruntime-node/README.md +51 -0
  806. package/plugin/node_modules/onnxruntime-node/dist/backend.d.ts +9 -0
  807. package/plugin/node_modules/onnxruntime-node/dist/backend.js +79 -0
  808. package/plugin/node_modules/onnxruntime-node/dist/backend.js.map +1 -0
  809. package/plugin/node_modules/onnxruntime-node/dist/binding.d.ts +40 -0
  810. package/plugin/node_modules/onnxruntime-node/dist/binding.js +41 -0
  811. package/plugin/node_modules/onnxruntime-node/dist/binding.js.map +1 -0
  812. package/plugin/node_modules/onnxruntime-node/dist/index.d.ts +2 -0
  813. package/plugin/node_modules/onnxruntime-node/dist/index.js +31 -0
  814. package/plugin/node_modules/onnxruntime-node/dist/index.js.map +1 -0
  815. package/plugin/node_modules/onnxruntime-node/dist/version.d.ts +1 -0
  816. package/plugin/node_modules/onnxruntime-node/dist/version.js +9 -0
  817. package/plugin/node_modules/onnxruntime-node/dist/version.js.map +1 -0
  818. package/plugin/node_modules/onnxruntime-node/lib/backend.ts +81 -0
  819. package/plugin/node_modules/onnxruntime-node/lib/binding.ts +84 -0
  820. package/plugin/node_modules/onnxruntime-node/lib/index.ts +15 -0
  821. package/plugin/node_modules/onnxruntime-node/lib/version.ts +7 -0
  822. package/plugin/node_modules/onnxruntime-node/package.json +56 -0
  823. package/plugin/node_modules/onnxruntime-node/script/build.js +148 -0
  824. package/plugin/node_modules/onnxruntime-node/script/build.ts +130 -0
  825. package/plugin/node_modules/onnxruntime-node/script/install.js +199 -0
  826. package/plugin/node_modules/onnxruntime-node/script/prepack.js +42 -0
  827. package/plugin/node_modules/onnxruntime-node/script/prepack.ts +20 -0
  828. package/plugin/node_modules/onnxruntime-web/README.md +74 -0
  829. package/plugin/node_modules/onnxruntime-web/__commit.txt +1 -0
  830. package/plugin/node_modules/onnxruntime-web/package.json +109 -0
  831. package/plugin/node_modules/onnxruntime-web/types.d.ts +22 -0
  832. package/plugin/node_modules/parseurl/HISTORY.md +58 -0
  833. package/plugin/node_modules/parseurl/LICENSE +24 -0
  834. package/plugin/node_modules/parseurl/README.md +133 -0
  835. package/plugin/node_modules/parseurl/index.js +158 -0
  836. package/plugin/node_modules/parseurl/package.json +40 -0
  837. package/plugin/node_modules/path-key/index.d.ts +40 -0
  838. package/plugin/node_modules/path-key/index.js +16 -0
  839. package/plugin/node_modules/path-key/license +9 -0
  840. package/plugin/node_modules/path-key/package.json +39 -0
  841. package/plugin/node_modules/path-key/readme.md +61 -0
  842. package/plugin/node_modules/path-to-regexp/LICENSE +21 -0
  843. package/plugin/node_modules/path-to-regexp/Readme.md +224 -0
  844. package/plugin/node_modules/path-to-regexp/package.json +64 -0
  845. package/plugin/node_modules/pkce-challenge/CHANGELOG.md +114 -0
  846. package/plugin/node_modules/pkce-challenge/LICENSE +21 -0
  847. package/plugin/node_modules/pkce-challenge/README.md +55 -0
  848. package/plugin/node_modules/pkce-challenge/package.json +59 -0
  849. package/plugin/node_modules/platform/LICENSE +21 -0
  850. package/plugin/node_modules/platform/README.md +76 -0
  851. package/plugin/node_modules/platform/package.json +30 -0
  852. package/plugin/node_modules/platform/platform.js +1260 -0
  853. package/plugin/node_modules/prebuild-install/CHANGELOG.md +131 -0
  854. package/plugin/node_modules/prebuild-install/CONTRIBUTING.md +6 -0
  855. package/plugin/node_modules/prebuild-install/LICENSE +21 -0
  856. package/plugin/node_modules/prebuild-install/README.md +163 -0
  857. package/plugin/node_modules/prebuild-install/asset.js +44 -0
  858. package/plugin/node_modules/prebuild-install/bin.js +78 -0
  859. package/plugin/node_modules/prebuild-install/download.js +142 -0
  860. package/plugin/node_modules/prebuild-install/error.js +14 -0
  861. package/plugin/node_modules/prebuild-install/help.txt +16 -0
  862. package/plugin/node_modules/prebuild-install/index.js +1 -0
  863. package/plugin/node_modules/prebuild-install/log.js +33 -0
  864. package/plugin/node_modules/prebuild-install/package.json +67 -0
  865. package/plugin/node_modules/prebuild-install/proxy.js +35 -0
  866. package/plugin/node_modules/prebuild-install/rc.js +64 -0
  867. package/plugin/node_modules/prebuild-install/util.js +143 -0
  868. package/plugin/node_modules/protobufjs/LICENSE +39 -0
  869. package/plugin/node_modules/protobufjs/README.md +727 -0
  870. package/plugin/node_modules/protobufjs/index.d.ts +2799 -0
  871. package/plugin/node_modules/protobufjs/index.js +4 -0
  872. package/plugin/node_modules/protobufjs/light.d.ts +2 -0
  873. package/plugin/node_modules/protobufjs/light.js +4 -0
  874. package/plugin/node_modules/protobufjs/minimal.d.ts +2 -0
  875. package/plugin/node_modules/protobufjs/minimal.js +4 -0
  876. package/plugin/node_modules/protobufjs/package.json +114 -0
  877. package/plugin/node_modules/protobufjs/tsconfig.json +8 -0
  878. package/plugin/node_modules/proxy-addr/HISTORY.md +161 -0
  879. package/plugin/node_modules/proxy-addr/LICENSE +22 -0
  880. package/plugin/node_modules/proxy-addr/README.md +139 -0
  881. package/plugin/node_modules/proxy-addr/index.js +327 -0
  882. package/plugin/node_modules/proxy-addr/package.json +47 -0
  883. package/plugin/node_modules/pump/.travis.yml +5 -0
  884. package/plugin/node_modules/pump/LICENSE +21 -0
  885. package/plugin/node_modules/pump/README.md +74 -0
  886. package/plugin/node_modules/pump/SECURITY.md +5 -0
  887. package/plugin/node_modules/pump/index.js +86 -0
  888. package/plugin/node_modules/pump/package.json +24 -0
  889. package/plugin/node_modules/pump/test-browser.js +66 -0
  890. package/plugin/node_modules/pump/test-node.js +53 -0
  891. package/plugin/node_modules/qs/.editorconfig +46 -0
  892. package/plugin/node_modules/qs/.nycrc +13 -0
  893. package/plugin/node_modules/qs/CHANGELOG.md +806 -0
  894. package/plugin/node_modules/qs/LICENSE.md +29 -0
  895. package/plugin/node_modules/qs/README.md +758 -0
  896. package/plugin/node_modules/qs/eslint.config.mjs +56 -0
  897. package/plugin/node_modules/qs/package.json +94 -0
  898. package/plugin/node_modules/range-parser/HISTORY.md +56 -0
  899. package/plugin/node_modules/range-parser/LICENSE +23 -0
  900. package/plugin/node_modules/range-parser/README.md +84 -0
  901. package/plugin/node_modules/range-parser/index.js +162 -0
  902. package/plugin/node_modules/range-parser/package.json +44 -0
  903. package/plugin/node_modules/raw-body/LICENSE +22 -0
  904. package/plugin/node_modules/raw-body/README.md +223 -0
  905. package/plugin/node_modules/raw-body/index.d.ts +85 -0
  906. package/plugin/node_modules/raw-body/index.js +336 -0
  907. package/plugin/node_modules/raw-body/package.json +46 -0
  908. package/plugin/node_modules/rc/LICENSE.APACHE2 +15 -0
  909. package/plugin/node_modules/rc/LICENSE.BSD +26 -0
  910. package/plugin/node_modules/rc/LICENSE.MIT +24 -0
  911. package/plugin/node_modules/rc/README.md +227 -0
  912. package/plugin/node_modules/rc/browser.js +7 -0
  913. package/plugin/node_modules/rc/cli.js +4 -0
  914. package/plugin/node_modules/rc/index.js +53 -0
  915. package/plugin/node_modules/rc/package.json +29 -0
  916. package/plugin/node_modules/readable-stream/CONTRIBUTING.md +38 -0
  917. package/plugin/node_modules/readable-stream/GOVERNANCE.md +136 -0
  918. package/plugin/node_modules/readable-stream/LICENSE +47 -0
  919. package/plugin/node_modules/readable-stream/README.md +106 -0
  920. package/plugin/node_modules/readable-stream/errors-browser.js +127 -0
  921. package/plugin/node_modules/readable-stream/errors.js +116 -0
  922. package/plugin/node_modules/readable-stream/experimentalWarning.js +17 -0
  923. package/plugin/node_modules/readable-stream/package.json +68 -0
  924. package/plugin/node_modules/readable-stream/readable-browser.js +9 -0
  925. package/plugin/node_modules/readable-stream/readable.js +16 -0
  926. package/plugin/node_modules/require-from-string/index.js +34 -0
  927. package/plugin/node_modules/require-from-string/license +21 -0
  928. package/plugin/node_modules/require-from-string/package.json +28 -0
  929. package/plugin/node_modules/require-from-string/readme.md +56 -0
  930. package/plugin/node_modules/roarr/LICENSE +24 -0
  931. package/plugin/node_modules/roarr/README.md +689 -0
  932. package/plugin/node_modules/roarr/package.json +93 -0
  933. package/plugin/node_modules/router/HISTORY.md +228 -0
  934. package/plugin/node_modules/router/LICENSE +23 -0
  935. package/plugin/node_modules/router/README.md +416 -0
  936. package/plugin/node_modules/router/index.js +748 -0
  937. package/plugin/node_modules/router/package.json +44 -0
  938. package/plugin/node_modules/safe-buffer/LICENSE +21 -0
  939. package/plugin/node_modules/safe-buffer/README.md +584 -0
  940. package/plugin/node_modules/safe-buffer/index.d.ts +187 -0
  941. package/plugin/node_modules/safe-buffer/index.js +65 -0
  942. package/plugin/node_modules/safe-buffer/package.json +51 -0
  943. package/plugin/node_modules/safer-buffer/LICENSE +21 -0
  944. package/plugin/node_modules/safer-buffer/Porting-Buffer.md +268 -0
  945. package/plugin/node_modules/safer-buffer/Readme.md +156 -0
  946. package/plugin/node_modules/safer-buffer/dangerous.js +58 -0
  947. package/plugin/node_modules/safer-buffer/package.json +34 -0
  948. package/plugin/node_modules/safer-buffer/safer.js +77 -0
  949. package/plugin/node_modules/safer-buffer/tests.js +406 -0
  950. package/plugin/node_modules/semver/LICENSE +15 -0
  951. package/plugin/node_modules/semver/README.md +665 -0
  952. package/plugin/node_modules/semver/index.js +91 -0
  953. package/plugin/node_modules/semver/package.json +78 -0
  954. package/plugin/node_modules/semver/preload.js +4 -0
  955. package/plugin/node_modules/semver/range.bnf +16 -0
  956. package/plugin/node_modules/semver-compare/.travis.yml +6 -0
  957. package/plugin/node_modules/semver-compare/LICENSE +18 -0
  958. package/plugin/node_modules/semver-compare/index.js +13 -0
  959. package/plugin/node_modules/semver-compare/package.json +31 -0
  960. package/plugin/node_modules/semver-compare/readme.markdown +77 -0
  961. package/plugin/node_modules/send/LICENSE +23 -0
  962. package/plugin/node_modules/send/README.md +317 -0
  963. package/plugin/node_modules/send/index.js +997 -0
  964. package/plugin/node_modules/send/package.json +63 -0
  965. package/plugin/node_modules/serialize-error/index.d.ts +58 -0
  966. package/plugin/node_modules/serialize-error/index.js +101 -0
  967. package/plugin/node_modules/serialize-error/license +9 -0
  968. package/plugin/node_modules/serialize-error/package.json +41 -0
  969. package/plugin/node_modules/serialize-error/readme.md +55 -0
  970. package/plugin/node_modules/serve-static/LICENSE +25 -0
  971. package/plugin/node_modules/serve-static/README.md +253 -0
  972. package/plugin/node_modules/serve-static/index.js +208 -0
  973. package/plugin/node_modules/serve-static/package.json +44 -0
  974. package/plugin/node_modules/setprototypeof/LICENSE +13 -0
  975. package/plugin/node_modules/setprototypeof/README.md +31 -0
  976. package/plugin/node_modules/setprototypeof/index.d.ts +2 -0
  977. package/plugin/node_modules/setprototypeof/index.js +17 -0
  978. package/plugin/node_modules/setprototypeof/package.json +38 -0
  979. package/plugin/node_modules/sharp/LICENSE +191 -0
  980. package/plugin/node_modules/sharp/README.md +118 -0
  981. package/plugin/node_modules/sharp/install/build.js +38 -0
  982. package/plugin/node_modules/sharp/install/check.js +14 -0
  983. package/plugin/node_modules/sharp/lib/channel.js +177 -0
  984. package/plugin/node_modules/sharp/lib/colour.js +195 -0
  985. package/plugin/node_modules/sharp/lib/composite.js +212 -0
  986. package/plugin/node_modules/sharp/lib/constructor.js +499 -0
  987. package/plugin/node_modules/sharp/lib/index.d.ts +1971 -0
  988. package/plugin/node_modules/sharp/lib/index.js +16 -0
  989. package/plugin/node_modules/sharp/lib/input.js +809 -0
  990. package/plugin/node_modules/sharp/lib/is.js +143 -0
  991. package/plugin/node_modules/sharp/lib/libvips.js +207 -0
  992. package/plugin/node_modules/sharp/lib/operation.js +1016 -0
  993. package/plugin/node_modules/sharp/lib/output.js +1666 -0
  994. package/plugin/node_modules/sharp/lib/resize.js +595 -0
  995. package/plugin/node_modules/sharp/lib/sharp.js +121 -0
  996. package/plugin/node_modules/sharp/lib/utility.js +291 -0
  997. package/plugin/node_modules/sharp/package.json +202 -0
  998. package/plugin/node_modules/sharp/src/binding.gyp +298 -0
  999. package/plugin/node_modules/sharp/src/common.cc +1130 -0
  1000. package/plugin/node_modules/sharp/src/common.h +402 -0
  1001. package/plugin/node_modules/sharp/src/metadata.cc +346 -0
  1002. package/plugin/node_modules/sharp/src/metadata.h +90 -0
  1003. package/plugin/node_modules/sharp/src/operations.cc +499 -0
  1004. package/plugin/node_modules/sharp/src/operations.h +137 -0
  1005. package/plugin/node_modules/sharp/src/pipeline.cc +1814 -0
  1006. package/plugin/node_modules/sharp/src/pipeline.h +408 -0
  1007. package/plugin/node_modules/sharp/src/sharp.cc +43 -0
  1008. package/plugin/node_modules/sharp/src/stats.cc +186 -0
  1009. package/plugin/node_modules/sharp/src/stats.h +62 -0
  1010. package/plugin/node_modules/sharp/src/utilities.cc +288 -0
  1011. package/plugin/node_modules/sharp/src/utilities.h +22 -0
  1012. package/plugin/node_modules/shebang-command/index.js +19 -0
  1013. package/plugin/node_modules/shebang-command/license +9 -0
  1014. package/plugin/node_modules/shebang-command/package.json +34 -0
  1015. package/plugin/node_modules/shebang-command/readme.md +34 -0
  1016. package/plugin/node_modules/shebang-regex/index.d.ts +22 -0
  1017. package/plugin/node_modules/shebang-regex/index.js +2 -0
  1018. package/plugin/node_modules/shebang-regex/license +9 -0
  1019. package/plugin/node_modules/shebang-regex/package.json +35 -0
  1020. package/plugin/node_modules/shebang-regex/readme.md +33 -0
  1021. package/plugin/node_modules/side-channel/.editorconfig +9 -0
  1022. package/plugin/node_modules/side-channel/.eslintrc +12 -0
  1023. package/plugin/node_modules/side-channel/.nycrc +13 -0
  1024. package/plugin/node_modules/side-channel/CHANGELOG.md +110 -0
  1025. package/plugin/node_modules/side-channel/LICENSE +21 -0
  1026. package/plugin/node_modules/side-channel/README.md +61 -0
  1027. package/plugin/node_modules/side-channel/index.d.ts +14 -0
  1028. package/plugin/node_modules/side-channel/index.js +43 -0
  1029. package/plugin/node_modules/side-channel/package.json +85 -0
  1030. package/plugin/node_modules/side-channel/tsconfig.json +9 -0
  1031. package/plugin/node_modules/side-channel-list/.editorconfig +9 -0
  1032. package/plugin/node_modules/side-channel-list/.eslintrc +11 -0
  1033. package/plugin/node_modules/side-channel-list/.nycrc +13 -0
  1034. package/plugin/node_modules/side-channel-list/CHANGELOG.md +15 -0
  1035. package/plugin/node_modules/side-channel-list/LICENSE +21 -0
  1036. package/plugin/node_modules/side-channel-list/README.md +62 -0
  1037. package/plugin/node_modules/side-channel-list/index.d.ts +13 -0
  1038. package/plugin/node_modules/side-channel-list/index.js +113 -0
  1039. package/plugin/node_modules/side-channel-list/list.d.ts +14 -0
  1040. package/plugin/node_modules/side-channel-list/package.json +77 -0
  1041. package/plugin/node_modules/side-channel-list/tsconfig.json +9 -0
  1042. package/plugin/node_modules/side-channel-map/.editorconfig +9 -0
  1043. package/plugin/node_modules/side-channel-map/.eslintrc +11 -0
  1044. package/plugin/node_modules/side-channel-map/.nycrc +13 -0
  1045. package/plugin/node_modules/side-channel-map/CHANGELOG.md +22 -0
  1046. package/plugin/node_modules/side-channel-map/LICENSE +21 -0
  1047. package/plugin/node_modules/side-channel-map/README.md +62 -0
  1048. package/plugin/node_modules/side-channel-map/index.d.ts +15 -0
  1049. package/plugin/node_modules/side-channel-map/index.js +68 -0
  1050. package/plugin/node_modules/side-channel-map/package.json +80 -0
  1051. package/plugin/node_modules/side-channel-map/tsconfig.json +9 -0
  1052. package/plugin/node_modules/side-channel-weakmap/.editorconfig +9 -0
  1053. package/plugin/node_modules/side-channel-weakmap/.eslintrc +12 -0
  1054. package/plugin/node_modules/side-channel-weakmap/.nycrc +13 -0
  1055. package/plugin/node_modules/side-channel-weakmap/CHANGELOG.md +28 -0
  1056. package/plugin/node_modules/side-channel-weakmap/LICENSE +21 -0
  1057. package/plugin/node_modules/side-channel-weakmap/README.md +62 -0
  1058. package/plugin/node_modules/side-channel-weakmap/index.d.ts +15 -0
  1059. package/plugin/node_modules/side-channel-weakmap/index.js +84 -0
  1060. package/plugin/node_modules/side-channel-weakmap/package.json +87 -0
  1061. package/plugin/node_modules/side-channel-weakmap/tsconfig.json +9 -0
  1062. package/plugin/node_modules/simple-concat/.travis.yml +3 -0
  1063. package/plugin/node_modules/simple-concat/LICENSE +20 -0
  1064. package/plugin/node_modules/simple-concat/README.md +44 -0
  1065. package/plugin/node_modules/simple-concat/index.js +15 -0
  1066. package/plugin/node_modules/simple-concat/package.json +47 -0
  1067. package/plugin/node_modules/simple-get/LICENSE +20 -0
  1068. package/plugin/node_modules/simple-get/README.md +333 -0
  1069. package/plugin/node_modules/simple-get/index.js +108 -0
  1070. package/plugin/node_modules/simple-get/package.json +67 -0
  1071. package/plugin/node_modules/sprintf-js/CONTRIBUTORS.md +26 -0
  1072. package/plugin/node_modules/sprintf-js/LICENSE +24 -0
  1073. package/plugin/node_modules/sprintf-js/README.md +143 -0
  1074. package/plugin/node_modules/sprintf-js/package.json +35 -0
  1075. package/plugin/node_modules/sqlite-vec/README.md +1 -0
  1076. package/plugin/node_modules/sqlite-vec/index.cjs +58 -0
  1077. package/plugin/node_modules/sqlite-vec/index.d.ts +17 -0
  1078. package/plugin/node_modules/sqlite-vec/index.mjs +58 -0
  1079. package/plugin/node_modules/sqlite-vec/package.json +1 -0
  1080. package/plugin/node_modules/sqlite-vec-linux-x64/README.md +1 -0
  1081. package/plugin/node_modules/sqlite-vec-linux-x64/package.json +1 -0
  1082. package/plugin/node_modules/sqlite-vec-linux-x64/vec0.so +0 -0
  1083. package/plugin/node_modules/statuses/HISTORY.md +87 -0
  1084. package/plugin/node_modules/statuses/LICENSE +23 -0
  1085. package/plugin/node_modules/statuses/README.md +139 -0
  1086. package/plugin/node_modules/statuses/codes.json +65 -0
  1087. package/plugin/node_modules/statuses/index.js +146 -0
  1088. package/plugin/node_modules/statuses/package.json +49 -0
  1089. package/plugin/node_modules/string_decoder/LICENSE +48 -0
  1090. package/plugin/node_modules/string_decoder/README.md +47 -0
  1091. package/plugin/node_modules/string_decoder/package.json +34 -0
  1092. package/plugin/node_modules/strip-json-comments/index.js +70 -0
  1093. package/plugin/node_modules/strip-json-comments/license +21 -0
  1094. package/plugin/node_modules/strip-json-comments/package.json +42 -0
  1095. package/plugin/node_modules/strip-json-comments/readme.md +64 -0
  1096. package/plugin/node_modules/tar/LICENSE.md +55 -0
  1097. package/plugin/node_modules/tar/README.md +1145 -0
  1098. package/plugin/node_modules/tar/package.json +292 -0
  1099. package/plugin/node_modules/tar-fs/.travis.yml +6 -0
  1100. package/plugin/node_modules/tar-fs/LICENSE +21 -0
  1101. package/plugin/node_modules/tar-fs/README.md +165 -0
  1102. package/plugin/node_modules/tar-fs/index.js +363 -0
  1103. package/plugin/node_modules/tar-fs/package.json +41 -0
  1104. package/plugin/node_modules/tar-stream/LICENSE +21 -0
  1105. package/plugin/node_modules/tar-stream/README.md +168 -0
  1106. package/plugin/node_modules/tar-stream/extract.js +257 -0
  1107. package/plugin/node_modules/tar-stream/headers.js +295 -0
  1108. package/plugin/node_modules/tar-stream/index.js +2 -0
  1109. package/plugin/node_modules/tar-stream/pack.js +255 -0
  1110. package/plugin/node_modules/tar-stream/package.json +58 -0
  1111. package/plugin/node_modules/tar-stream/sandbox.js +11 -0
  1112. package/plugin/node_modules/toidentifier/HISTORY.md +9 -0
  1113. package/plugin/node_modules/toidentifier/LICENSE +21 -0
  1114. package/plugin/node_modules/toidentifier/README.md +61 -0
  1115. package/plugin/node_modules/toidentifier/index.js +32 -0
  1116. package/plugin/node_modules/toidentifier/package.json +38 -0
  1117. package/plugin/node_modules/tunnel-agent/LICENSE +55 -0
  1118. package/plugin/node_modules/tunnel-agent/README.md +4 -0
  1119. package/plugin/node_modules/tunnel-agent/index.js +244 -0
  1120. package/plugin/node_modules/tunnel-agent/package.json +22 -0
  1121. package/plugin/node_modules/type-fest/index.d.ts +29 -0
  1122. package/plugin/node_modules/type-fest/license +9 -0
  1123. package/plugin/node_modules/type-fest/package.json +45 -0
  1124. package/plugin/node_modules/type-fest/readme.md +642 -0
  1125. package/plugin/node_modules/type-is/HISTORY.md +292 -0
  1126. package/plugin/node_modules/type-is/LICENSE +23 -0
  1127. package/plugin/node_modules/type-is/README.md +198 -0
  1128. package/plugin/node_modules/type-is/index.js +250 -0
  1129. package/plugin/node_modules/type-is/package.json +47 -0
  1130. package/plugin/node_modules/undici-types/LICENSE +21 -0
  1131. package/plugin/node_modules/undici-types/README.md +6 -0
  1132. package/plugin/node_modules/undici-types/agent.d.ts +32 -0
  1133. package/plugin/node_modules/undici-types/api.d.ts +43 -0
  1134. package/plugin/node_modules/undici-types/balanced-pool.d.ts +30 -0
  1135. package/plugin/node_modules/undici-types/cache-interceptor.d.ts +173 -0
  1136. package/plugin/node_modules/undici-types/cache.d.ts +36 -0
  1137. package/plugin/node_modules/undici-types/client-stats.d.ts +15 -0
  1138. package/plugin/node_modules/undici-types/client.d.ts +108 -0
  1139. package/plugin/node_modules/undici-types/connector.d.ts +34 -0
  1140. package/plugin/node_modules/undici-types/content-type.d.ts +21 -0
  1141. package/plugin/node_modules/undici-types/cookies.d.ts +30 -0
  1142. package/plugin/node_modules/undici-types/diagnostics-channel.d.ts +74 -0
  1143. package/plugin/node_modules/undici-types/dispatcher.d.ts +276 -0
  1144. package/plugin/node_modules/undici-types/env-http-proxy-agent.d.ts +22 -0
  1145. package/plugin/node_modules/undici-types/errors.d.ts +161 -0
  1146. package/plugin/node_modules/undici-types/eventsource.d.ts +66 -0
  1147. package/plugin/node_modules/undici-types/fetch.d.ts +211 -0
  1148. package/plugin/node_modules/undici-types/formdata.d.ts +108 -0
  1149. package/plugin/node_modules/undici-types/global-dispatcher.d.ts +9 -0
  1150. package/plugin/node_modules/undici-types/global-origin.d.ts +7 -0
  1151. package/plugin/node_modules/undici-types/h2c-client.d.ts +73 -0
  1152. package/plugin/node_modules/undici-types/handlers.d.ts +15 -0
  1153. package/plugin/node_modules/undici-types/header.d.ts +160 -0
  1154. package/plugin/node_modules/undici-types/index.d.ts +88 -0
  1155. package/plugin/node_modules/undici-types/interceptors.d.ts +73 -0
  1156. package/plugin/node_modules/undici-types/mock-agent.d.ts +68 -0
  1157. package/plugin/node_modules/undici-types/mock-call-history.d.ts +111 -0
  1158. package/plugin/node_modules/undici-types/mock-client.d.ts +27 -0
  1159. package/plugin/node_modules/undici-types/mock-errors.d.ts +12 -0
  1160. package/plugin/node_modules/undici-types/mock-interceptor.d.ts +94 -0
  1161. package/plugin/node_modules/undici-types/mock-pool.d.ts +27 -0
  1162. package/plugin/node_modules/undici-types/package.json +55 -0
  1163. package/plugin/node_modules/undici-types/patch.d.ts +29 -0
  1164. package/plugin/node_modules/undici-types/pool-stats.d.ts +19 -0
  1165. package/plugin/node_modules/undici-types/pool.d.ts +41 -0
  1166. package/plugin/node_modules/undici-types/proxy-agent.d.ts +29 -0
  1167. package/plugin/node_modules/undici-types/readable.d.ts +68 -0
  1168. package/plugin/node_modules/undici-types/retry-agent.d.ts +8 -0
  1169. package/plugin/node_modules/undici-types/retry-handler.d.ts +125 -0
  1170. package/plugin/node_modules/undici-types/round-robin-pool.d.ts +41 -0
  1171. package/plugin/node_modules/undici-types/snapshot-agent.d.ts +109 -0
  1172. package/plugin/node_modules/undici-types/util.d.ts +18 -0
  1173. package/plugin/node_modules/undici-types/utility.d.ts +7 -0
  1174. package/plugin/node_modules/undici-types/webidl.d.ts +341 -0
  1175. package/plugin/node_modules/undici-types/websocket.d.ts +186 -0
  1176. package/plugin/node_modules/unpipe/HISTORY.md +4 -0
  1177. package/plugin/node_modules/unpipe/LICENSE +22 -0
  1178. package/plugin/node_modules/unpipe/README.md +43 -0
  1179. package/plugin/node_modules/unpipe/index.js +69 -0
  1180. package/plugin/node_modules/unpipe/package.json +27 -0
  1181. package/plugin/node_modules/util-deprecate/History.md +16 -0
  1182. package/plugin/node_modules/util-deprecate/LICENSE +24 -0
  1183. package/plugin/node_modules/util-deprecate/README.md +53 -0
  1184. package/plugin/node_modules/util-deprecate/browser.js +67 -0
  1185. package/plugin/node_modules/util-deprecate/node.js +6 -0
  1186. package/plugin/node_modules/util-deprecate/package.json +27 -0
  1187. package/plugin/node_modules/vary/HISTORY.md +39 -0
  1188. package/plugin/node_modules/vary/LICENSE +22 -0
  1189. package/plugin/node_modules/vary/README.md +101 -0
  1190. package/plugin/node_modules/vary/index.js +149 -0
  1191. package/plugin/node_modules/vary/package.json +43 -0
  1192. package/plugin/node_modules/which/CHANGELOG.md +166 -0
  1193. package/plugin/node_modules/which/LICENSE +15 -0
  1194. package/plugin/node_modules/which/README.md +54 -0
  1195. package/plugin/node_modules/which/package.json +43 -0
  1196. package/plugin/node_modules/which/which.js +125 -0
  1197. package/plugin/node_modules/wrappy/LICENSE +15 -0
  1198. package/plugin/node_modules/wrappy/README.md +36 -0
  1199. package/plugin/node_modules/wrappy/package.json +29 -0
  1200. package/plugin/node_modules/wrappy/wrappy.js +33 -0
  1201. package/plugin/node_modules/yallist/LICENSE.md +63 -0
  1202. package/plugin/node_modules/yallist/README.md +205 -0
  1203. package/plugin/node_modules/yallist/package.json +68 -0
  1204. package/plugin/node_modules/zod/LICENSE +21 -0
  1205. package/plugin/node_modules/zod/README.md +208 -0
  1206. package/plugin/node_modules/zod/index.cjs +33 -0
  1207. package/plugin/node_modules/zod/index.d.cts +4 -0
  1208. package/plugin/node_modules/zod/index.d.ts +4 -0
  1209. package/plugin/node_modules/zod/index.js +4 -0
  1210. package/plugin/node_modules/zod/package.json +135 -0
  1211. package/plugin/node_modules/zod-to-json-schema/.prettierrc.json +1 -0
  1212. package/plugin/node_modules/zod-to-json-schema/LICENSE +15 -0
  1213. package/plugin/node_modules/zod-to-json-schema/README.md +390 -0
  1214. package/plugin/node_modules/zod-to-json-schema/changelog.md +82 -0
  1215. package/plugin/node_modules/zod-to-json-schema/contributing.md +9 -0
  1216. package/plugin/node_modules/zod-to-json-schema/createIndex.ts +32 -0
  1217. package/plugin/node_modules/zod-to-json-schema/package.json +78 -0
  1218. package/plugin/node_modules/zod-to-json-schema/postcjs.ts +3 -0
  1219. package/plugin/node_modules/zod-to-json-schema/postesm.ts +3 -0
  1220. package/plugin/package-lock.json +2654 -0
  1221. package/plugin/scripts/dev-sync.sh +26 -8
  1222. package/plugin/scripts/doctor.sh +235 -0
  1223. package/plugin/scripts/ensure-deps.sh +82 -2
  1224. package/plugin/scripts/install.sh +87 -45
  1225. package/plugin/scripts/local-install.sh +112 -84
  1226. package/plugin/scripts/repair.sh +71 -0
  1227. package/plugin/scripts/uninstall.sh +74 -47
  1228. package/plugin/scripts/update.sh +29 -4
  1229. package/plugin/scripts/verify-install.sh +87 -35
  1230. package/plugin/skills/doctor/SKILL.md +16 -0
  1231. package/plugin/dist/tool-registry-D8un_AcG.mjs.map +0 -1
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","names":["DEFAULTS","DEFAULTS","prependNotifications","textResponse","errorResponse","prependNotifications","textResponse","prependNotifications","textResponse","errorResponse","prependNotifications","textResponse","prependNotifications","textResponse","prependNotifications","textResponse","textResponse","errorResponse","prependNotifications","textResponse","prependNotifications","textResponse","errorResponse","fileURLToPath","DEFAULTS","DEFAULTS","jaccardSimilarity","SYSTEM_PROMPT","SYSTEM_PROMPT","SYSTEM_PROMPT","getDb","getProjectHash","fileURLToPath","getProjectHash"],"sources":["../../src/storage/embeddings.ts","../../src/storage/stash-manager.ts","../../src/storage/threshold-store.ts","../../src/mcp/server.ts","../../src/config/cross-access.ts","../../src/mcp/token-budget.ts","../../src/config/tool-verbosity-config.ts","../../src/mcp/tools/recall.ts","../../src/mcp/tools/save-memory.ts","../../src/ingestion/markdown-parser.ts","../../src/ingestion/knowledge-ingester.ts","../../src/mcp/tools/ingest-knowledge.ts","../../src/commands/resume.ts","../../src/mcp/tools/topic-context.ts","../../src/graph/types.ts","../../src/mcp/tools/query-graph.ts","../../src/mcp/tools/graph-stats.ts","../../src/mcp/tools/hygiene.ts","../../src/mcp/tools/status.ts","../../src/mcp/status-cache.ts","../../src/mcp/tools/discover-tools.ts","../../src/mcp/tools/report-tools.ts","../../src/mcp/tools/debug-paths.ts","../../src/mcp/tools/thought-branches.ts","../../src/branches/arc-detector.ts","../../src/config/haiku-config.ts","../../src/intelligence/haiku-client.ts","../../src/branches/branch-classifier-agent.ts","../../src/branches/branch-tracker.ts","../../src/analysis/worker-bridge.ts","../../src/hooks/topic-shift-handler.ts","../../src/intelligence/topic-detector.ts","../../src/intelligence/adaptive-threshold.ts","../../src/intelligence/decision-logger.ts","../../src/config/topic-detection-config.ts","../../src/config/graph-extraction-config.ts","../../src/graph/observation-merger.ts","../../src/graph/fuzzy-dedup.ts","../../src/graph/constraints.ts","../../src/graph/temporal-decay.ts","../../src/graph/curation-agent.ts","../../src/intelligence/haiku-classifier-agent.ts","../../src/intelligence/haiku-entity-agent.ts","../../src/intelligence/haiku-relationship-agent.ts","../../src/graph/write-quality-gate.ts","../../src/web/routes/sse.ts","../../src/intelligence/haiku-processor.ts","../../src/paths/kiss-summary-agent.ts","../../src/paths/path-tracker.ts","../../src/web/routes/api.ts","../../src/web/routes/admin.ts","../../src/web/server.ts","../../src/index.ts"],"sourcesContent":["/**\n * EmbeddingStore for sqlite-vec vec0 table operations.\n *\n * Provides store/search/delete/has/findUnembedded methods against the\n * cosine-distance vec0 table (observation_embeddings). All operations\n * are project-scoped via subquery join on the observations table.\n *\n * Float32Array passes directly to better-sqlite3 for vec0 operations\n * (per research Finding 7 -- no Buffer conversion needed).\n */\n\nimport type BetterSqlite3 from 'better-sqlite3';\n\nimport { debug } from '../shared/debug.js';\n\n/** A single search result with observation ID and cosine distance. */\nexport interface EmbeddingSearchResult {\n observationId: string;\n distance: number;\n}\n\n/**\n * Data layer for vector insert/query against the cosine-distance vec0 table.\n *\n * All methods catch errors internally and return empty/default values for\n * graceful degradation (DQ-03). Uses debug('embed', ...) logging.\n */\nexport class EmbeddingStore {\n private stmtInsert: BetterSqlite3.Statement;\n private stmtSearch: BetterSqlite3.Statement;\n private stmtDelete: BetterSqlite3.Statement;\n private stmtExists: BetterSqlite3.Statement;\n private stmtFindUnembedded: BetterSqlite3.Statement;\n\n constructor(\n private db: BetterSqlite3.Database,\n private projectHash: string,\n ) {\n this.stmtInsert = db.prepare(\n 'INSERT OR REPLACE INTO observation_embeddings(observation_id, embedding) VALUES (?, ?)',\n );\n\n this.stmtSearch = db.prepare(`\n SELECT observation_id, distance\n FROM observation_embeddings\n WHERE embedding MATCH ?\n AND observation_id IN (\n SELECT id FROM observations WHERE project_hash = ? AND deleted_at IS NULL\n )\n ORDER BY distance\n LIMIT ?\n `);\n\n this.stmtDelete = db.prepare(\n 'DELETE FROM observation_embeddings WHERE observation_id = ?',\n );\n\n this.stmtExists = db.prepare(\n 'SELECT 1 FROM observation_embeddings WHERE observation_id = ?',\n );\n\n this.stmtFindUnembedded = db.prepare(`\n SELECT id FROM observations\n WHERE project_hash = ?\n AND deleted_at IS NULL\n AND id NOT IN (SELECT observation_id FROM observation_embeddings)\n LIMIT ?\n `);\n }\n\n /**\n * Stores an embedding for an observation.\n *\n * Uses INSERT OR REPLACE so re-embedding an observation overwrites\n * the old vector.\n */\n store(observationId: string, embedding: Float32Array): void {\n try {\n this.stmtInsert.run(observationId, embedding);\n debug('embed', 'Stored embedding', { observationId, dimensions: embedding.length });\n } catch (err) {\n debug('embed', 'Failed to store embedding', {\n observationId,\n error: String(err),\n });\n }\n }\n\n /**\n * Project-scoped KNN search using cosine distance.\n *\n * Returns the nearest observations ordered by distance (ascending).\n * Only returns observations belonging to this store's project that\n * have not been soft-deleted.\n */\n search(queryEmbedding: Float32Array, limit = 20): EmbeddingSearchResult[] {\n try {\n const rows = this.stmtSearch.all(\n queryEmbedding,\n this.projectHash,\n limit,\n ) as Array<{ observation_id: string; distance: number }>;\n\n debug('embed', 'Search completed', {\n results: rows.length,\n limit,\n });\n\n return rows.map((row) => ({\n observationId: row.observation_id,\n distance: row.distance,\n }));\n } catch (err) {\n debug('embed', 'Search failed', { error: String(err) });\n return [];\n }\n }\n\n /**\n * Removes the embedding for a deleted observation.\n */\n delete(observationId: string): void {\n try {\n this.stmtDelete.run(observationId);\n debug('embed', 'Deleted embedding', { observationId });\n } catch (err) {\n debug('embed', 'Failed to delete embedding', {\n observationId,\n error: String(err),\n });\n }\n }\n\n /**\n * Checks if an observation has an embedding stored.\n */\n has(observationId: string): boolean {\n try {\n const row = this.stmtExists.get(observationId);\n return row !== undefined;\n } catch (err) {\n debug('embed', 'Failed to check embedding existence', {\n observationId,\n error: String(err),\n });\n return false;\n }\n }\n\n /**\n * Finds observation IDs that need embeddings generated.\n *\n * Returns IDs of observations belonging to this project that are\n * not soft-deleted and have no entry in the embeddings table.\n */\n findUnembedded(limit = 50): string[] {\n try {\n const rows = this.stmtFindUnembedded.all(\n this.projectHash,\n limit,\n ) as Array<{ id: string }>;\n\n debug('embed', 'Found unembedded observations', { count: rows.length, limit });\n\n return rows.map((row) => row.id);\n } catch (err) {\n debug('embed', 'Failed to find unembedded observations', {\n error: String(err),\n });\n return [];\n }\n }\n}\n","import type BetterSqlite3 from 'better-sqlite3';\nimport { randomBytes } from 'node:crypto';\n\nimport { debug } from '../shared/debug.js';\nimport type {\n ContextStash,\n StashObservation,\n CreateStashInput,\n} from '../types/stash.js';\n\n/**\n * Raw context_stashes row from SQLite (snake_case column names).\n */\ninterface StashRow {\n id: string;\n project_id: string;\n session_id: string;\n topic_label: string;\n summary: string;\n observation_snapshots: string; // JSON string\n observation_ids: string; // JSON string\n status: string;\n created_at: string;\n resumed_at: string | null;\n}\n\n/**\n * Maps a snake_case StashRow to a camelCase ContextStash interface.\n * JSON-parses observation_snapshots and observation_ids from their\n * serialized TEXT column format back into arrays.\n */\nfunction rowToStash(row: StashRow): ContextStash {\n return {\n id: row.id,\n projectId: row.project_id,\n sessionId: row.session_id,\n topicLabel: row.topic_label,\n summary: row.summary,\n observationIds: JSON.parse(row.observation_ids) as string[],\n observationSnapshots: JSON.parse(\n row.observation_snapshots,\n ) as StashObservation[],\n createdAt: row.created_at,\n resumedAt: row.resumed_at,\n status: row.status as ContextStash['status'],\n };\n}\n\n/**\n * Repository for context stash CRUD operations.\n *\n * Manages the lifecycle of stashed context threads: creating snapshots\n * when topic shifts are detected, listing available stashes, retrieving\n * full stash records, resuming stashes, and deleting them.\n *\n * All SQL statements are prepared once in the constructor and reused\n * for every call (better-sqlite3 performance best practice).\n */\nexport class StashManager {\n private readonly db: BetterSqlite3.Database;\n\n // Prepared statements\n private readonly stmtInsert: BetterSqlite3.Statement;\n private readonly stmtGetById: BetterSqlite3.Statement;\n private readonly stmtResume: BetterSqlite3.Statement;\n private readonly stmtDelete: BetterSqlite3.Statement;\n\n constructor(db: BetterSqlite3.Database) {\n this.db = db;\n\n this.stmtInsert = db.prepare(`\n INSERT INTO context_stashes (id, project_id, session_id, topic_label, summary, observation_snapshots, observation_ids, status)\n VALUES (?, ?, ?, ?, ?, ?, ?, 'stashed')\n `);\n\n this.stmtGetById = db.prepare(`\n SELECT * FROM context_stashes WHERE id = ?\n `);\n\n this.stmtResume = db.prepare(`\n UPDATE context_stashes\n SET status = 'resumed', resumed_at = datetime('now')\n WHERE id = ?\n `);\n\n this.stmtDelete = db.prepare(`\n DELETE FROM context_stashes WHERE id = ?\n `);\n\n debug('db', 'StashManager initialized');\n }\n\n /**\n * Creates a new stash record from a context thread snapshot.\n * JSON-serializes observation snapshots and IDs for TEXT column storage.\n * Uses randomBytes(16) hex for ID generation (matches ObservationRepository pattern).\n */\n createStash(input: CreateStashInput): ContextStash {\n const id = randomBytes(16).toString('hex');\n const observationIds = input.observations.map((o) => o.id);\n const snapshotsJson = JSON.stringify(input.observations);\n const idsJson = JSON.stringify(observationIds);\n\n debug('db', 'Creating stash', {\n topicLabel: input.topicLabel,\n observationCount: input.observations.length,\n });\n\n this.stmtInsert.run(\n id,\n input.projectId,\n input.sessionId,\n input.topicLabel,\n input.summary,\n snapshotsJson,\n idsJson,\n );\n\n const row = this.stmtGetById.get(id) as StashRow | undefined;\n if (!row) {\n throw new Error('Failed to retrieve newly created stash');\n }\n\n debug('db', 'Stash created', { id });\n\n return rowToStash(row);\n }\n\n /**\n * Lists stashes for a project, ordered by created_at DESC.\n * Supports optional filtering by session_id and status.\n */\n listStashes(\n projectId: string,\n options?: { sessionId?: string; status?: string; limit?: number },\n ): ContextStash[] {\n const limit = options?.limit ?? 10;\n\n let sql =\n 'SELECT * FROM context_stashes WHERE project_id = ?';\n const params: unknown[] = [projectId];\n\n if (options?.sessionId) {\n sql += ' AND session_id = ?';\n params.push(options.sessionId);\n }\n\n if (options?.status) {\n sql += ' AND status = ?';\n params.push(options.status);\n }\n\n sql += ' ORDER BY created_at DESC LIMIT ?';\n params.push(limit);\n\n debug('db', 'Listing stashes', { projectId, ...options });\n\n const rows = this.db.prepare(sql).all(...params) as StashRow[];\n return rows.map(rowToStash);\n }\n\n /**\n * Retrieves a single stash by ID with full observation snapshot data.\n * Returns null for nonexistent IDs.\n */\n getStash(id: string): ContextStash | null {\n const row = this.stmtGetById.get(id) as StashRow | undefined;\n return row ? rowToStash(row) : null;\n }\n\n /**\n * Marks a stash as resumed and sets resumed_at timestamp.\n * Returns the updated record.\n * Throws if the stash does not exist.\n */\n resumeStash(id: string): ContextStash {\n const result = this.stmtResume.run(id);\n\n if (result.changes === 0) {\n throw new Error(`Stash not found: ${id}`);\n }\n\n debug('db', 'Stash resumed', { id });\n\n const row = this.stmtGetById.get(id) as StashRow | undefined;\n if (!row) {\n throw new Error(`Failed to retrieve resumed stash: ${id}`);\n }\n\n return rowToStash(row);\n }\n\n /**\n * Hard-deletes a stash record.\n */\n deleteStash(id: string): void {\n this.stmtDelete.run(id);\n debug('db', 'Stash deleted', { id });\n }\n\n /**\n * Returns stashes with status='stashed' (excludes resumed) for a project,\n * ordered by created_at DESC.\n */\n getRecentStashes(projectId: string, limit?: number): ContextStash[] {\n return this.listStashes(projectId, {\n status: 'stashed',\n limit: limit ?? 10,\n });\n }\n}\n","import type BetterSqlite3 from 'better-sqlite3';\n\nimport { debug } from '../shared/debug.js';\nimport type { ThresholdState } from '../intelligence/adaptive-threshold.js';\n\n/**\n * Result of loading historical seed data for cold start.\n */\nexport interface HistoricalSeed {\n /** Average EWMA distance across recent sessions */\n averageDistance: number;\n /** Average EWMA variance across recent sessions */\n averageVariance: number;\n}\n\n/**\n * Persists and loads EWMA threshold history for session seeding.\n *\n * At the end of each session, the final EWMA state is saved via\n * saveSessionThreshold(). When a new session starts, loadHistoricalSeed()\n * computes averages from the last 10 sessions to bootstrap the EWMA\n * without cold-start problems.\n *\n * All SQL statements are prepared once in the constructor and reused\n * for every call (better-sqlite3 performance best practice).\n */\nexport class ThresholdStore {\n private readonly db: BetterSqlite3.Database;\n\n // Prepared statements\n private readonly stmtInsert: BetterSqlite3.Statement;\n private readonly stmtLoadSeed: BetterSqlite3.Statement;\n\n constructor(db: BetterSqlite3.Database) {\n this.db = db;\n\n this.stmtInsert = db.prepare(`\n INSERT INTO threshold_history (project_id, session_id, final_ewma_distance, final_ewma_variance, observation_count)\n VALUES (?, ?, ?, ?, ?)\n `);\n\n this.stmtLoadSeed = db.prepare(`\n SELECT\n AVG(final_ewma_distance) AS avg_distance,\n AVG(final_ewma_variance) AS avg_variance\n FROM (\n SELECT final_ewma_distance, final_ewma_variance\n FROM threshold_history\n WHERE project_id = ?\n ORDER BY created_at DESC\n LIMIT 10\n )\n `);\n\n debug('db', 'ThresholdStore initialized');\n }\n\n /**\n * Persist the final EWMA state of a session for future seeding.\n */\n saveSessionThreshold(\n projectId: string,\n sessionId: string,\n state: ThresholdState,\n ): void {\n this.stmtInsert.run(\n projectId,\n sessionId,\n state.ewmaDistance,\n state.ewmaVariance,\n state.observationCount,\n );\n\n debug('db', 'Threshold saved', {\n projectId,\n sessionId,\n ewmaDistance: state.ewmaDistance,\n observations: state.observationCount,\n });\n }\n\n /**\n * Load historical seed by averaging the last 10 sessions for a project.\n *\n * Returns null if no history exists for this project.\n */\n loadHistoricalSeed(projectId: string): HistoricalSeed | null {\n const row = this.stmtLoadSeed.get(projectId) as {\n avg_distance: number | null;\n avg_variance: number | null;\n };\n\n if (row.avg_distance === null || row.avg_variance === null) {\n debug('db', 'No threshold history found', { projectId });\n return null;\n }\n\n debug('db', 'Threshold seed loaded', {\n projectId,\n avgDistance: row.avg_distance,\n avgVariance: row.avg_variance,\n });\n\n return {\n averageDistance: row.avg_distance,\n averageVariance: row.avg_variance,\n };\n }\n}\n","import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\n\nimport { debug } from '../shared/debug.js';\n\nexport function createServer(): McpServer {\n return new McpServer(\n { name: 'laminark', version: '0.1.0' },\n { capabilities: { tools: {} } },\n );\n}\n\nexport async function startServer(server: McpServer): Promise<void> {\n const transport = new StdioServerTransport();\n await server.connect(transport);\n debug('mcp', 'MCP server started on stdio transport');\n}\n","/**\n * Cross-Project Access Configuration\n *\n * Per-project config that controls which other projects' memories\n * the current project can read from. Read-only access — no writes\n * cross projects.\n *\n * Config stored at: {configDir}/cross-access-{projectHash}.json\n */\n\nimport { existsSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs';\nimport { join } from 'node:path';\n\nimport { getConfigDir } from '../shared/config.js';\n\nexport interface CrossAccessConfig {\n readableProjects: string[];\n}\n\nconst DEFAULTS: CrossAccessConfig = {\n readableProjects: [],\n};\n\nfunction getConfigPath(projectHash: string): string {\n return join(getConfigDir(), `cross-access-${projectHash}.json`);\n}\n\nexport function loadCrossAccessConfig(projectHash: string): CrossAccessConfig {\n const configPath = getConfigPath(projectHash);\n try {\n if (!existsSync(configPath)) return { ...DEFAULTS };\n const raw = readFileSync(configPath, 'utf-8');\n const parsed = JSON.parse(raw) as Partial<CrossAccessConfig>;\n return {\n readableProjects: Array.isArray(parsed.readableProjects)\n ? parsed.readableProjects.filter((h): h is string => typeof h === 'string')\n : [],\n };\n } catch {\n return { ...DEFAULTS };\n }\n}\n\nexport function saveCrossAccessConfig(projectHash: string, config: CrossAccessConfig): void {\n const configPath = getConfigPath(projectHash);\n const validated: CrossAccessConfig = {\n readableProjects: Array.isArray(config.readableProjects)\n ? config.readableProjects.filter((h): h is string => typeof h === 'string' && h !== projectHash)\n : [],\n };\n writeFileSync(configPath, JSON.stringify(validated, null, 2), 'utf-8');\n}\n\nexport function resetCrossAccessConfig(projectHash: string): void {\n const configPath = getConfigPath(projectHash);\n try {\n if (existsSync(configPath)) unlinkSync(configPath);\n } catch { /* ignore */ }\n}\n","export const TOKEN_BUDGET = 2000;\nexport const FULL_VIEW_BUDGET = 4000;\n\nexport function estimateTokens(text: string): number {\n return Math.ceil(text.length / 4);\n}\n\nexport function enforceTokenBudget<T>(\n results: T[],\n formatResult: (item: T) => string,\n budget: number = TOKEN_BUDGET,\n): { items: T[]; truncated: boolean; tokenEstimate: number } {\n const METADATA_RESERVE = 100;\n const effectiveBudget = budget - METADATA_RESERVE;\n let totalTokens = 0;\n const items: T[] = [];\n\n for (const result of results) {\n const formatted = formatResult(result);\n const tokens = estimateTokens(formatted);\n if (totalTokens + tokens > effectiveBudget && items.length > 0) {\n return { items, truncated: true, tokenEstimate: totalTokens };\n }\n items.push(result);\n totalTokens += tokens;\n }\n\n return { items, truncated: false, tokenEstimate: totalTokens };\n}\n","/**\n * Tool Response Verbosity Configuration\n *\n * Controls how much detail MCP tool responses include.\n * Three levels:\n * 1 (minimal): Just confirms the tool ran\n * 2 (standard): Shows title/key info (default)\n * 3 (verbose): Full formatted text with all details\n *\n * Configuration is loaded from .laminark/tool-verbosity.json with\n * a 5-second cache to avoid repeated disk reads.\n */\n\nimport { readFileSync, writeFileSync } from 'node:fs';\nimport { join } from 'node:path';\n\nimport { debug } from '../shared/debug.js';\nimport { getConfigDir } from '../shared/config.js';\n\nexport type VerbosityLevel = 1 | 2 | 3;\n\nexport interface ToolVerbosityConfig {\n level: VerbosityLevel;\n}\n\nconst DEFAULTS: ToolVerbosityConfig = { level: 2 };\nconst CACHE_TTL_MS = 5000;\n\nlet cachedConfig: ToolVerbosityConfig | null = null;\nlet cachedAt = 0;\n\n/**\n * Loads tool verbosity configuration from disk with a 5-second cache.\n */\nexport function loadToolVerbosityConfig(): ToolVerbosityConfig {\n const now = Date.now();\n if (cachedConfig && now - cachedAt < CACHE_TTL_MS) {\n return cachedConfig;\n }\n\n const configPath = join(getConfigDir(), 'tool-verbosity.json');\n try {\n const content = readFileSync(configPath, 'utf-8');\n const raw = JSON.parse(content) as Record<string, unknown>;\n const level = raw.level;\n if (level === 1 || level === 2 || level === 3) {\n cachedConfig = { level };\n } else {\n cachedConfig = { ...DEFAULTS };\n }\n debug('config', 'Loaded tool verbosity config', { level: cachedConfig.level });\n } catch {\n cachedConfig = { ...DEFAULTS };\n }\n\n cachedAt = now;\n return cachedConfig;\n}\n\n/**\n * Saves tool verbosity configuration to disk and invalidates cache.\n */\nexport function saveToolVerbosityConfig(config: ToolVerbosityConfig): void {\n const configPath = join(getConfigDir(), 'tool-verbosity.json');\n writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf-8');\n cachedConfig = config;\n cachedAt = Date.now();\n}\n\n/**\n * Resets tool verbosity to defaults by invalidating cache.\n */\nexport function resetToolVerbosityConfig(): ToolVerbosityConfig {\n cachedConfig = null;\n cachedAt = 0;\n return { ...DEFAULTS };\n}\n\n/**\n * Selects the appropriate response text based on the current verbosity level.\n *\n * Each tool passes three pre-built strings:\n * - minimal: Level 1 — just confirms the tool ran\n * - standard: Level 2 — shows title/key info\n * - verbose: Level 3 — full formatted text\n */\nexport function formatResponse(\n level: VerbosityLevel,\n minimal: string,\n standard: string,\n verbose: string,\n): string {\n switch (level) {\n case 1: return minimal;\n case 2: return standard;\n case 3: return verbose;\n }\n}\n\n/**\n * Convenience: loads config and selects the response in one call.\n */\nexport function verboseResponse(\n minimal: string,\n standard: string,\n verbose: string,\n): string {\n const { level } = loadToolVerbosityConfig();\n return formatResponse(level, minimal, standard, verbose);\n}\n","import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type BetterSqlite3 from 'better-sqlite3';\nimport { z } from 'zod';\n\nimport { debug } from '../../shared/debug.js';\nimport type { Observation, SearchResult } from '../../shared/types.js';\nimport { ObservationRepository } from '../../storage/observations.js';\nimport { SearchEngine } from '../../storage/search.js';\nimport type { EmbeddingStore } from '../../storage/embeddings.js';\nimport type { AnalysisWorker } from '../../analysis/worker-bridge.js';\nimport type { NotificationStore } from '../../storage/notifications.js';\nimport { hybridSearch } from '../../search/hybrid.js';\nimport type { ProjectHashRef } from '../../shared/types.js';\nimport { loadCrossAccessConfig } from '../../config/cross-access.js';\nimport {\n enforceTokenBudget,\n estimateTokens,\n FULL_VIEW_BUDGET,\n TOKEN_BUDGET,\n} from '../token-budget.js';\nimport type { StatusCache } from '../status-cache.js';\nimport { loadToolVerbosityConfig } from '../../config/tool-verbosity-config.js';\n\n// ---------------------------------------------------------------------------\n// Formatting helpers\n// ---------------------------------------------------------------------------\n\nfunction shortId(id: string): string {\n return id.slice(0, 8);\n}\n\nfunction dateStr(iso: string): string {\n return iso.slice(0, 10); // YYYY-MM-DD\n}\n\nfunction timeStr(iso: string): string {\n return iso.slice(11, 16); // HH:MM\n}\n\nfunction snippetText(content: string, maxLen: number): string {\n return content.replace(/\\n/g, ' ').slice(0, maxLen);\n}\n\n// ---------------------------------------------------------------------------\n// Detail-level formatters\n// ---------------------------------------------------------------------------\n\nfunction formatCompactItem(\n obs: Observation,\n index: number,\n score?: number,\n): string {\n const idShort = shortId(obs.id);\n const title = obs.title ?? 'untitled';\n const scoreStr = score !== undefined ? score.toFixed(2) : '-';\n const snippet = snippetText(obs.content, 100);\n const date = dateStr(obs.createdAt);\n return `[${index}] ${idShort} | ${title} | ${scoreStr} | ${snippet} | ${date}`;\n}\n\nfunction formatTimelineGroup(\n date: string,\n items: { obs: Observation; score?: number }[],\n): string {\n const lines = [`## ${date}`];\n for (const { obs } of items) {\n const time = timeStr(obs.createdAt);\n const title = obs.title ?? 'untitled';\n const source = obs.source;\n const snippet = snippetText(obs.content, 150);\n lines.push(`${time} | ${title} | ${source} | ${snippet}`);\n }\n return lines.join('\\n');\n}\n\nfunction formatFullItem(obs: Observation): string {\n const idShort = shortId(obs.id);\n const title = obs.title ?? 'untitled';\n return `--- ${idShort} | ${title} | ${obs.createdAt} ---\\n${obs.content}`;\n}\n\n// ---------------------------------------------------------------------------\n// Response helpers\n// ---------------------------------------------------------------------------\n\nfunction prependNotifications(\n notificationStore: NotificationStore | null,\n projectHash: string,\n responseText: string,\n): string {\n if (!notificationStore) return responseText;\n const pending = notificationStore.consumePending(projectHash);\n if (pending.length === 0) return responseText;\n const banner = pending.map(n => `[Laminark] ${n.message}`).join('\\n');\n return banner + '\\n\\n' + responseText;\n}\n\nfunction textResponse(text: string) {\n return { content: [{ type: 'text' as const, text }] };\n}\n\nfunction errorResponse(text: string) {\n return { content: [{ type: 'text' as const, text }], isError: true };\n}\n\n// ---------------------------------------------------------------------------\n// Cross-project helpers\n// ---------------------------------------------------------------------------\n\ninterface ProjectNameRow {\n project_hash: string;\n display_name: string | null;\n}\n\nfunction getProjectNameMap(db: BetterSqlite3.Database): Map<string, string> {\n const map = new Map<string, string>();\n try {\n const rows = db.prepare(\n 'SELECT project_hash, display_name FROM project_metadata'\n ).all() as ProjectNameRow[];\n for (const row of rows) {\n map.set(row.project_hash, row.display_name ?? row.project_hash.slice(0, 8));\n }\n } catch { /* table may not exist yet */ }\n return map;\n}\n\n// ---------------------------------------------------------------------------\n// registerRecall\n// ---------------------------------------------------------------------------\n\nexport function registerRecall(\n server: McpServer,\n db: BetterSqlite3.Database,\n projectHashRef: ProjectHashRef,\n worker: AnalysisWorker | null = null,\n embeddingStore: EmbeddingStore | null = null,\n notificationStore: NotificationStore | null = null,\n statusCache: StatusCache | null = null,\n): void {\n server.registerTool(\n 'recall',\n {\n title: 'Recall Memories',\n description:\n 'Search, view, purge, or restore memories. Search first to find matches, then act on specific results by ID.',\n inputSchema: {\n query: z\n .string()\n .optional()\n .describe('FTS5 keyword search query'),\n id: z.string().optional().describe('Direct lookup by observation ID'),\n title: z\n .string()\n .optional()\n .describe('Search by title (partial match)'),\n action: z\n .enum(['view', 'purge', 'restore'])\n .default('view')\n .describe(\n 'Action to take on results: view (show details), purge (soft-delete), restore (un-delete)',\n ),\n ids: z\n .array(z.string())\n .optional()\n .describe(\n 'Specific observation IDs to act on (from a previous search result)',\n ),\n detail: z\n .enum(['compact', 'timeline', 'full'])\n .default('compact')\n .describe(\n 'View detail level: compact (index ~80 tokens/result), timeline (date-grouped), full (complete text)',\n ),\n kind: z\n .enum(['change', 'reference', 'finding', 'decision', 'verification'])\n .optional()\n .describe(\n 'Filter results by observation kind: change, reference, finding, decision, verification',\n ),\n limit: z\n .number()\n .int()\n .min(1)\n .max(50)\n .default(10)\n .describe('Maximum results to return'),\n include_purged: z\n .boolean()\n .default(false)\n .describe(\n 'Include soft-deleted items in results (needed for restore)',\n ),\n },\n },\n async (args) => {\n const projectHash = projectHashRef.current;\n // Helper to wrap textResponse with pending notifications\n const withNotifications = (text: string) =>\n textResponse(prependNotifications(notificationStore, projectHash, text));\n\n try {\n const repo = new ObservationRepository(db, projectHash);\n const searchEngine = new SearchEngine(db, projectHash);\n\n // -------------------------------------------------------------------\n // PHASE A: Input Validation\n // -------------------------------------------------------------------\n const hasSearch = args.query !== undefined || args.id !== undefined || args.title !== undefined;\n if (args.ids && hasSearch) {\n return errorResponse(\n 'Provide either a search query or IDs to act on, not both.',\n );\n }\n\n if (\n (args.action === 'purge' || args.action === 'restore') &&\n !args.ids &&\n !args.id\n ) {\n return errorResponse(\n `Provide ids array or id to specify which memories to ${args.action}.`,\n );\n }\n\n // -------------------------------------------------------------------\n // PHASE B: Resolve Observations\n // -------------------------------------------------------------------\n let observations: Observation[] = [];\n let searchResults: SearchResult[] | null = null;\n\n if (args.ids) {\n // Fetch each ID, track not-found\n const notFound: string[] = [];\n for (const itemId of args.ids) {\n const obs = repo.getByIdIncludingDeleted(itemId);\n if (obs) {\n observations.push(obs);\n } else {\n notFound.push(itemId);\n }\n }\n if (notFound.length > 0 && observations.length === 0) {\n return withNotifications(\n `No memories found matching '${notFound.join(', ')}'. Try broader search terms or check the ID.`,\n );\n }\n } else if (args.id) {\n const obs = args.include_purged\n ? repo.getByIdIncludingDeleted(args.id)\n : repo.getById(args.id);\n if (!obs) {\n return withNotifications(\n `No memories found matching '${args.id}'. Try broader search terms or check the ID.`,\n );\n }\n observations = [obs];\n } else if (args.query) {\n if (embeddingStore) {\n searchResults = await hybridSearch({\n searchEngine,\n embeddingStore,\n worker,\n query: args.query,\n db,\n projectHash,\n options: { limit: args.limit },\n });\n } else {\n searchResults = searchEngine.searchKeyword(args.query, {\n limit: args.limit,\n });\n }\n observations = searchResults.map((r) => r.observation);\n\n // Cross-project search: also search readable projects\n const crossConfig = loadCrossAccessConfig(projectHash);\n if (crossConfig.readableProjects.length > 0) {\n const nameMap = getProjectNameMap(db);\n for (const otherHash of crossConfig.readableProjects) {\n const otherEngine = new SearchEngine(db, otherHash);\n let otherResults: SearchResult[];\n if (embeddingStore) {\n otherResults = await hybridSearch({\n searchEngine: otherEngine,\n embeddingStore,\n worker,\n query: args.query,\n db,\n projectHash: otherHash,\n options: { limit: args.limit },\n });\n } else {\n otherResults = otherEngine.searchKeyword(args.query, {\n limit: args.limit,\n });\n }\n if (otherResults.length > 0) {\n const projName = nameMap.get(otherHash) ?? otherHash.slice(0, 8);\n for (const r of otherResults) {\n r.observation.title = `[${projName}] ${r.observation.title ?? 'untitled'}`;\n }\n searchResults.push(...otherResults);\n observations.push(...otherResults.map((r) => r.observation));\n }\n }\n }\n } else if (args.title) {\n observations = repo.getByTitle(args.title, {\n limit: args.limit,\n includePurged: args.include_purged,\n });\n } else {\n // No query, id, title, or ids -- list recent\n observations = args.include_purged\n ? repo.listIncludingDeleted({ limit: args.limit })\n : repo.list({ limit: args.limit, kind: args.kind });\n }\n\n // Apply kind filter to resolved observations (post-search filtering)\n if (args.kind && observations.length > 0) {\n observations = observations.filter((obs) => obs.kind === args.kind);\n }\n\n if (observations.length === 0) {\n const searchTerm = args.query ?? args.title ?? args.id ?? '';\n return withNotifications(\n `No memories found matching '${searchTerm}'. Try broader search terms or check the ID.`,\n );\n }\n\n // -------------------------------------------------------------------\n // PHASE C: Execute Action\n // -------------------------------------------------------------------\n\n // --- VIEW ---\n if (args.action === 'view') {\n const verbosity = loadToolVerbosityConfig().level;\n\n if (verbosity === 1) {\n // Minimal: just count\n const searchTerm = args.query ?? args.title ?? 'query';\n return textResponse(\n prependNotifications(notificationStore, projectHash,\n `Found ${observations.length} memories matching \"${searchTerm}\"`),\n );\n }\n\n if (verbosity === 2) {\n // Standard: title-only numbered list\n const lines = observations.map((obs, i) => {\n const title = obs.title ?? 'untitled';\n return `${i + 1}. ${title}`;\n });\n const footer = `\\n---\\n${observations.length} result(s)`;\n return textResponse(\n prependNotifications(notificationStore, projectHash, lines.join('\\n') + footer),\n );\n }\n\n // Verbose (level 3): full output (current behavior)\n const viewResponse = formatViewResponse(\n observations,\n searchResults,\n args.detail,\n args.id !== undefined,\n );\n const originalText = viewResponse.content[0].text;\n return textResponse(prependNotifications(notificationStore, projectHash, originalText));\n }\n\n // --- PURGE ---\n if (args.action === 'purge') {\n const targetIds = args.ids ?? (args.id ? [args.id] : []);\n let success = 0;\n const failures: string[] = [];\n for (const targetId of targetIds) {\n if (repo.softDelete(targetId)) {\n success++;\n } else {\n failures.push(targetId);\n }\n }\n debug('mcp', 'recall: purge', { success, total: targetIds.length });\n if (success > 0) statusCache?.markDirty();\n let msg = `Purged ${success}/${targetIds.length} memories.`;\n if (failures.length > 0) {\n msg += ` Not found or already purged: ${failures.join(', ')}`;\n }\n return withNotifications(msg);\n }\n\n // --- RESTORE ---\n if (args.action === 'restore') {\n const targetIds = args.ids ?? (args.id ? [args.id] : []);\n let success = 0;\n const failures: string[] = [];\n for (const targetId of targetIds) {\n if (repo.restore(targetId)) {\n success++;\n } else {\n failures.push(targetId);\n }\n }\n debug('mcp', 'recall: restore', {\n success,\n total: targetIds.length,\n });\n if (success > 0) statusCache?.markDirty();\n let msg = `Restored ${success}/${targetIds.length} memories.`;\n if (failures.length > 0) {\n msg += ` Not found: ${failures.join(', ')}`;\n }\n return withNotifications(msg);\n }\n\n // Should not reach here, but TypeScript exhaustiveness\n return errorResponse(`Unknown action: ${args.action as string}`);\n } catch (err) {\n const message =\n err instanceof Error ? err.message : 'Unknown error';\n debug('mcp', 'recall: error', { error: message });\n return errorResponse(`Recall error: ${message}`);\n }\n },\n );\n}\n\n// ---------------------------------------------------------------------------\n// View formatting with token budget\n// ---------------------------------------------------------------------------\n\nfunction formatViewResponse(\n observations: Observation[],\n searchResults: SearchResult[] | null,\n detail: 'compact' | 'timeline' | 'full',\n isSingleIdLookup: boolean,\n): { content: { type: 'text'; text: string }[] } {\n let body: string;\n let truncated: boolean;\n let tokenEstimate: number;\n\n if (detail === 'compact') {\n const scoreMap = buildScoreMap(searchResults);\n const result = enforceTokenBudget(\n observations,\n (obs) => formatCompactItem(obs, observations.indexOf(obs) + 1, scoreMap.get(obs.id)),\n TOKEN_BUDGET,\n );\n body = result.items\n .map((obs, i) => formatCompactItem(obs, i + 1, scoreMap.get(obs.id)))\n .join('\\n');\n truncated = result.truncated;\n tokenEstimate = result.tokenEstimate;\n } else if (detail === 'timeline') {\n // Group by date\n const groups = new Map<string, { obs: Observation; score?: number }[]>();\n const scoreMap = buildScoreMap(searchResults);\n for (const obs of observations) {\n const date = dateStr(obs.createdAt);\n if (!groups.has(date)) {\n groups.set(date, []);\n }\n groups.get(date)!.push({ obs, score: scoreMap.get(obs.id) });\n }\n\n const result = enforceTokenBudget(\n observations,\n (obs) => {\n const time = timeStr(obs.createdAt);\n const title = obs.title ?? 'untitled';\n return `${time} | ${title} | ${obs.source} | ${snippetText(obs.content, 150)}`;\n },\n TOKEN_BUDGET,\n );\n\n // Re-group the budget-enforced items\n const includedIds = new Set(result.items.map((o) => o.id));\n const filteredGroups = new Map<string, { obs: Observation; score?: number }[]>();\n for (const [date, items] of groups) {\n const filtered = items.filter((item) => includedIds.has(item.obs.id));\n if (filtered.length > 0) {\n filteredGroups.set(date, filtered);\n }\n }\n\n body = Array.from(filteredGroups.entries())\n .map(([date, items]) => formatTimelineGroup(date, items))\n .join('\\n\\n');\n truncated = result.truncated;\n tokenEstimate = result.tokenEstimate;\n } else {\n // detail === 'full'\n const budget = isSingleIdLookup ? FULL_VIEW_BUDGET : TOKEN_BUDGET;\n\n if (observations.length === 1) {\n const formatted = formatFullItem(observations[0]);\n tokenEstimate = estimateTokens(formatted);\n if (tokenEstimate > budget) {\n // Truncate single item to fit budget\n const maxChars = budget * 4; // ~4 chars per token\n body =\n formatted.slice(0, maxChars) +\n `\\n[...truncated at ~${budget} tokens]`;\n truncated = true;\n tokenEstimate = budget;\n } else {\n body = formatted;\n truncated = false;\n }\n } else {\n const result = enforceTokenBudget(\n observations,\n formatFullItem,\n budget,\n );\n body = result.items.map(formatFullItem).join('\\n\\n');\n truncated = result.truncated;\n tokenEstimate = result.tokenEstimate;\n }\n }\n\n // Metadata footer\n let footer = `---\\n${observations.length} result(s) | ~${tokenEstimate} tokens | detail: ${detail}`;\n if (truncated) {\n footer += ' | truncated (use id for full view)';\n }\n\n return textResponse(`${body}\\n${footer}`);\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction buildScoreMap(\n searchResults: SearchResult[] | null,\n): Map<string, number> {\n const map = new Map<string, number>();\n if (searchResults) {\n for (const r of searchResults) {\n map.set(r.observation.id, r.score);\n }\n }\n return map;\n}\n","import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type BetterSqlite3 from 'better-sqlite3';\nimport { z } from 'zod';\n\nimport { debug } from '../../shared/debug.js';\nimport type { ProjectHashRef } from '../../shared/types.js';\nimport { ObservationRepository } from '../../storage/observations.js';\nimport type { NotificationStore } from '../../storage/notifications.js';\nimport type { AnalysisWorker } from '../../analysis/worker-bridge.js';\nimport type { EmbeddingStore } from '../../storage/embeddings.js';\nimport { SaveGuard } from '../../hooks/save-guard.js';\nimport type { StatusCache } from '../status-cache.js';\nimport { verboseResponse } from '../../config/tool-verbosity-config.js';\n\n/**\n * Generates a title from observation content.\n * Extracts the first sentence (up to 100 chars) or first 80 chars with ellipsis.\n */\nexport function generateTitle(content: string): string {\n const firstSentence = content.match(/^[^.!?\\n]+[.!?]?/);\n if (firstSentence && firstSentence[0].length <= 100) {\n return firstSentence[0].trim();\n }\n if (content.length <= 80) return content.trim();\n return content.slice(0, 80).trim() + '...';\n}\n\n/**\n * Registers the save_memory tool on the MCP server.\n *\n * save_memory persists user-provided text as a new observation with an optional title.\n * If title is omitted, one is auto-generated from the text content.\n */\nexport function registerSaveMemory(\n server: McpServer,\n db: BetterSqlite3.Database,\n projectHashRef: ProjectHashRef,\n notificationStore: NotificationStore | null = null,\n worker: AnalysisWorker | null = null,\n embeddingStore: EmbeddingStore | null = null,\n statusCache: StatusCache | null = null,\n): void {\n server.registerTool(\n 'save_memory',\n {\n title: 'Save Memory',\n description:\n 'Save a new memory observation. Provide text content and an optional title. If title is omitted, one is auto-generated from the text.',\n inputSchema: {\n text: z\n .string()\n .min(1)\n .max(10000)\n .describe('The text content to save as a memory'),\n title: z\n .string()\n .max(200)\n .optional()\n .describe(\n 'Optional title for the memory. Auto-generated from text if omitted.',\n ),\n source: z\n .string()\n .default('manual')\n .describe(\"Source identifier (e.g., manual, hook:PostToolUse)\"),\n kind: z\n .enum(['change', 'reference', 'finding', 'decision', 'verification'])\n .default('finding')\n .describe('Observation kind: change, reference, finding, decision, or verification'),\n },\n },\n async (args) => {\n const projectHash = projectHashRef.current;\n try {\n const repo = new ObservationRepository(db, projectHash);\n\n // Pre-save gate: duplicate detection + relevance scoring\n const guard = new SaveGuard(repo, { worker, embeddingStore });\n const decision = await guard.evaluate(args.text, args.source);\n if (!decision.save) {\n debug('mcp', 'save_memory: rejected by save guard', {\n reason: decision.reason,\n duplicateOf: decision.duplicateOf,\n });\n return {\n content: [{\n type: 'text' as const,\n text: `Memory not saved: ${decision.reason}` +\n (decision.duplicateOf ? ` (similar to existing observation ${decision.duplicateOf})` : ''),\n }],\n };\n }\n\n const resolvedTitle = args.title ?? generateTitle(args.text);\n // Create as unclassified so HaikuProcessor picks it up for\n // entity extraction and graph population in its next cycle.\n const obs = repo.create({\n content: args.text,\n title: resolvedTitle,\n source: args.source,\n kind: args.kind,\n });\n\n debug('mcp', 'save_memory: saved', {\n id: obs.id,\n title: resolvedTitle,\n });\n\n statusCache?.markDirty();\n\n // Prepend any pending notifications to the response\n let responseText = verboseResponse(\n 'Memory saved.',\n `Saved \"${resolvedTitle}\"`,\n `Saved memory \"${resolvedTitle}\" (id: ${obs.id})`,\n );\n if (notificationStore) {\n const pending = notificationStore.consumePending(projectHash);\n if (pending.length > 0) {\n const banner = pending.map(n => `[Laminark] ${n.message}`).join('\\n');\n responseText = banner + '\\n\\n' + responseText;\n }\n }\n\n return {\n content: [\n {\n type: 'text' as const,\n text: responseText,\n },\n ],\n };\n } catch (err) {\n const message =\n err instanceof Error ? err.message : 'Unknown error';\n return {\n content: [\n { type: 'text' as const, text: `Failed to save: ${message}` },\n ],\n isError: true,\n };\n }\n },\n );\n}\n","/**\n * Markdown section parser for knowledge ingestion.\n *\n * Splits structured markdown documents (GSD map-codebase output) into\n * discrete sections by ## headings. Each section becomes one observation\n * in the knowledge store.\n */\n\nexport interface ParsedSection {\n title: string; // Full title: \"Technology Stack > Languages\" (docTitle > heading)\n heading: string; // Just the heading text: \"Languages\"\n content: string; // Everything under heading until next ## or EOF\n sourceFile: string; // Filename: \"STACK.md\"\n sectionIndex: number; // 0-based index within file\n}\n\n/**\n * Parse a markdown file into discrete sections split on ## headings.\n *\n * - The # (level 1) heading is the doc title, used as prefix: \"DocTitle > SectionHeading\"\n * - ### subsections stay within their parent ## section (not split separately)\n * - Sections with empty content after trimming are skipped\n * - Content before the first ## heading is skipped\n * - ## inside fenced code blocks are not treated as headings\n */\nexport function parseMarkdownSections(\n fileContent: string,\n sourceFile: string,\n): ParsedSection[] {\n const lines = fileContent.split('\\n');\n const sections: ParsedSection[] = [];\n\n let docTitle = '';\n let currentHeading = '';\n let currentLines: string[] = [];\n let sectionIndex = 0;\n let inCodeBlock = false;\n\n for (const line of lines) {\n // Track fenced code blocks to avoid splitting on ## inside them\n if (line.trimStart().startsWith('```')) {\n inCodeBlock = !inCodeBlock;\n }\n\n if (inCodeBlock) {\n if (currentHeading) {\n currentLines.push(line);\n }\n continue;\n }\n\n // Document title (# heading) -- only match single # not ##\n if (/^# (?!#)/.test(line)) {\n docTitle = line.slice(2).trim();\n continue;\n }\n\n // Section heading (## heading) -- only match exactly ## not ###\n if (/^## (?!#)/.test(line)) {\n // Save previous section if any\n if (currentHeading) {\n const content = currentLines.join('\\n').trim();\n if (content.length > 0) {\n sections.push({\n title: docTitle ? `${docTitle} > ${currentHeading}` : currentHeading,\n heading: currentHeading,\n content,\n sourceFile,\n sectionIndex,\n });\n sectionIndex++;\n }\n }\n currentHeading = line.slice(3).trim();\n currentLines = [];\n continue;\n }\n\n if (currentHeading) {\n currentLines.push(line);\n }\n }\n\n // Don't forget the last section\n if (currentHeading) {\n const content = currentLines.join('\\n').trim();\n if (content.length > 0) {\n sections.push({\n title: docTitle ? `${docTitle} > ${currentHeading}` : currentHeading,\n heading: currentHeading,\n content,\n sourceFile,\n sectionIndex,\n });\n }\n }\n\n return sections;\n}\n","/**\n * Knowledge ingester for markdown documents.\n *\n * Transforms structured markdown files (from .planning/codebase/ or .laminark/codebase/)\n * into discrete, queryable reference observations. Implements idempotent re-ingestion\n * via soft-delete + recreate strategy.\n */\n\nimport type BetterSqlite3 from 'better-sqlite3';\nimport { existsSync, statSync } from 'node:fs';\nimport { readdir, readFile } from 'node:fs/promises';\nimport { basename, join } from 'node:path';\nimport { parseMarkdownSections } from './markdown-parser.js';\nimport { ObservationRepository } from '../storage/observations.js';\n\n/**\n * Statistics from an ingestion operation.\n */\nexport interface IngestionStats {\n filesProcessed: number;\n sectionsCreated: number;\n sectionsRemoved: number;\n}\n\n/**\n * Ingests markdown files into the knowledge store.\n *\n * Creates one observation per ## section, with idempotent re-ingestion\n * that cleans up stale sections without duplication.\n */\nexport class KnowledgeIngester {\n private readonly db: BetterSqlite3.Database;\n private readonly projectHash: string;\n\n constructor(db: BetterSqlite3.Database, projectHash: string) {\n this.db = db;\n this.projectHash = projectHash;\n }\n\n /**\n * Detects the knowledge directory for a project.\n * Checks in order:\n * 1. {projectRoot}/.planning/codebase/ (GSD output)\n * 2. {projectRoot}/.laminark/codebase/\n * Returns the first existing directory, or null if none exist.\n */\n static detectKnowledgeDir(projectRoot: string): string | null {\n const candidates = [\n join(projectRoot, '.planning', 'codebase'),\n join(projectRoot, '.laminark', 'codebase'),\n ];\n\n for (const candidate of candidates) {\n try {\n if (existsSync(candidate) && statSync(candidate).isDirectory()) {\n return candidate;\n }\n } catch {\n // Directory doesn't exist, try next\n }\n }\n\n return null;\n }\n\n /**\n * Ingests all markdown files from a directory.\n * Reads all files async first, then runs DB operations in a single transaction.\n */\n async ingestDirectory(dirPath: string): Promise<IngestionStats> {\n let files: string[];\n try {\n files = await readdir(dirPath);\n } catch {\n // Directory doesn't exist or can't be read\n return { filesProcessed: 0, sectionsCreated: 0, sectionsRemoved: 0 };\n }\n\n // Filter to .md files only\n const mdFiles = files.filter((f) => f.endsWith('.md'));\n\n // Read all file contents async first\n const fileContents = new Map<string, string>();\n for (const file of mdFiles) {\n const filePath = join(dirPath, file);\n try {\n const content = await readFile(filePath, 'utf-8');\n fileContents.set(file, content);\n } catch {\n // Skip files that can't be read\n }\n }\n\n // Aggregate stats\n let totalCreated = 0;\n let totalRemoved = 0;\n\n // Process each file in a transaction\n for (const [filename, content] of fileContents) {\n const stats = this.ingestFileSync(filename, content);\n totalCreated += stats.sectionsCreated;\n totalRemoved += stats.sectionsRemoved;\n }\n\n return {\n filesProcessed: fileContents.size,\n sectionsCreated: totalCreated,\n sectionsRemoved: totalRemoved,\n };\n }\n\n /**\n * Ingests a single markdown file.\n * Wraps async file reading with sync ingestion.\n */\n async ingestFile(filePath: string): Promise<IngestionStats> {\n try {\n const content = await readFile(filePath, 'utf-8');\n const filename = basename(filePath);\n return this.ingestFileSync(filename, content);\n } catch {\n // File can't be read\n return { filesProcessed: 0, sectionsCreated: 0, sectionsRemoved: 0 };\n }\n }\n\n /**\n * Internal sync ingestion method (runs within transaction).\n * Implements idempotent upsert via soft-delete + recreate.\n */\n private ingestFileSync(filename: string, fileContent: string): IngestionStats {\n const sourceTag = `ingest:${filename}`;\n\n // Parse sections from file\n const sections = parseMarkdownSections(fileContent, filename);\n\n // Run DB operations in a transaction\n return this.db.transaction(() => {\n const repo = new ObservationRepository(this.db, this.projectHash);\n\n // Soft-delete ALL existing observations with matching source and project\n const deleteResult = this.db\n .prepare(\n `UPDATE observations\n SET deleted_at = datetime('now'), updated_at = datetime('now')\n WHERE project_hash = ? AND source = ? AND deleted_at IS NULL`,\n )\n .run(this.projectHash, sourceTag);\n\n const sectionsRemoved = deleteResult.changes;\n\n // Create new observations for each parsed section\n let sectionsCreated = 0;\n for (const section of sections) {\n repo.createClassified(\n {\n content: section.content,\n title: section.title,\n source: sourceTag,\n kind: 'reference',\n sessionId: null,\n },\n 'discovery', // Bypass noise filter, make immediately searchable\n );\n sectionsCreated++;\n }\n\n return {\n filesProcessed: 1,\n sectionsCreated,\n sectionsRemoved,\n };\n })();\n }\n}\n","import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type BetterSqlite3 from 'better-sqlite3';\nimport { z } from 'zod';\n\nimport { debug } from '../../shared/debug.js';\nimport type { ProjectHashRef } from '../../shared/types.js';\nimport type { NotificationStore } from '../../storage/notifications.js';\nimport { KnowledgeIngester } from '../../ingestion/knowledge-ingester.js';\nimport type { StatusCache } from '../status-cache.js';\nimport { verboseResponse } from '../../config/tool-verbosity-config.js';\n\n/**\n * Registers the ingest_knowledge tool on the MCP server.\n *\n * ingest_knowledge transforms structured markdown documents into per-project\n * reference observations. Supports optional directory path; auto-detects\n * .planning/codebase/ or .laminark/codebase/ from project metadata when\n * directory is omitted.\n */\nexport function registerIngestKnowledge(\n server: McpServer,\n db: BetterSqlite3.Database,\n projectHashRef: ProjectHashRef,\n notificationStore: NotificationStore | null = null,\n statusCache: StatusCache | null = null,\n): void {\n server.registerTool(\n 'ingest_knowledge',\n {\n title: 'Ingest Knowledge',\n description:\n 'Ingest structured markdown documents from a directory into queryable per-project memories. Reads .md files, splits by ## headings, and stores each section as a reference observation. Supports .planning/codebase/ (GSD output) and .laminark/codebase/.',\n inputSchema: {\n directory: z\n .string()\n .optional()\n .describe(\n 'Directory containing .md files to ingest. If omitted, auto-detects .planning/codebase/ or .laminark/codebase/ using the project path from project_metadata.',\n ),\n },\n },\n async (args) => {\n const projectHash = projectHashRef.current;\n try {\n let resolvedDir = args.directory;\n\n // If directory not provided, resolve from project_metadata\n if (!resolvedDir) {\n const row = db\n .prepare(\n 'SELECT project_path FROM project_metadata WHERE project_hash = ? ORDER BY last_seen_at DESC LIMIT 1',\n )\n .get(projectHash) as { project_path: string } | undefined;\n\n if (!row) {\n return {\n content: [\n {\n type: 'text' as const,\n text: 'Could not determine project path. Please provide the directory parameter explicitly.',\n },\n ],\n isError: true,\n };\n }\n\n const detected = KnowledgeIngester.detectKnowledgeDir(row.project_path);\n if (!detected) {\n return {\n content: [\n {\n type: 'text' as const,\n text: 'No knowledge directory found. Expected .planning/codebase/ or .laminark/codebase/ in the project root. Run /gsd:map-codebase first or provide a directory path.',\n },\n ],\n isError: true,\n };\n }\n\n resolvedDir = detected;\n }\n\n const ingester = new KnowledgeIngester(db, projectHash);\n const stats = await ingester.ingestDirectory(resolvedDir!);\n\n debug('mcp', 'ingest_knowledge: completed', {\n directory: resolvedDir,\n stats,\n });\n\n statusCache?.markDirty();\n\n const responseText = verboseResponse(\n `Ingested ${stats.filesProcessed} files: ${stats.sectionsCreated} sections created, ${stats.sectionsRemoved} stale sections removed.`,\n `Ingested ${stats.filesProcessed} file(s): ${stats.sectionsCreated} sections created, ${stats.sectionsRemoved} removed.`,\n `Ingested ${stats.filesProcessed} file(s) from ${resolvedDir}: ${stats.sectionsCreated} sections created, ${stats.sectionsRemoved} stale sections removed.`,\n );\n\n // Prepend any pending notifications to the response\n let finalResponse = responseText;\n if (notificationStore) {\n const pending = notificationStore.consumePending(projectHash);\n if (pending.length > 0) {\n const banner = pending.map(n => `[Laminark] ${n.message}`).join('\\n');\n finalResponse = banner + '\\n\\n' + finalResponse;\n }\n }\n\n return {\n content: [\n {\n type: 'text' as const,\n text: finalResponse,\n },\n ],\n };\n } catch (err) {\n const message =\n err instanceof Error ? err.message : 'Unknown error';\n return {\n content: [\n {\n type: 'text' as const,\n text: `Failed to ingest knowledge: ${message}`,\n },\n ],\n isError: true,\n };\n }\n },\n );\n}\n","import type { StashManager } from '../storage/stash-manager.js';\nimport type { StashObservation } from '../types/stash.js';\n\n/**\n * Result of the /laminark:resume command.\n */\nexport interface ResumeResult {\n success: boolean;\n message: string;\n context?: StashObservation[];\n}\n\n/**\n * Dependencies injected into the resume command handler.\n */\nexport interface ResumeDeps {\n stashManager: StashManager;\n}\n\n/**\n * Returns a human-readable relative time string from an ISO date.\n * Examples: \"just now\", \"2 minutes ago\", \"3 hours ago\", \"yesterday\", \"5 days ago\"\n */\nexport function timeAgo(dateString: string, now?: Date): string {\n const date = new Date(dateString);\n const ref = now ?? new Date();\n const diffMs = ref.getTime() - date.getTime();\n\n if (diffMs < 0) return 'just now';\n\n const seconds = Math.floor(diffMs / 1000);\n if (seconds < 60) return 'just now';\n\n const minutes = Math.floor(seconds / 60);\n if (minutes === 1) return '1 minute ago';\n if (minutes < 60) return `${minutes} minutes ago`;\n\n const hours = Math.floor(minutes / 60);\n if (hours === 1) return '1 hour ago';\n if (hours < 24) return `${hours} hours ago`;\n\n const days = Math.floor(hours / 24);\n if (days === 1) return 'yesterday';\n if (days < 30) return `${days} days ago`;\n\n const months = Math.floor(days / 30);\n if (months === 1) return '1 month ago';\n return `${months} months ago`;\n}\n\n/**\n * Truncates a string to maxLen characters, appending \"...\" if truncated.\n */\nfunction truncate(text: string, maxLen: number): string {\n if (text.length <= maxLen) return text;\n return text.slice(0, maxLen) + '...';\n}\n\n/**\n * Handles the /laminark:resume slash command.\n *\n * Two modes:\n * 1. List mode (no stashId): shows stashed context threads\n * 2. Resume mode (stashId provided): restores context from a specific stash\n */\nexport async function handleResumeCommand(\n args: { projectId: string; stashId?: string },\n deps: ResumeDeps,\n): Promise<ResumeResult> {\n const { stashManager } = deps;\n\n // Resume mode: restore a specific stash\n if (args.stashId) {\n const stash = stashManager.getStash(args.stashId);\n if (!stash) {\n return { success: false, message: `Stash not found: ${args.stashId}` };\n }\n\n stashManager.resumeStash(args.stashId);\n\n const count = stash.observationSnapshots.length;\n return {\n success: true,\n message: `Resumed: \"${stash.topicLabel}\"\\n\\nContext restored with ${count} observations.`,\n context: stash.observationSnapshots,\n };\n }\n\n // List mode: show available stashed threads\n const stashes = stashManager.listStashes(args.projectId, {\n status: 'stashed',\n limit: 5,\n });\n\n if (stashes.length === 0) {\n return { success: true, message: 'No stashed context threads found.' };\n }\n\n const lines = ['Stashed context threads:'];\n for (let i = 0; i < stashes.length; i++) {\n const s = stashes[i];\n const ago = timeAgo(s.createdAt);\n const summary = truncate(s.summary, 80);\n lines.push(`${i + 1}. ${s.topicLabel} (${ago}) - ${summary}`);\n }\n lines.push('');\n lines.push('Use /laminark:resume {id} to restore a thread.');\n\n return { success: true, message: lines.join('\\n') };\n}\n","import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type BetterSqlite3 from 'better-sqlite3';\nimport { z } from 'zod';\n\nimport { debug } from '../../shared/debug.js';\nimport type { ProjectHashRef } from '../../shared/types.js';\nimport { StashManager } from '../../storage/stash-manager.js';\nimport type { NotificationStore } from '../../storage/notifications.js';\nimport type { ContextStash } from '../../types/stash.js';\nimport { timeAgo } from '../../commands/resume.js';\nimport { loadToolVerbosityConfig } from '../../config/tool-verbosity-config.js';\n\n// ---------------------------------------------------------------------------\n// Formatting helpers (progressive disclosure)\n// ---------------------------------------------------------------------------\n\nfunction truncate(text: string, maxLen: number): string {\n if (text.length <= maxLen) return text;\n return text.slice(0, maxLen) + '...';\n}\n\n/**\n * Compact format: numbered list of topic labels with relative time.\n */\nfunction formatCompact(stashes: ContextStash[]): string {\n return stashes\n .map(\n (s, i) =>\n `${i + 1}. ${s.topicLabel} (${timeAgo(s.createdAt)})`,\n )\n .join('\\n');\n}\n\n/**\n * Detail format: topic labels with summaries.\n */\nfunction formatDetail(stashes: ContextStash[]): string {\n return stashes\n .map(\n (s, i) =>\n `${i + 1}. **${s.topicLabel}** (${timeAgo(s.createdAt)})\\n ${truncate(s.summary, 120)}`,\n )\n .join('\\n\\n');\n}\n\n/**\n * Full format: topic labels, summaries, observation count, and first few observation snippets.\n */\nfunction formatFull(stashes: ContextStash[]): string {\n return stashes\n .map((s, i) => {\n const lines = [\n `${i + 1}. **${s.topicLabel}** (${timeAgo(s.createdAt)})`,\n ` ${s.summary}`,\n ` Observations: ${s.observationSnapshots.length}`,\n ];\n\n // Show first 3 observation snippets\n const previews = s.observationSnapshots.slice(0, 3);\n for (const obs of previews) {\n lines.push(` - ${truncate(obs.content.replace(/\\n/g, ' '), 80)}`);\n }\n if (s.observationSnapshots.length > 3) {\n lines.push(\n ` ... and ${s.observationSnapshots.length - 3} more`,\n );\n }\n\n return lines.join('\\n');\n })\n .join('\\n\\n');\n}\n\n/**\n * Formats stashes using progressive disclosure based on count.\n * - 1-3 stashes: full detail\n * - 4-8 stashes: detail (summaries)\n * - 9+: compact (labels only)\n */\nexport function formatStashes(stashes: ContextStash[]): string {\n if (stashes.length <= 3) return formatFull(stashes);\n if (stashes.length <= 8) return formatDetail(stashes);\n return formatCompact(stashes);\n}\n\n// ---------------------------------------------------------------------------\n// Response helpers\n// ---------------------------------------------------------------------------\n\nfunction prependNotifications(\n notificationStore: NotificationStore | null,\n projectHash: string,\n responseText: string,\n): string {\n if (!notificationStore) return responseText;\n const pending = notificationStore.consumePending(projectHash);\n if (pending.length === 0) return responseText;\n const banner = pending.map(n => `[Laminark] ${n.message}`).join('\\n');\n return banner + '\\n\\n' + responseText;\n}\n\nfunction textResponse(text: string) {\n return { content: [{ type: 'text' as const, text }] };\n}\n\n// ---------------------------------------------------------------------------\n// registerTopicContext\n// ---------------------------------------------------------------------------\n\n/**\n * Registers the topic_context MCP tool.\n *\n * Shows recently stashed context threads. Used when the user asks\n * \"where was I?\" or wants to see abandoned conversation threads.\n */\nexport function registerTopicContext(\n server: McpServer,\n db: BetterSqlite3.Database,\n projectHashRef: ProjectHashRef,\n notificationStore: NotificationStore | null = null,\n): void {\n const stashManager = new StashManager(db);\n\n server.registerTool(\n 'topic_context',\n {\n title: 'Topic Context',\n description:\n \"Shows recently stashed context threads. Use when the user asks 'where was I?' or wants to see abandoned conversation threads.\",\n inputSchema: {\n query: z\n .string()\n .optional()\n .describe('Optional search query to filter threads by topic label or summary'),\n limit: z\n .number()\n .int()\n .min(1)\n .max(20)\n .default(5)\n .describe('Max threads to return'),\n },\n },\n async (args) => {\n const projectHash = projectHashRef.current;\n // Helper to wrap textResponse with pending notifications\n const withNotifications = (text: string) =>\n textResponse(prependNotifications(notificationStore, projectHash, text));\n\n try {\n debug('mcp', 'topic_context: request', { query: args.query, limit: args.limit });\n\n let stashes = stashManager.getRecentStashes(projectHash, args.limit);\n\n // Filter by query if provided (case-insensitive match on topicLabel or summary)\n if (args.query) {\n const q = args.query.toLowerCase();\n stashes = stashes.filter(\n (s) =>\n s.topicLabel.toLowerCase().includes(q) ||\n s.summary.toLowerCase().includes(q),\n );\n }\n\n if (stashes.length === 0) {\n return withNotifications(\n 'No stashed context threads found. You\\'re working in a single thread.',\n );\n }\n\n const verbosity = loadToolVerbosityConfig().level;\n\n if (verbosity === 1) {\n return withNotifications(`${stashes.length} stashed thread(s)`);\n }\n\n if (verbosity === 2) {\n // Standard: topic labels with relative time only\n const lines = stashes.map(\n (s, i) => `${i + 1}. ${s.topicLabel} (${timeAgo(s.createdAt)})`,\n );\n return withNotifications(lines.join('\\n'));\n }\n\n // Verbose: full output\n const formatted = formatStashes(stashes);\n const footer = `\\n---\\n${stashes.length} stashed thread(s) | Use /laminark:resume {id} to restore`;\n\n debug('mcp', 'topic_context: returning', { count: stashes.length });\n\n return withNotifications(formatted + footer);\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Unknown error';\n debug('mcp', 'topic_context: error', { error: message });\n return textResponse(`Error retrieving context threads: ${message}`);\n }\n },\n );\n}\n","/**\n * Type definitions for the knowledge graph.\n *\n * Defines a fixed entity/relationship taxonomy using const arrays and\n * derived union types (NOT enums) for better type inference and runtime\n * validation. Every Phase 7 module imports from this file.\n */\n\n// =============================================================================\n// Entity Type Taxonomy (FIXED -- no other types allowed)\n// =============================================================================\n\nexport const ENTITY_TYPES = [\n 'Project',\n 'File',\n 'Decision',\n 'Problem',\n 'Solution',\n 'Reference',\n] as const;\n\nexport type EntityType = (typeof ENTITY_TYPES)[number];\n\n// =============================================================================\n// Relationship Type Taxonomy (FIXED -- no other types allowed)\n// =============================================================================\n\nexport const RELATIONSHIP_TYPES = [\n 'related_to',\n 'solved_by',\n 'caused_by',\n 'modifies',\n 'informed_by',\n 'references',\n 'verified_by',\n 'preceded_by',\n] as const;\n\nexport type RelationshipType = (typeof RELATIONSHIP_TYPES)[number];\n\n// =============================================================================\n// Graph Node Interface\n// =============================================================================\n\n/**\n * A node in the knowledge graph representing a named entity.\n *\n * - id: UUID (hex-encoded randomBytes)\n * - type: one of the 6 entity types\n * - name: canonical name (e.g., \"src/auth/login.ts\" for File, \"Use JWT\" for Decision)\n * - metadata: flexible JSON for type-specific data\n * - observation_ids: source observations this entity was extracted from\n * - created_at / updated_at: ISO 8601 timestamps\n */\nexport interface GraphNode {\n id: string;\n type: EntityType;\n name: string;\n metadata: Record<string, unknown>;\n observation_ids: string[];\n created_at: string;\n updated_at: string;\n}\n\n// =============================================================================\n// Graph Edge Interface\n// =============================================================================\n\n/**\n * A directed edge in the knowledge graph connecting two nodes.\n *\n * - id: UUID (hex-encoded randomBytes)\n * - source_id / target_id: references to GraphNode.id\n * - type: one of the 8 relationship types\n * - weight: confidence/strength score between 0.0 and 1.0\n * - metadata: flexible JSON for relationship-specific data\n * - created_at: ISO 8601 timestamp\n */\nexport interface GraphEdge {\n id: string;\n source_id: string;\n target_id: string;\n type: RelationshipType;\n weight: number;\n metadata: Record<string, unknown>;\n created_at: string;\n}\n\n// =============================================================================\n// Type Guards\n// =============================================================================\n\n/**\n * Runtime type guard for EntityType.\n * Uses the ENTITY_TYPES const array for O(n) lookup (n=6, negligible).\n */\nexport function isEntityType(s: string): s is EntityType {\n return (ENTITY_TYPES as readonly string[]).includes(s);\n}\n\n/**\n * Runtime type guard for RelationshipType.\n * Uses the RELATIONSHIP_TYPES const array for O(n) lookup (n=8, negligible).\n */\nexport function isRelationshipType(s: string): s is RelationshipType {\n return (RELATIONSHIP_TYPES as readonly string[]).includes(s);\n}\n\n// =============================================================================\n// Constants\n// =============================================================================\n\n/**\n * Maximum number of edges a single node can have.\n * Used by constraint enforcement in Plan 05 to prevent\n * hub nodes from dominating the graph.\n */\nexport const MAX_NODE_DEGREE = 50;\n","/**\n * MCP tool handler for knowledge graph queries.\n *\n * Allows Claude to search the knowledge graph for entities by name or type,\n * traverse relationships to a configurable depth, and see linked observation\n * excerpts. Results use progressive disclosure: entity list with connection\n * counts, then relationships, then observation excerpts.\n */\n\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type BetterSqlite3 from 'better-sqlite3';\nimport { z } from 'zod';\n\nimport { debug } from '../../shared/debug.js';\nimport type { ProjectHashRef } from '../../shared/types.js';\nimport type { NotificationStore } from '../../storage/notifications.js';\nimport {\n type EntityType,\n type RelationshipType,\n ENTITY_TYPES,\n RELATIONSHIP_TYPES,\n isEntityType,\n isRelationshipType,\n} from '../../graph/types.js';\nimport type { GraphNode, GraphEdge } from '../../graph/types.js';\nimport {\n initGraphSchema,\n traverseFrom,\n getNodeByNameAndType,\n getEdgesForNode,\n type TraversalResult,\n} from '../../graph/schema.js';\nimport { loadToolVerbosityConfig } from '../../config/tool-verbosity-config.js';\n\n// =============================================================================\n// Types\n// =============================================================================\n\nexport interface QueryGraphInput {\n query: string;\n entity_type?: EntityType;\n depth?: number;\n relationship_types?: RelationshipType[];\n limit?: number;\n}\n\nexport interface QueryGraphOutput {\n entities: Array<{\n node: GraphNode;\n connectionCount: number;\n relationships: Array<{\n direction: 'outgoing' | 'incoming';\n type: RelationshipType;\n targetName: string;\n targetType: EntityType;\n }>;\n }>;\n observations: Array<{\n text: string;\n createdAt: string;\n }>;\n totalFound: number;\n}\n\n// =============================================================================\n// Internal Row Types\n// =============================================================================\n\ninterface NodeRow {\n id: string;\n type: string;\n name: string;\n metadata: string;\n observation_ids: string;\n created_at: string;\n updated_at: string;\n}\n\ninterface ObsSnippetRow {\n content: string;\n created_at: string;\n}\n\n// =============================================================================\n// Formatting Helpers\n// =============================================================================\n\nfunction truncateText(text: string, maxLen: number): string {\n if (text.length <= maxLen) return text;\n return text.slice(0, maxLen).trimEnd() + '...';\n}\n\nfunction formatEntityType(type: EntityType): string {\n return `[${type}]`;\n}\n\n/**\n * Formats query results as readable text for Claude consumption.\n * Uses progressive disclosure: entity list -> relationships -> observations.\n */\nfunction formatResults(\n rootNodes: GraphNode[],\n traversalsByNode: Map<string, TraversalResult[]>,\n observations: Array<{ text: string; createdAt: string }>,\n query: string,\n): string {\n const lines: string[] = [];\n\n // Section: Entities Found\n lines.push('## Entities Found');\n lines.push('');\n\n for (const node of rootNodes) {\n const traversals = traversalsByNode.get(node.id) ?? [];\n const connectionCount = traversals.length;\n\n lines.push(\n `- ${formatEntityType(node.type)} ${node.name} (${connectionCount} connection${connectionCount !== 1 ? 's' : ''})`,\n );\n\n // Show relationships\n for (const t of traversals) {\n if (!t.edge) continue;\n const direction = t.edge.source_id === node.id ? '->' : '<-';\n lines.push(\n ` ${direction} ${t.edge.type} ${formatEntityType(t.node.type)} ${t.node.name}`,\n );\n }\n\n lines.push('');\n }\n\n // Section: Related Observations\n if (observations.length > 0) {\n lines.push('## Related Observations');\n lines.push('');\n\n for (const obs of observations) {\n const age = formatAge(obs.createdAt);\n const snippet = truncateText(obs.text.replace(/\\n/g, ' '), 200);\n lines.push(`- \"${snippet}\" (${age})`);\n }\n }\n\n return lines.join('\\n').trim();\n}\n\n/**\n * Simple relative time formatting.\n */\nfunction formatAge(isoDate: string): string {\n const now = Date.now();\n const then = new Date(isoDate).getTime();\n const diffMs = now - then;\n\n const hours = Math.floor(diffMs / (1000 * 60 * 60));\n if (hours < 1) return 'just now';\n if (hours < 24) return `${hours} hour${hours !== 1 ? 's' : ''} ago`;\n\n const days = Math.floor(hours / 24);\n if (days < 30) return `${days} day${days !== 1 ? 's' : ''} ago`;\n\n const months = Math.floor(days / 30);\n return `${months} month${months !== 1 ? 's' : ''} ago`;\n}\n\n// =============================================================================\n// Response Helpers\n// =============================================================================\n\nfunction prependNotifications(\n notificationStore: NotificationStore | null,\n projectHash: string,\n responseText: string,\n): string {\n if (!notificationStore) return responseText;\n const pending = notificationStore.consumePending(projectHash);\n if (pending.length === 0) return responseText;\n const banner = pending.map((n) => `[Laminark] ${n.message}`).join('\\n');\n return banner + '\\n\\n' + responseText;\n}\n\nfunction textResponse(text: string) {\n return { content: [{ type: 'text' as const, text }] };\n}\n\nfunction errorResponse(text: string) {\n return { content: [{ type: 'text' as const, text }], isError: true };\n}\n\n// =============================================================================\n// Tool Registration\n// =============================================================================\n\n/**\n * Registers the query_graph MCP tool on the server.\n *\n * Allows Claude to search entities by name (exact or fuzzy), filter by type,\n * traverse relationships to configurable depth, and see linked observations.\n */\nexport function registerQueryGraph(\n server: McpServer,\n db: BetterSqlite3.Database,\n projectHashRef: ProjectHashRef,\n notificationStore: NotificationStore | null = null,\n): void {\n // Ensure graph schema is initialized\n initGraphSchema(db);\n\n server.registerTool(\n 'query_graph',\n {\n title: 'Query Knowledge Graph',\n description:\n \"Query the knowledge graph to find entities and their relationships. Use to answer questions like 'what files does this decision affect?' or 'what references informed this change?'\",\n inputSchema: {\n query: z\n .string()\n .min(1)\n .describe('Entity name or search text to look for'),\n entity_type: z\n .string()\n .optional()\n .describe(\n `Filter to entity type: ${ENTITY_TYPES.join(', ')}`,\n ),\n depth: z\n .number()\n .int()\n .min(1)\n .max(4)\n .default(2)\n .describe('Traversal depth (default: 2, max: 4)'),\n relationship_types: z\n .array(z.string())\n .optional()\n .describe(\n `Filter to relationship types: ${RELATIONSHIP_TYPES.join(', ')}`,\n ),\n limit: z\n .number()\n .int()\n .min(1)\n .max(50)\n .default(20)\n .describe('Max root entities to return (default: 20, max: 50)'),\n },\n },\n async (args) => {\n const projectHash = projectHashRef.current;\n const withNotifications = (text: string) =>\n textResponse(\n prependNotifications(notificationStore, projectHash, text),\n );\n\n try {\n debug('mcp', 'query_graph: request', {\n query: args.query,\n entity_type: args.entity_type,\n depth: args.depth,\n });\n\n // Validate entity_type if provided\n if (args.entity_type !== undefined && !isEntityType(args.entity_type)) {\n return errorResponse(\n `Invalid entity_type \"${args.entity_type}\". Valid types: ${ENTITY_TYPES.join(', ')}`,\n );\n }\n const entityType = args.entity_type as EntityType | undefined;\n\n // Validate relationship_types if provided\n if (args.relationship_types) {\n for (const rt of args.relationship_types) {\n if (!isRelationshipType(rt)) {\n return errorResponse(\n `Invalid relationship_type \"${rt}\". Valid types: ${RELATIONSHIP_TYPES.join(', ')}`,\n );\n }\n }\n }\n const relationshipTypes = args.relationship_types as\n | RelationshipType[]\n | undefined;\n\n // Search strategy: exact match first, then fuzzy LIKE search\n const rootNodes: GraphNode[] = [];\n\n // 1. Try exact name match (optionally filtered by type)\n if (entityType) {\n const exact = getNodeByNameAndType(db, args.query, entityType);\n if (exact) rootNodes.push(exact);\n } else {\n // Try exact match across all types\n for (const t of ENTITY_TYPES) {\n const exact = getNodeByNameAndType(db, args.query, t);\n if (exact) {\n rootNodes.push(exact);\n break; // Take first exact match\n }\n }\n }\n\n // 2. If no exact match, try case-insensitive LIKE search\n if (rootNodes.length === 0) {\n const likePattern = `%${args.query}%`;\n let sql: string;\n const params: unknown[] = [likePattern];\n\n if (entityType) {\n sql =\n 'SELECT * FROM graph_nodes WHERE name LIKE ? COLLATE NOCASE AND type = ? LIMIT ?';\n params.push(entityType, args.limit);\n } else {\n sql =\n 'SELECT * FROM graph_nodes WHERE name LIKE ? COLLATE NOCASE LIMIT ?';\n params.push(args.limit);\n }\n\n const rows = db.prepare(sql).all(...params) as NodeRow[];\n for (const row of rows) {\n rootNodes.push({\n id: row.id,\n type: row.type as EntityType,\n name: row.name,\n metadata: JSON.parse(row.metadata) as Record<string, unknown>,\n observation_ids: JSON.parse(row.observation_ids) as string[],\n created_at: row.created_at,\n updated_at: row.updated_at,\n });\n }\n }\n\n // No results found\n if (rootNodes.length === 0) {\n const suggestions = entityType\n ? `Try searching without the entity_type filter, or try a different name.`\n : `Try: entity types ${ENTITY_TYPES.join(', ')}`;\n return withNotifications(\n `No entities matching \"${args.query}\" found. ${suggestions}`,\n );\n }\n\n // 3. Traverse from each root node\n const traversalsByNode = new Map<string, TraversalResult[]>();\n\n for (const node of rootNodes) {\n const results = traverseFrom(db, node.id, {\n depth: args.depth,\n edgeTypes: relationshipTypes,\n direction: 'both',\n });\n traversalsByNode.set(node.id, results);\n }\n\n // 4. Collect observation snippets from root nodes\n const allObsIds = new Set<string>();\n for (const node of rootNodes) {\n for (const obsId of node.observation_ids) {\n allObsIds.add(obsId);\n }\n }\n\n const observations: Array<{ text: string; createdAt: string }> = [];\n if (allObsIds.size > 0) {\n const obsIdList = [...allObsIds];\n const placeholders = obsIdList.map(() => '?').join(', ');\n const obsRows = db\n .prepare(\n `SELECT content, created_at FROM observations WHERE id IN (${placeholders}) AND deleted_at IS NULL ORDER BY created_at DESC LIMIT 10`,\n )\n .all(...obsIdList) as ObsSnippetRow[];\n\n for (const row of obsRows) {\n observations.push({\n text: row.content,\n createdAt: row.created_at,\n });\n }\n }\n\n // 5. Format and return\n const verbosity = loadToolVerbosityConfig().level;\n\n if (verbosity === 1) {\n const totalTraversals = [...traversalsByNode.values()].reduce(\n (sum, arr) => sum + arr.length, 0);\n return withNotifications(\n `${rootNodes.length} entities, ${totalTraversals} connections found`,\n );\n }\n\n if (verbosity === 2) {\n // Standard: entity names and types, no observations\n const lines: string[] = [];\n lines.push('## Entities Found');\n lines.push('');\n for (const node of rootNodes) {\n const traversals = traversalsByNode.get(node.id) ?? [];\n lines.push(\n `- ${formatEntityType(node.type)} ${node.name} (${traversals.length} connections)`,\n );\n }\n return withNotifications(lines.join('\\n'));\n }\n\n // Verbose: full output\n const formatted = formatResults(\n rootNodes,\n traversalsByNode,\n observations,\n args.query,\n );\n\n debug('mcp', 'query_graph: returning', {\n rootNodes: rootNodes.length,\n totalTraversals: [...traversalsByNode.values()].reduce(\n (sum, arr) => sum + arr.length,\n 0,\n ),\n observations: observations.length,\n });\n\n return withNotifications(formatted);\n } catch (err) {\n const message =\n err instanceof Error ? err.message : 'Unknown error';\n debug('mcp', 'query_graph: error', { error: message });\n return errorResponse(`Graph query error: ${message}`);\n }\n },\n );\n}\n","/**\n * MCP tool handler for graph statistics and health metrics.\n *\n * Provides a dashboard view of the knowledge graph: node/edge counts,\n * entity type distribution, relationship type distribution, degree stats,\n * hotspots (nodes near edge limit), duplicate candidates, and staleness flags.\n *\n * No input parameters -- this is a read-only dashboard.\n */\n\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type BetterSqlite3 from 'better-sqlite3';\n\nimport { debug } from '../../shared/debug.js';\nimport type { ProjectHashRef } from '../../shared/types.js';\nimport type { NotificationStore } from '../../storage/notifications.js';\nimport {\n type EntityType,\n type RelationshipType,\n ENTITY_TYPES,\n RELATIONSHIP_TYPES,\n MAX_NODE_DEGREE,\n} from '../../graph/types.js';\nimport { initGraphSchema } from '../../graph/schema.js';\nimport { initStalenessSchema } from '../../graph/staleness.js';\n\n// =============================================================================\n// Types\n// =============================================================================\n\nexport interface GraphStatsOutput {\n total_nodes: number;\n total_edges: number;\n by_entity_type: Record<string, number>;\n by_relationship_type: Record<string, number>;\n avg_degree: number;\n max_degree: { node_name: string; node_type: EntityType; degree: number } | null;\n hotspots: Array<{ name: string; type: EntityType; degree: number }>;\n duplicate_candidates: number;\n staleness_flags: number;\n}\n\n// =============================================================================\n// Internal Row Types\n// =============================================================================\n\ninterface CountRow {\n type: string;\n cnt: number;\n}\n\ninterface DegreeRow {\n node_id: string;\n node_name: string;\n node_type: string;\n degree: number;\n}\n\n// =============================================================================\n// Stats Collection\n// =============================================================================\n\n/**\n * Collects comprehensive graph statistics directly from the database.\n * Does not depend on constraints module (which may not be built yet).\n */\nfunction collectGraphStats(db: BetterSqlite3.Database): GraphStatsOutput {\n // Total counts\n const totalNodes =\n (db.prepare('SELECT COUNT(*) as cnt FROM graph_nodes').get() as { cnt: number })\n .cnt;\n const totalEdges =\n (db.prepare('SELECT COUNT(*) as cnt FROM graph_edges').get() as { cnt: number })\n .cnt;\n\n // By entity type\n const entityCounts = db\n .prepare('SELECT type, COUNT(*) as cnt FROM graph_nodes GROUP BY type')\n .all() as CountRow[];\n const byEntityType: Record<string, number> = {};\n for (const t of ENTITY_TYPES) {\n byEntityType[t] = 0;\n }\n for (const row of entityCounts) {\n byEntityType[row.type] = row.cnt;\n }\n\n // By relationship type\n const relCounts = db\n .prepare('SELECT type, COUNT(*) as cnt FROM graph_edges GROUP BY type')\n .all() as CountRow[];\n const byRelType: Record<string, number> = {};\n for (const t of RELATIONSHIP_TYPES) {\n byRelType[t] = 0;\n }\n for (const row of relCounts) {\n byRelType[row.type] = row.cnt;\n }\n\n // Degree statistics\n const avgDegree =\n totalNodes > 0 ? (totalEdges * 2) / totalNodes : 0;\n\n // Max degree node -- count edges in both directions per node\n const degreeRows = db\n .prepare(\n `SELECT n.id as node_id, n.name as node_name, n.type as node_type,\n (SELECT COUNT(*) FROM graph_edges WHERE source_id = n.id OR target_id = n.id) as degree\n FROM graph_nodes n\n ORDER BY degree DESC\n LIMIT 10`,\n )\n .all() as DegreeRow[];\n\n let maxDegreeEntry: { node_name: string; node_type: EntityType; degree: number } | null =\n null;\n const hotspots: Array<{ name: string; type: EntityType; degree: number }> = [];\n const hotspotThreshold = Math.floor(MAX_NODE_DEGREE * 0.8); // 80% of limit\n\n for (const row of degreeRows) {\n if (!maxDegreeEntry || row.degree > maxDegreeEntry.degree) {\n maxDegreeEntry = {\n node_name: row.node_name,\n node_type: row.node_type as EntityType,\n degree: row.degree,\n };\n }\n if (row.degree >= hotspotThreshold) {\n hotspots.push({\n name: row.node_name,\n type: row.node_type as EntityType,\n degree: row.degree,\n });\n }\n }\n\n // Duplicate candidates: nodes with same name but different type\n const dupCount =\n (\n db\n .prepare(\n `SELECT COUNT(*) as cnt FROM (\n SELECT name FROM graph_nodes GROUP BY name HAVING COUNT(DISTINCT type) > 1\n )`,\n )\n .get() as { cnt: number }\n ).cnt;\n\n // Staleness flags count\n let stalenessCount = 0;\n try {\n initStalenessSchema(db);\n stalenessCount =\n (\n db\n .prepare(\n 'SELECT COUNT(*) as cnt FROM staleness_flags WHERE resolved = 0',\n )\n .get() as { cnt: number }\n ).cnt;\n } catch {\n // staleness_flags table may not exist yet\n stalenessCount = 0;\n }\n\n return {\n total_nodes: totalNodes,\n total_edges: totalEdges,\n by_entity_type: byEntityType,\n by_relationship_type: byRelType,\n avg_degree: Math.round(avgDegree * 10) / 10,\n max_degree: maxDegreeEntry,\n hotspots,\n duplicate_candidates: dupCount,\n staleness_flags: stalenessCount,\n };\n}\n\n// =============================================================================\n// Formatting\n// =============================================================================\n\n/**\n * Formats graph stats as a readable dashboard for Claude.\n */\nfunction formatStats(stats: GraphStatsOutput): string {\n const lines: string[] = [];\n\n // Header\n lines.push('## Knowledge Graph Stats');\n lines.push(\n `Nodes: ${stats.total_nodes} | Edges: ${stats.total_edges} | Avg degree: ${stats.avg_degree}`,\n );\n lines.push('');\n\n // Entity Distribution\n lines.push('### Entity Distribution');\n const entityParts: string[] = [];\n for (const t of ENTITY_TYPES) {\n const count = stats.by_entity_type[t] ?? 0;\n if (count > 0) {\n entityParts.push(`${t}: ${count}`);\n }\n }\n lines.push(entityParts.length > 0 ? entityParts.join(' | ') : 'No entities yet');\n lines.push('');\n\n // Relationship Distribution\n lines.push('### Relationship Distribution');\n const relParts: string[] = [];\n for (const t of RELATIONSHIP_TYPES) {\n const count = stats.by_relationship_type[t] ?? 0;\n if (count > 0) {\n relParts.push(`${t}: ${count}`);\n }\n }\n lines.push(relParts.length > 0 ? relParts.join(' | ') : 'No relationships yet');\n lines.push('');\n\n // Health\n lines.push('### Health');\n if (stats.hotspots.length > 0) {\n const hotspotStr = stats.hotspots\n .map((h) => `${h.name} (${h.degree} edges)`)\n .join(', ');\n lines.push(`Hotspots (near ${MAX_NODE_DEGREE}-edge limit): ${hotspotStr}`);\n } else {\n lines.push('Hotspots: none (all nodes well within edge limits)');\n }\n lines.push(`Duplicate candidates: ${stats.duplicate_candidates} name${stats.duplicate_candidates !== 1 ? 's' : ''}`);\n lines.push(`Stale observations: ${stats.staleness_flags}`);\n\n if (stats.max_degree) {\n lines.push('');\n lines.push(\n `Most connected: ${stats.max_degree.node_name} (${stats.max_degree.node_type}, ${stats.max_degree.degree} edges)`,\n );\n }\n\n return lines.join('\\n');\n}\n\n// =============================================================================\n// Response Helpers\n// =============================================================================\n\nfunction prependNotifications(\n notificationStore: NotificationStore | null,\n projectHash: string,\n responseText: string,\n): string {\n if (!notificationStore) return responseText;\n const pending = notificationStore.consumePending(projectHash);\n if (pending.length === 0) return responseText;\n const banner = pending.map((n) => `[Laminark] ${n.message}`).join('\\n');\n return banner + '\\n\\n' + responseText;\n}\n\nfunction textResponse(text: string) {\n return { content: [{ type: 'text' as const, text }] };\n}\n\n// =============================================================================\n// Tool Registration\n// =============================================================================\n\n/**\n * Registers the graph_stats MCP tool on the server.\n *\n * Returns comprehensive knowledge graph health metrics: entity/relationship\n * type distribution, degree statistics, hotspot nodes, duplicate candidates,\n * and staleness flags. No input parameters -- dashboard view.\n */\nexport function registerGraphStats(\n server: McpServer,\n db: BetterSqlite3.Database,\n projectHashRef: ProjectHashRef,\n notificationStore: NotificationStore | null = null,\n): void {\n // Ensure graph schema is initialized\n initGraphSchema(db);\n\n server.registerTool(\n 'graph_stats',\n {\n title: 'Graph Statistics',\n description:\n 'Get knowledge graph statistics: entity counts, relationship distribution, health metrics. Use to understand the state of accumulated knowledge.',\n inputSchema: {},\n },\n async () => {\n const projectHash = projectHashRef.current;\n try {\n debug('mcp', 'graph_stats: request');\n\n const stats = collectGraphStats(db);\n const formatted = formatStats(stats);\n\n debug('mcp', 'graph_stats: returning', {\n nodes: stats.total_nodes,\n edges: stats.total_edges,\n });\n\n return textResponse(\n prependNotifications(notificationStore, projectHash, formatted),\n );\n } catch (err) {\n const message =\n err instanceof Error ? err.message : 'Unknown error';\n debug('mcp', 'graph_stats: error', { error: message });\n return textResponse(`Graph stats error: ${message}`);\n }\n },\n );\n}\n","/**\n * MCP tool handler for database hygiene analysis and cleanup.\n *\n * Analyzes observations for deletion signals, scores candidates, and\n * optionally purges them. Default mode is simulate (dry-run).\n */\n\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type BetterSqlite3 from 'better-sqlite3';\nimport { z } from 'zod';\n\nimport { debug } from '../../shared/debug.js';\nimport type { ProjectHashRef } from '../../shared/types.js';\nimport type { NotificationStore } from '../../storage/notifications.js';\nimport {\n analyzeObservations,\n executePurge,\n type HygieneCandidate,\n type HygieneReport,\n} from '../../graph/hygiene-analyzer.js';\n\n// =============================================================================\n// Formatting\n// =============================================================================\n\nfunction formatReport(report: HygieneReport, mode: string, tier: string): string {\n const lines: string[] = [];\n\n lines.push('## Database Hygiene Report');\n lines.push(`Analyzed ${report.totalObservations.toLocaleString()} observations`);\n lines.push('');\n\n // Summary table\n lines.push('### Summary');\n lines.push('| Tier | Count | Action |');\n lines.push('|------|-------|--------|');\n lines.push(`| High (>= 0.7) | ${report.summary.high} | Safe to purge |`);\n lines.push(`| Medium (0.5-0.69) | ${report.summary.medium} | Review recommended |`);\n if (report.summary.low > 0) {\n lines.push(`| Low (< 0.5) | ${report.summary.low} | Kept |`);\n }\n lines.push(`| Orphan graph nodes | ${report.summary.orphanNodeCount} | Dead references |`);\n lines.push('');\n\n if (report.candidates.length === 0) {\n lines.push('No candidates found matching the selected tier.');\n return lines.join('\\n');\n }\n\n // Group candidates by session\n const bySession = new Map<string, HygieneCandidate[]>();\n for (const c of report.candidates) {\n const key = c.sessionId ?? '(no session)';\n const list = bySession.get(key) ?? [];\n list.push(c);\n bySession.set(key, list);\n }\n\n const tierLabel = tier === 'all' ? 'All' : tier === 'medium' ? 'Medium+' : 'High';\n lines.push(`### ${tierLabel} Confidence Candidates (showing ${report.candidates.length})`);\n lines.push('');\n\n for (const [sessionId, candidates] of bySession) {\n const sessionDate = candidates[0]?.createdAt?.substring(0, 10) ?? '';\n lines.push(`#### Session: ${sessionId.substring(0, 8)} (${sessionDate}, ${candidates.length} obs)`);\n lines.push('| ID | Kind | Source | Confidence | Signals | Preview |');\n lines.push('|----|------|--------|------------|---------|---------|');\n\n for (const c of candidates) {\n const signals: string[] = [];\n if (c.signals.orphaned) signals.push('orphaned');\n if (c.signals.islandNode) signals.push('island');\n if (c.signals.noiseClassified) signals.push('noise');\n if (c.signals.shortContent) signals.push('short');\n if (c.signals.autoCaptured) signals.push('auto');\n if (c.signals.stale) signals.push('stale');\n\n const preview = c.contentPreview\n .replace(/\\|/g, '\\\\|')\n .replace(/\\n/g, ' ');\n\n lines.push(\n `| ${c.shortId} | ${c.kind} | ${c.source} | ${c.confidence.toFixed(2)} | ${signals.join(',') || '-'} | ${preview} |`,\n );\n }\n lines.push('');\n }\n\n if (mode === 'simulate') {\n lines.push(`_Dry run — no data modified. Use \\`hygiene(mode=\"purge\", tier=\"${tier}\")\\` to execute._`);\n }\n\n return lines.join('\\n');\n}\n\nfunction formatPurgeResult(\n observationsPurged: number,\n orphanNodesRemoved: number,\n tier: string,\n): string {\n const lines: string[] = [];\n lines.push('## Hygiene Purge Complete');\n lines.push(`- Tier: ${tier}`);\n lines.push(`- Observations soft-deleted: ${observationsPurged}`);\n lines.push(`- Orphan graph nodes removed: ${orphanNodesRemoved}`);\n return lines.join('\\n');\n}\n\n// =============================================================================\n// Response Helpers\n// =============================================================================\n\nfunction prependNotifications(\n notificationStore: NotificationStore | null,\n projectHash: string,\n responseText: string,\n): string {\n if (!notificationStore) return responseText;\n const pending = notificationStore.consumePending(projectHash);\n if (pending.length === 0) return responseText;\n const banner = pending.map((n) => `[Laminark] ${n.message}`).join('\\n');\n return banner + '\\n\\n' + responseText;\n}\n\nfunction textResponse(text: string) {\n return { content: [{ type: 'text' as const, text }] };\n}\n\n// =============================================================================\n// Tool Registration\n// =============================================================================\n\nexport function registerHygiene(\n server: McpServer,\n db: BetterSqlite3.Database,\n projectHashRef: ProjectHashRef,\n notificationStore: NotificationStore | null = null,\n): void {\n server.registerTool(\n 'hygiene',\n {\n title: 'Database Hygiene',\n description:\n 'Analyze observations for deletion candidates with confidence scoring. ' +\n 'Simulate mode (default) produces a dry-run report. Purge mode soft-deletes candidates and removes dead orphan graph nodes.',\n inputSchema: {\n mode: z.enum(['simulate', 'purge']).default('simulate')\n .describe('simulate = dry-run report, purge = execute deletions'),\n tier: z.enum(['high', 'medium', 'all']).default('high')\n .describe('Which confidence tier to act on'),\n session_id: z.string().optional()\n .describe('Optional: scope analysis to a single session'),\n limit: z.number().int().min(1).max(200).default(50)\n .describe('Max results to return'),\n },\n },\n async (args) => {\n const projectHash = projectHashRef.current;\n try {\n const mode = args.mode ?? 'simulate';\n const tier = args.tier ?? 'high';\n const sessionId = args.session_id;\n const limit = args.limit ?? 50;\n\n debug('hygiene', 'Request', { mode, tier, sessionId, limit });\n\n // Determine minimum tier for analysis\n const minTier = tier === 'all' ? 'low' as const : tier;\n\n const report = analyzeObservations(db, projectHash, {\n sessionId,\n limit,\n minTier,\n });\n\n if (mode === 'purge') {\n const result = executePurge(db, projectHash, report, tier);\n const formatted = formatPurgeResult(\n result.observationsPurged,\n result.orphanNodesRemoved,\n tier,\n );\n return textResponse(\n prependNotifications(notificationStore, projectHash, formatted),\n );\n }\n\n const formatted = formatReport(report, mode, tier);\n return textResponse(\n prependNotifications(notificationStore, projectHash, formatted),\n );\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Unknown error';\n debug('hygiene', 'Error', { error: message });\n return textResponse(`Hygiene analysis error: ${message}`);\n }\n },\n );\n}\n","/**\n * MCP tool handler for Laminark system status.\n *\n * Returns a compact dashboard: connection info, memory counts,\n * token estimates, and capability flags. No input parameters.\n *\n * The heavy lifting (SQL queries, formatting) lives in StatusCache.\n * This handler just returns the pre-built string.\n */\n\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\n\nimport { debug } from '../../shared/debug.js';\nimport type { ProjectHashRef } from '../../shared/types.js';\nimport type { NotificationStore } from '../../storage/notifications.js';\nimport type { StatusCache } from '../status-cache.js';\nimport { loadToolVerbosityConfig } from '../../config/tool-verbosity-config.js';\n\n// =============================================================================\n// Response Helpers\n// =============================================================================\n\nfunction prependNotifications(\n notificationStore: NotificationStore | null,\n projectHash: string,\n responseText: string,\n): string {\n if (!notificationStore) return responseText;\n const pending = notificationStore.consumePending(projectHash);\n if (pending.length === 0) return responseText;\n const banner = pending.map((n) => `[Laminark] ${n.message}`).join('\\n');\n return banner + '\\n\\n' + responseText;\n}\n\nfunction textResponse(text: string) {\n return { content: [{ type: 'text' as const, text }] };\n}\n\n// =============================================================================\n// Tool Registration\n// =============================================================================\n\nexport function registerStatus(\n server: McpServer,\n cache: StatusCache,\n projectHashRef: ProjectHashRef,\n notificationStore: NotificationStore | null = null,\n): void {\n server.registerTool(\n 'status',\n {\n title: 'Laminark Status',\n description:\n 'Show Laminark system status: connection info, memory count, token estimates, and capabilities.',\n inputSchema: {},\n },\n async () => {\n const projectHash = projectHashRef.current;\n try {\n debug('mcp', 'status: request (cached)');\n\n const verbosity = loadToolVerbosityConfig().level;\n\n if (verbosity === 1) {\n return textResponse(\n prependNotifications(notificationStore, projectHash, 'Laminark: connected'),\n );\n }\n\n const formatted = cache.getFormatted();\n\n if (verbosity === 2) {\n // Standard: first few lines only (connection + counts)\n const lines = formatted.split('\\n').slice(0, 8);\n return textResponse(\n prependNotifications(notificationStore, projectHash, lines.join('\\n')),\n );\n }\n\n // Verbose: full output\n return textResponse(\n prependNotifications(notificationStore, projectHash, formatted),\n );\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Unknown error';\n debug('mcp', 'status: error', { error: message });\n return textResponse(`Status error: ${message}`);\n }\n },\n );\n}\n","/**\n * Pre-built status cache for the Laminark MCP status tool.\n *\n * Queries the database once at construction, stores the formatted markdown\n * string, and only re-queries when explicitly marked dirty (after writes).\n * The tool handler returns the cached string with zero SQL overhead.\n */\n\nimport type BetterSqlite3 from 'better-sqlite3';\n\nimport { debug } from '../shared/debug.js';\nimport type { ProjectHashRef } from '../shared/types.js';\nimport { getDbPath } from '../shared/config.js';\nimport { estimateTokens } from './token-budget.js';\n\n// =============================================================================\n// Uptime formatting\n// =============================================================================\n\nfunction formatUptime(seconds: number): string {\n const h = Math.floor(seconds / 3600);\n const m = Math.floor((seconds % 3600) / 60);\n const s = seconds % 60;\n if (h > 0) return `${h}h ${m}m`;\n if (m > 0) return `${m}m ${s}s`;\n return `${s}s`;\n}\n\n// =============================================================================\n// StatusCache\n// =============================================================================\n\nexport class StatusCache {\n private db: BetterSqlite3.Database;\n private projectHashRef: ProjectHashRef;\n private projectPath: string;\n private hasVectorSupport: boolean;\n private isWorkerReady: () => boolean;\n\n /** Pre-built markdown string (everything except the uptime line). */\n private cachedBody = '';\n /** Uptime snapshot at the time cachedBody was built. */\n private builtAtUptime = 0;\n private dirty = false;\n\n constructor(\n db: BetterSqlite3.Database,\n projectHashRef: ProjectHashRef,\n projectPath: string,\n hasVectorSupport: boolean,\n isWorkerReady: () => boolean,\n ) {\n this.db = db;\n this.projectHashRef = projectHashRef;\n this.projectPath = projectPath;\n this.hasVectorSupport = hasVectorSupport;\n this.isWorkerReady = isWorkerReady;\n\n this.rebuild();\n }\n\n /** Flag that underlying data has changed (cheap -- no queries). */\n markDirty(): void {\n this.dirty = true;\n }\n\n /** Re-query and rebuild if dirty. Call from a background timer. */\n refreshIfDirty(): void {\n if (!this.dirty) return;\n this.dirty = false;\n this.rebuild();\n }\n\n /**\n * Return the formatted status string instantly.\n * Patches the uptime line inline so it's always current.\n */\n getFormatted(): string {\n const currentUptime = formatUptime(Math.floor(process.uptime()));\n const workerReady = this.isWorkerReady();\n return this.cachedBody\n .replace(\n `Uptime: ${formatUptime(this.builtAtUptime)}`,\n `Uptime: ${currentUptime}`,\n )\n .replace(\n /Embedding worker: (?:ready|degraded)/,\n `Embedding worker: ${workerReady ? 'ready' : 'degraded'}`,\n );\n }\n\n // ---------------------------------------------------------------------------\n // Internal: query + format\n // ---------------------------------------------------------------------------\n\n private rebuild(): void {\n try {\n const ph = this.projectHashRef.current;\n\n const totalObs = (\n this.db.prepare(\n 'SELECT COUNT(*) as cnt FROM observations WHERE project_hash = ? AND deleted_at IS NULL',\n ).get(ph) as { cnt: number }\n ).cnt;\n\n const embeddedObs = (\n this.db.prepare(\n 'SELECT COUNT(*) as cnt FROM observations WHERE project_hash = ? AND deleted_at IS NULL AND embedding_model IS NOT NULL',\n ).get(ph) as { cnt: number }\n ).cnt;\n\n const deletedObs = (\n this.db.prepare(\n 'SELECT COUNT(*) as cnt FROM observations WHERE project_hash = ? AND deleted_at IS NOT NULL',\n ).get(ph) as { cnt: number }\n ).cnt;\n\n const sessions = (\n this.db.prepare(\n 'SELECT COUNT(DISTINCT session_id) as cnt FROM observations WHERE project_hash = ? AND session_id IS NOT NULL AND deleted_at IS NULL',\n ).get(ph) as { cnt: number }\n ).cnt;\n\n let stashes = 0;\n try {\n stashes = (\n this.db.prepare(\n \"SELECT COUNT(*) as cnt FROM context_stashes WHERE project_hash = ? AND status = 'stashed'\",\n ).get(ph) as { cnt: number }\n ).cnt;\n } catch {\n // Table may not exist\n }\n\n const totalChars = (\n this.db.prepare(\n 'SELECT COALESCE(SUM(LENGTH(content)), 0) as chars FROM observations WHERE project_hash = ? AND deleted_at IS NULL',\n ).get(ph) as { chars: number }\n ).chars;\n\n let graphNodes = 0;\n let graphEdges = 0;\n try {\n graphNodes = (\n this.db.prepare('SELECT COUNT(*) as cnt FROM graph_nodes').get() as { cnt: number }\n ).cnt;\n graphEdges = (\n this.db.prepare('SELECT COUNT(*) as cnt FROM graph_edges').get() as { cnt: number }\n ).cnt;\n } catch {\n // Graph tables may not exist\n }\n\n const uptimeNow = Math.floor(process.uptime());\n const tokenEstimate = estimateTokens(String('x').repeat(totalChars));\n const workerReady = this.isWorkerReady();\n\n // Build markdown\n const lines: string[] = [];\n lines.push('## Laminark Status');\n lines.push('');\n lines.push('### Connection');\n lines.push(`Project: ${this.projectPath}`);\n lines.push(`Project hash: ${ph}`);\n lines.push(`Database: ${getDbPath()}`);\n lines.push(`Uptime: ${formatUptime(uptimeNow)}`);\n lines.push('');\n lines.push('### Capabilities');\n lines.push(`Vector search: ${this.hasVectorSupport ? 'active' : 'unavailable (keyword-only)'}`);\n lines.push(`Embedding worker: ${workerReady ? 'ready' : 'degraded'}`);\n lines.push('');\n lines.push('### Memories');\n lines.push(`Observations: ${totalObs} (${embeddedObs} embedded, ${deletedObs} deleted)`);\n lines.push(`Sessions: ${sessions}`);\n lines.push(`Stashed threads: ${stashes}`);\n lines.push('');\n lines.push('### Tokens');\n lines.push(`Estimated total: ~${tokenEstimate.toLocaleString()} tokens across all memories`);\n lines.push('');\n lines.push('### Knowledge Graph');\n lines.push(`Nodes: ${graphNodes} | Edges: ${graphEdges}`);\n\n this.cachedBody = lines.join('\\n');\n this.builtAtUptime = uptimeNow;\n\n debug('mcp', 'status-cache: rebuilt', { memories: totalObs, tokens: tokenEstimate });\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n debug('mcp', 'status-cache: rebuild error', { error: msg });\n // Keep previous cached body on failure\n }\n }\n}\n","/**\n * MCP tool handler for discovering available tools by keyword or semantic search.\n *\n * Allows Claude to search the tool registry for MCP servers, slash commands,\n * skills, and plugins. Supports hybrid search (FTS5 keyword + vec0 vector)\n * with scope filtering and deduplication of server-level vs individual tool entries.\n */\n\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { z } from 'zod';\n\nimport { debug } from '../../shared/debug.js';\nimport type { ProjectHashRef } from '../../shared/types.js';\nimport type { ToolSearchResult } from '../../shared/tool-types.js';\nimport type { ToolRegistryRepository } from '../../storage/tool-registry.js';\nimport type { AnalysisWorker } from '../../analysis/worker-bridge.js';\nimport type { NotificationStore } from '../../storage/notifications.js';\nimport { enforceTokenBudget, TOKEN_BUDGET } from '../token-budget.js';\n\n// ---------------------------------------------------------------------------\n// Response helpers\n// ---------------------------------------------------------------------------\n\nfunction textResponse(text: string) {\n return { content: [{ type: 'text' as const, text }] };\n}\n\nfunction errorResponse(text: string) {\n return { content: [{ type: 'text' as const, text }], isError: true };\n}\n\nfunction prependNotifications(\n notificationStore: NotificationStore | null,\n projectHash: string,\n responseText: string,\n): string {\n if (!notificationStore) return responseText;\n const pending = notificationStore.consumePending(projectHash);\n if (pending.length === 0) return responseText;\n const banner = pending.map(n => `[Laminark] ${n.message}`).join('\\n');\n return banner + '\\n\\n' + responseText;\n}\n\n// ---------------------------------------------------------------------------\n// Formatting helpers\n// ---------------------------------------------------------------------------\n\nfunction formatToolResult(result: ToolSearchResult, index: number): string {\n const { tool, score } = result;\n const description = tool.description ? ` -- ${tool.description}` : '';\n const statusTag = tool.status !== 'active' ? ` [${tool.status}]` : '';\n const usageStr = tool.usage_count > 0 ? `${tool.usage_count} uses` : 'never used';\n const lastUsedStr = tool.last_used_at ? `last: ${tool.last_used_at.slice(0, 10)}` : 'never';\n return `${index}. ${tool.name}${statusTag}${description}\\n [${tool.scope}] | ${usageStr} | ${lastUsedStr} | score: ${score.toFixed(2)}`;\n}\n\n// ---------------------------------------------------------------------------\n// Tool registration\n// ---------------------------------------------------------------------------\n\n/**\n * Registers the discover_tools MCP tool on the server.\n *\n * Allows Claude to search the tool registry by keyword or semantic description,\n * with optional scope filtering. Returns ranked results with scope, usage count,\n * and last used timestamp metadata.\n */\nexport function registerDiscoverTools(\n server: McpServer,\n toolRegistry: ToolRegistryRepository,\n worker: AnalysisWorker | null,\n hasVectorSupport: boolean,\n notificationStore: NotificationStore | null,\n projectHashRef: ProjectHashRef,\n): void {\n server.registerTool(\n 'discover_tools',\n {\n title: 'Discover Tools',\n description:\n 'Search the tool registry to find available tools by keyword or description. Supports semantic search -- \"file manipulation\" finds tools described as \"read and write files\". Returns scope, usage count, and last used timestamp for each result.',\n inputSchema: {\n query: z\n .string()\n .min(1)\n .describe('Search query: keywords or natural language description'),\n scope: z\n .enum(['global', 'project', 'plugin'])\n .optional()\n .describe('Optional scope filter. Omit to search all scopes.'),\n limit: z\n .number()\n .int()\n .min(1)\n .max(50)\n .default(20)\n .describe('Maximum results to return (default: 20)'),\n },\n },\n async (args) => {\n const projectHash = projectHashRef.current;\n const withNotifications = (text: string) =>\n textResponse(prependNotifications(notificationStore, projectHash, text));\n\n try {\n debug('mcp', 'discover_tools: request', {\n query: args.query,\n scope: args.scope,\n limit: args.limit,\n });\n\n // Search the tool registry (hybrid FTS5 + vector via RRF)\n const searchResults = await toolRegistry.searchTools(args.query, {\n scope: args.scope,\n limit: args.limit,\n worker,\n hasVectorSupport,\n });\n\n // Zero results\n if (searchResults.length === 0) {\n const scopeContext = args.scope ? ` in scope \"${args.scope}\"` : '';\n return withNotifications(\n `No tools found matching \"${args.query}\"${scopeContext}.`,\n );\n }\n\n // Deduplicate: prefer mcp_server entries over individual mcp_tool entries\n const seenServers = new Set<string>();\n for (const result of searchResults) {\n if (result.tool.tool_type === 'mcp_server') {\n seenServers.add(result.tool.server_name ?? result.tool.name);\n }\n }\n const deduped = searchResults.filter(result => {\n if (\n result.tool.tool_type === 'mcp_tool' &&\n result.tool.server_name &&\n seenServers.has(result.tool.server_name)\n ) {\n return false;\n }\n return true;\n });\n\n // Format results with token budget enforcement\n const budgetResult = enforceTokenBudget(\n deduped,\n (r) => formatToolResult(r, deduped.indexOf(r) + 1),\n TOKEN_BUDGET,\n );\n\n const body = budgetResult.items\n .map((r, i) => formatToolResult(r, i + 1))\n .join('\\n');\n\n // Metadata footer\n const scopeLabel = args.scope ?? 'all';\n let footer = `---\\n${deduped.length} result(s) | query: \"${args.query}\" | scope: ${scopeLabel}`;\n if (budgetResult.truncated) {\n footer += ' | truncated';\n }\n\n debug('mcp', 'discover_tools: returning', {\n total: searchResults.length,\n deduped: deduped.length,\n displayed: budgetResult.items.length,\n truncated: budgetResult.truncated,\n });\n\n return withNotifications(`${body}\\n${footer}`);\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Unknown error';\n debug('mcp', 'discover_tools: error', { error: message });\n return errorResponse(`Discover tools error: ${message}`);\n }\n },\n );\n}\n","/**\n * MCP tool handler for bulk-registering available tools reported by Claude.\n *\n * At session start, Claude is prompted to call this tool with every tool it\n * has access to (built-in + MCP). This lets Laminark dynamically discover\n * the full tool surface without hardcoding names or relying on organic\n * PostToolUse discovery.\n */\n\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { z } from 'zod';\n\nimport { debug } from '../../shared/debug.js';\nimport type { ProjectHashRef } from '../../shared/types.js';\nimport type { ToolRegistryRepository } from '../../storage/tool-registry.js';\nimport { inferToolType, inferScope, extractServerName } from '../../hooks/tool-name-parser.js';\n\n// ---------------------------------------------------------------------------\n// Response helpers\n// ---------------------------------------------------------------------------\n\nfunction textResponse(text: string) {\n return { content: [{ type: 'text' as const, text }] };\n}\n\n// ---------------------------------------------------------------------------\n// Tool registration\n// ---------------------------------------------------------------------------\n\n/**\n * Registers the report_available_tools MCP tool on the server.\n *\n * Accepts an array of tool names (with optional descriptions) and upserts\n * each into the tool registry. Tool type, scope, and server name are inferred\n * from the tool name using the same parser as PostToolUse organic discovery.\n */\nexport function registerReportTools(\n server: McpServer,\n toolRegistry: ToolRegistryRepository,\n projectHashRef: ProjectHashRef,\n): void {\n server.registerTool(\n 'report_available_tools',\n {\n title: 'Report Available Tools',\n description:\n 'Register all tools available in this session with Laminark. Call this once at session start with every tool name you have access to (built-in and MCP). This populates the tool registry for discovery and routing.',\n inputSchema: {\n tools: z\n .array(\n z.object({\n name: z.string().min(1).describe('Tool name exactly as it appears (e.g., \"Read\", \"mcp__playwright__browser_click\")'),\n description: z.string().optional().describe('Brief description of the tool'),\n }),\n )\n .min(1)\n .describe('Array of tools available in this session'),\n },\n },\n async (args) => {\n const projectHash = projectHashRef.current;\n try {\n let registered = 0;\n let skipped = 0;\n\n for (const tool of args.tools) {\n // Skip Laminark's own tools — they're already registered\n if (\n tool.name.startsWith('mcp__plugin_laminark_') ||\n tool.name.startsWith('mcp__laminark__')\n ) {\n skipped++;\n continue;\n }\n\n const toolType = inferToolType(tool.name);\n const scope = inferScope(tool.name);\n const serverName = extractServerName(tool.name);\n\n toolRegistry.upsert({\n name: tool.name,\n toolType,\n scope,\n source: 'config:session-report',\n projectHash: scope === 'global' ? null : projectHash,\n description: tool.description ?? null,\n serverName,\n triggerHints: null,\n });\n registered++;\n }\n\n debug('mcp', 'report_available_tools: completed', {\n total: args.tools.length,\n registered,\n skipped,\n });\n\n return textResponse(\n `Registered ${registered} tools in the tool registry.${skipped > 0 ? ` Skipped ${skipped} Laminark tools (already known).` : ''}`,\n );\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Unknown error';\n debug('mcp', 'report_available_tools: error', { error: message });\n return {\n content: [{ type: 'text' as const, text: `Report tools error: ${message}` }],\n isError: true,\n };\n }\n },\n );\n}\n","/**\n * MCP tool handlers for debug resolution path management.\n *\n * Provides four tools for explicit user control over debug path lifecycle:\n * - path_start: Manually start tracking a debug path (UI-01)\n * - path_resolve: Manually resolve the active debug path (UI-02)\n * - path_show: Show a debug path with waypoints and KISS summary (UI-03)\n * - path_list: List recent debug paths with optional status filter (UI-04)\n */\n\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { z } from 'zod';\n\nimport { debug } from '../../shared/debug.js';\nimport type { ProjectHashRef } from '../../shared/types.js';\nimport type { NotificationStore } from '../../storage/notifications.js';\nimport type { PathRepository } from '../../paths/path-repository.js';\nimport type { PathTracker } from '../../paths/path-tracker.js';\nimport { loadToolVerbosityConfig, verboseResponse } from '../../config/tool-verbosity-config.js';\n\n// =============================================================================\n// Response Helpers\n// =============================================================================\n\nfunction prependNotifications(\n notificationStore: NotificationStore | null,\n projectHash: string,\n responseText: string,\n): string {\n if (!notificationStore) return responseText;\n const pending = notificationStore.consumePending(projectHash);\n if (pending.length === 0) return responseText;\n const banner = pending.map((n) => `[Laminark] ${n.message}`).join('\\n');\n return banner + '\\n\\n' + responseText;\n}\n\nfunction textResponse(text: string) {\n return { content: [{ type: 'text' as const, text }] };\n}\n\nfunction errorResponse(text: string) {\n return { content: [{ type: 'text' as const, text }], isError: true };\n}\n\n// =============================================================================\n// KISS Summary Formatting\n// =============================================================================\n\ninterface KissSummary {\n kiss_summary: string;\n root_cause: string;\n what_fixed_it: string;\n dimensions: {\n logical: string;\n programmatic: string;\n development: string;\n };\n}\n\nfunction formatKissSummary(raw: string | null): string {\n if (!raw) return 'KISS summary not yet generated';\n\n try {\n const kiss = JSON.parse(raw) as KissSummary;\n const lines: string[] = [];\n lines.push(`**Next time:** ${kiss.kiss_summary}`);\n lines.push(`**Root cause:** ${kiss.root_cause}`);\n lines.push(`**What fixed it:** ${kiss.what_fixed_it}`);\n lines.push(`**Logical:** ${kiss.dimensions.logical}`);\n lines.push(`**Programmatic:** ${kiss.dimensions.programmatic}`);\n lines.push(`**Development:** ${kiss.dimensions.development}`);\n return lines.join('\\n');\n } catch {\n return 'KISS summary not yet generated';\n }\n}\n\n// =============================================================================\n// Tool Registration\n// =============================================================================\n\n/**\n * Registers four debug path MCP tools on the server.\n *\n * Tools: path_start, path_resolve, path_show, path_list\n */\nexport function registerDebugPathTools(\n server: McpServer,\n pathRepo: PathRepository,\n pathTracker: PathTracker,\n notificationStore: NotificationStore | null,\n projectHashRef: ProjectHashRef,\n): void {\n // ---------------------------------------------------------------------------\n // path_start (UI-01)\n // ---------------------------------------------------------------------------\n\n server.registerTool(\n 'path_start',\n {\n title: 'Start Debug Path',\n description:\n \"Explicitly start tracking a debug path. Use when auto-detection hasn't triggered but you're actively debugging.\",\n inputSchema: {\n trigger: z\n .string()\n .describe('Brief description of the issue being debugged'),\n },\n },\n async (args) => {\n const projectHash = projectHashRef.current;\n const withNotifications = (text: string) =>\n textResponse(\n prependNotifications(notificationStore, projectHash, text),\n );\n\n try {\n debug('mcp', 'path_start: request', { trigger: args.trigger });\n\n const existingPathId = pathTracker.getActivePathId();\n const pathId = pathTracker.startManually(args.trigger);\n\n if (!pathId) {\n return errorResponse('Failed to start debug path');\n }\n\n if (existingPathId && existingPathId === pathId) {\n return withNotifications(`Debug path already active: ${pathId}`);\n }\n\n return withNotifications(verboseResponse(\n 'Debug path started.',\n `Debug path started: ${pathId}`,\n `Debug path started: ${pathId}\\nTracking: ${args.trigger}`,\n ));\n } catch (err) {\n const message =\n err instanceof Error ? err.message : 'Unknown error';\n debug('mcp', 'path_start: error', { error: message });\n return errorResponse(`path_start error: ${message}`);\n }\n },\n );\n\n // ---------------------------------------------------------------------------\n // path_resolve (UI-02)\n // ---------------------------------------------------------------------------\n\n server.registerTool(\n 'path_resolve',\n {\n title: 'Resolve Debug Path',\n description:\n \"Explicitly resolve the active debug path with a resolution summary. Use when auto-detection hasn't detected resolution.\",\n inputSchema: {\n resolution: z\n .string()\n .describe('What fixed the issue'),\n },\n },\n async (args) => {\n const projectHash = projectHashRef.current;\n const withNotifications = (text: string) =>\n textResponse(\n prependNotifications(notificationStore, projectHash, text),\n );\n\n try {\n debug('mcp', 'path_resolve: request', { resolution: args.resolution });\n\n const pathId = pathTracker.getActivePathId();\n if (!pathId) {\n return errorResponse('No active debug path to resolve');\n }\n\n pathTracker.resolveManually(args.resolution);\n\n return withNotifications(verboseResponse(\n 'Debug path resolved.',\n `Debug path resolved: ${pathId}`,\n `Debug path resolved: ${pathId}\\nResolution: ${args.resolution}\\nKISS summary generating in background...`,\n ));\n } catch (err) {\n const message =\n err instanceof Error ? err.message : 'Unknown error';\n debug('mcp', 'path_resolve: error', { error: message });\n return errorResponse(`path_resolve error: ${message}`);\n }\n },\n );\n\n // ---------------------------------------------------------------------------\n // path_show (UI-03)\n // ---------------------------------------------------------------------------\n\n server.registerTool(\n 'path_show',\n {\n title: 'Show Debug Path',\n description:\n 'Show a debug path with its waypoints and KISS summary.',\n inputSchema: {\n path_id: z\n .string()\n .optional()\n .describe('Path ID to show. Omit for active path.'),\n },\n },\n async (args) => {\n const projectHash = projectHashRef.current;\n const withNotifications = (text: string) =>\n textResponse(\n prependNotifications(notificationStore, projectHash, text),\n );\n\n try {\n debug('mcp', 'path_show: request', { path_id: args.path_id });\n\n let pathData;\n if (args.path_id) {\n pathData = pathRepo.getPath(args.path_id);\n if (!pathData) {\n return errorResponse(`Debug path not found: ${args.path_id}`);\n }\n } else {\n pathData = pathRepo.getActivePath();\n if (!pathData) {\n return errorResponse('No active debug path');\n }\n }\n\n const verbosity = loadToolVerbosityConfig().level;\n\n if (verbosity === 1) {\n return withNotifications(`Showing debug path: ${pathData.status}`);\n }\n\n const waypoints = pathRepo.getWaypoints(pathData.id);\n\n if (verbosity === 2) {\n // Standard: key fields\n const lines: string[] = [];\n lines.push(`## Debug Path: ${pathData.id}`);\n lines.push(`**Status:** ${pathData.status} | **Trigger:** ${pathData.trigger_summary}`);\n lines.push(`Waypoints: ${waypoints.length}`);\n if (pathData.resolution_summary) lines.push(`Resolution: ${pathData.resolution_summary}`);\n return withNotifications(lines.join('\\n'));\n }\n\n // Verbose: full output\n const lines: string[] = [];\n lines.push(`## Debug Path: ${pathData.id}`);\n lines.push(`Status: ${pathData.status}`);\n lines.push(`Started: ${pathData.started_at}`);\n lines.push(`Trigger: ${pathData.trigger_summary}`);\n lines.push('');\n\n // Waypoints section\n lines.push(`### Waypoints (${waypoints.length})`);\n for (let i = 0; i < waypoints.length; i++) {\n const wp = waypoints[i];\n lines.push(\n `${i + 1}. [${wp.waypoint_type}] ${wp.summary} (${wp.created_at})`,\n );\n }\n lines.push('');\n\n // Resolution section\n lines.push('### Resolution');\n lines.push(pathData.resolution_summary ?? 'Still active');\n lines.push('');\n\n // KISS summary section\n lines.push('### KISS Summary');\n lines.push(formatKissSummary(pathData.kiss_summary));\n\n return withNotifications(lines.join('\\n'));\n } catch (err) {\n const message =\n err instanceof Error ? err.message : 'Unknown error';\n debug('mcp', 'path_show: error', { error: message });\n return errorResponse(`path_show error: ${message}`);\n }\n },\n );\n\n // ---------------------------------------------------------------------------\n // path_list (UI-04)\n // ---------------------------------------------------------------------------\n\n server.registerTool(\n 'path_list',\n {\n title: 'List Debug Paths',\n description:\n 'List recent debug paths, optionally filtered by status.',\n inputSchema: {\n status: z\n .enum(['active', 'resolved', 'abandoned'])\n .optional()\n .describe('Filter by status'),\n limit: z\n .number()\n .int()\n .min(1)\n .max(50)\n .default(10)\n .describe('Max paths to return'),\n },\n },\n async (args) => {\n const projectHash = projectHashRef.current;\n const withNotifications = (text: string) =>\n textResponse(\n prependNotifications(notificationStore, projectHash, text),\n );\n\n try {\n debug('mcp', 'path_list: request', {\n status: args.status,\n limit: args.limit,\n });\n\n let paths = pathRepo.listPaths(args.limit);\n\n // In-memory status filter\n if (args.status) {\n paths = paths.filter((p) => p.status === args.status);\n }\n\n if (paths.length === 0) {\n return withNotifications('No debug paths found');\n }\n\n const verbosity = loadToolVerbosityConfig().level;\n\n if (verbosity === 1) {\n return withNotifications(`${paths.length} debug paths found`);\n }\n\n const lines: string[] = [];\n lines.push('## Debug Paths');\n lines.push('');\n\n if (verbosity === 2) {\n // Standard: compact table\n lines.push('| Status | Trigger |');\n lines.push('|--------|---------|');\n for (const p of paths) {\n const trigger = p.trigger_summary.length > 60\n ? p.trigger_summary.slice(0, 60) + '...'\n : p.trigger_summary;\n lines.push(`| ${p.status} | ${trigger} |`);\n }\n } else {\n // Verbose: full table\n lines.push(\n '| ID (short) | Status | Trigger | Started | Resolved |',\n );\n lines.push(\n '|------------|--------|---------|---------|----------|',\n );\n for (const p of paths) {\n const shortId = p.id.slice(0, 8);\n const trigger = p.trigger_summary.length > 50\n ? p.trigger_summary.slice(0, 50) + '...'\n : p.trigger_summary;\n const resolved = p.resolved_at ?? '-';\n lines.push(\n `| ${shortId} | ${p.status} | ${trigger} | ${p.started_at} | ${resolved} |`,\n );\n }\n }\n\n return withNotifications(lines.join('\\n'));\n } catch (err) {\n const message =\n err instanceof Error ? err.message : 'Unknown error';\n debug('mcp', 'path_list: error', { error: message });\n return errorResponse(`path_list error: ${message}`);\n }\n },\n );\n}\n","/**\n * MCP tool handlers for thought branch management.\n *\n * Provides three tools for querying work history:\n * - query_branches: List/search branches by status or type\n * - show_branch: Show branch detail with observation timeline\n * - branch_summary: Summary of recent work activity\n */\n\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { z } from 'zod';\n\nimport { debug } from '../../shared/debug.js';\nimport type { ProjectHashRef } from '../../shared/types.js';\nimport type { NotificationStore } from '../../storage/notifications.js';\nimport type { BranchRepository } from '../../branches/branch-repository.js';\nimport type { ObservationRepository } from '../../storage/observations.js';\nimport { loadToolVerbosityConfig } from '../../config/tool-verbosity-config.js';\n\n// =============================================================================\n// Response Helpers\n// =============================================================================\n\nfunction prependNotifications(\n notificationStore: NotificationStore | null,\n projectHash: string,\n responseText: string,\n): string {\n if (!notificationStore) return responseText;\n const pending = notificationStore.consumePending(projectHash);\n if (pending.length === 0) return responseText;\n const banner = pending.map((n) => `[Laminark] ${n.message}`).join('\\n');\n return banner + '\\n\\n' + responseText;\n}\n\nfunction textResponse(text: string) {\n return { content: [{ type: 'text' as const, text }] };\n}\n\nfunction errorResponse(text: string) {\n return { content: [{ type: 'text' as const, text }], isError: true };\n}\n\n// =============================================================================\n// Tool Registration\n// =============================================================================\n\nexport function registerThoughtBranchTools(\n server: McpServer,\n branchRepo: BranchRepository,\n obsRepo: ObservationRepository,\n notificationStore: NotificationStore | null,\n projectHashRef: ProjectHashRef,\n): void {\n // ---------------------------------------------------------------------------\n // query_branches\n // ---------------------------------------------------------------------------\n\n server.registerTool(\n 'query_branches',\n {\n title: 'Query Thought Branches',\n description:\n \"Search and list thought branches - coherent units of work (investigations, bug fixes, features). Use to see work history and what was investigated, fixed, or built.\",\n inputSchema: {\n status: z\n .enum(['active', 'completed', 'abandoned', 'merged'])\n .optional()\n .describe('Filter by branch status'),\n branch_type: z\n .enum(['investigation', 'bug_fix', 'feature', 'refactor', 'research', 'unknown'])\n .optional()\n .describe('Filter by branch type'),\n limit: z\n .number()\n .int()\n .min(1)\n .max(50)\n .default(10)\n .describe('Maximum results to return'),\n },\n },\n async (args) => {\n const projectHash = projectHashRef.current;\n const withNotifications = (text: string) =>\n textResponse(prependNotifications(notificationStore, projectHash, text));\n\n try {\n debug('mcp', 'query_branches: request', {\n status: args.status,\n branch_type: args.branch_type,\n limit: args.limit,\n });\n\n let branches;\n if (args.status) {\n branches = branchRepo.listByStatus(args.status, args.limit);\n } else if (args.branch_type) {\n branches = branchRepo.listByType(args.branch_type, args.limit);\n } else {\n branches = branchRepo.listBranches(args.limit);\n }\n\n if (branches.length === 0) {\n return withNotifications('No thought branches found');\n }\n\n const verbosity = loadToolVerbosityConfig().level;\n\n if (verbosity === 1) {\n return withNotifications(`${branches.length} branches found`);\n }\n\n const lines: string[] = [];\n lines.push('## Thought Branches');\n lines.push('');\n\n if (verbosity === 2) {\n // Standard: key columns, no observation timeline\n lines.push('| Status | Type | Title |');\n lines.push('|--------|------|-------|');\n for (const b of branches) {\n const title = b.title\n ? b.title.length > 50 ? b.title.slice(0, 50) + '...' : b.title\n : '-';\n lines.push(`| ${b.status} | ${b.branch_type} | ${title} |`);\n }\n } else {\n // Verbose: full table\n lines.push(\n '| ID (short) | Status | Type | Stage | Title | Observations | Started |',\n );\n lines.push(\n '|------------|--------|------|-------|-------|-------------|---------|',\n );\n for (const b of branches) {\n const shortId = b.id.slice(0, 8);\n const title = b.title\n ? b.title.length > 40\n ? b.title.slice(0, 40) + '...'\n : b.title\n : '-';\n lines.push(\n `| ${shortId} | ${b.status} | ${b.branch_type} | ${b.arc_stage} | ${title} | ${b.observation_count} | ${b.started_at} |`,\n );\n }\n }\n\n return withNotifications(lines.join('\\n'));\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Unknown error';\n debug('mcp', 'query_branches: error', { error: message });\n return errorResponse(`query_branches error: ${message}`);\n }\n },\n );\n\n // ---------------------------------------------------------------------------\n // show_branch\n // ---------------------------------------------------------------------------\n\n server.registerTool(\n 'show_branch',\n {\n title: 'Show Thought Branch',\n description:\n 'Show detailed view of a thought branch with observation timeline and arc stage annotations. Trace the full arc of a work unit.',\n inputSchema: {\n branch_id: z\n .string()\n .optional()\n .describe('Branch ID to show. Omit for active branch.'),\n },\n },\n async (args) => {\n const projectHash = projectHashRef.current;\n const withNotifications = (text: string) =>\n textResponse(prependNotifications(notificationStore, projectHash, text));\n\n try {\n debug('mcp', 'show_branch: request', { branch_id: args.branch_id });\n\n let branch;\n if (args.branch_id) {\n branch = branchRepo.getBranch(args.branch_id);\n if (!branch) {\n return errorResponse(`Branch not found: ${args.branch_id}`);\n }\n } else {\n branch = branchRepo.getActiveBranch();\n if (!branch) {\n return errorResponse('No active thought branch');\n }\n }\n\n const verbosity = loadToolVerbosityConfig().level;\n const branchTitle = branch.title ?? branch.id.slice(0, 12);\n\n if (verbosity === 1) {\n return withNotifications(`Showing \"${branchTitle}\"`);\n }\n\n const observations = branchRepo.getObservations(branch.id);\n\n if (verbosity === 2) {\n // Standard: key fields, no observation timeline\n const lines: string[] = [];\n lines.push(`## ${branchTitle}`);\n lines.push(`**Status:** ${branch.status} | **Type:** ${branch.branch_type} | **Stage:** ${branch.arc_stage}`);\n if (branch.summary) lines.push(branch.summary);\n lines.push(`Observations: ${observations.length}`);\n return withNotifications(lines.join('\\n'));\n }\n\n // Verbose: full output\n const lines: string[] = [];\n lines.push(`## Thought Branch: ${branchTitle}`);\n lines.push(`**ID:** ${branch.id}`);\n lines.push(`**Status:** ${branch.status}`);\n lines.push(`**Type:** ${branch.branch_type}`);\n lines.push(`**Arc Stage:** ${branch.arc_stage}`);\n lines.push(`**Started:** ${branch.started_at}`);\n if (branch.ended_at) lines.push(`**Ended:** ${branch.ended_at}`);\n if (branch.trigger_source) lines.push(`**Trigger:** ${branch.trigger_source}`);\n if (branch.linked_debug_path_id) {\n lines.push(`**Linked Debug Path:** ${branch.linked_debug_path_id}`);\n }\n lines.push('');\n\n // Tool pattern\n const tools = Object.entries(branch.tool_pattern)\n .sort(([, a], [, b]) => b - a);\n if (tools.length > 0) {\n lines.push('### Tool Usage');\n for (const [tool, count] of tools) {\n lines.push(`- ${tool}: ${count}`);\n }\n lines.push('');\n }\n\n // Summary\n if (branch.summary) {\n lines.push('### Summary');\n lines.push(branch.summary);\n lines.push('');\n }\n\n // Observation timeline\n lines.push(`### Observation Timeline (${observations.length})`);\n for (const bo of observations) {\n const obs = obsRepo.getById(bo.observation_id);\n const content = obs\n ? (obs.title ?? obs.content.slice(0, 100))\n : bo.observation_id.slice(0, 8);\n const stageTag = bo.arc_stage_at_add ? `[${bo.arc_stage_at_add}]` : '';\n const toolTag = bo.tool_name ? `(${bo.tool_name})` : '';\n lines.push(\n `${bo.sequence_order}. ${stageTag} ${toolTag} ${content}`,\n );\n }\n\n return withNotifications(lines.join('\\n'));\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Unknown error';\n debug('mcp', 'show_branch: error', { error: message });\n return errorResponse(`show_branch error: ${message}`);\n }\n },\n );\n\n // ---------------------------------------------------------------------------\n // branch_summary\n // ---------------------------------------------------------------------------\n\n server.registerTool(\n 'branch_summary',\n {\n title: 'Branch Activity Summary',\n description:\n 'Summary of recent work activity grouped by time window. Shows what was investigated, fixed, built, and where work left off.',\n inputSchema: {\n hours: z\n .number()\n .int()\n .min(1)\n .max(168)\n .default(24)\n .describe('Time window in hours (default 24)'),\n },\n },\n async (args) => {\n const projectHash = projectHashRef.current;\n const withNotifications = (text: string) =>\n textResponse(prependNotifications(notificationStore, projectHash, text));\n\n try {\n debug('mcp', 'branch_summary: request', { hours: args.hours });\n\n const branches = branchRepo.listRecentBranches(args.hours);\n\n if (branches.length === 0) {\n return withNotifications(`No work branches in the last ${args.hours} hours`);\n }\n\n const verbosity = loadToolVerbosityConfig().level;\n\n if (verbosity === 1) {\n return withNotifications(`${branches.length} branches in ${args.hours}h`);\n }\n\n // Group by status\n const active = branches.filter(b => b.status === 'active');\n const completed = branches.filter(b => b.status === 'completed');\n const abandoned = branches.filter(b => b.status === 'abandoned');\n\n const lines: string[] = [];\n lines.push(`## Work Summary (last ${args.hours}h)`);\n lines.push(`**Total branches:** ${branches.length}`);\n lines.push('');\n\n if (active.length > 0) {\n lines.push('### Active');\n for (const b of active) {\n const title = b.title ?? b.id.slice(0, 8);\n lines.push(verbosity === 2\n ? `- ${title} (${b.branch_type})`\n : `- **${title}** (${b.branch_type}, ${b.arc_stage}) — ${b.observation_count} obs`);\n }\n lines.push('');\n }\n\n if (completed.length > 0) {\n lines.push('### Completed');\n for (const b of completed) {\n const title = b.title ?? b.id.slice(0, 8);\n const summary = b.summary ? `: ${b.summary.slice(0, 100)}` : '';\n lines.push(verbosity === 2\n ? `- ${title} (${b.branch_type})`\n : `- **${title}** (${b.branch_type})${summary}`);\n }\n lines.push('');\n }\n\n if (abandoned.length > 0) {\n lines.push('### Abandoned');\n for (const b of abandoned) {\n const title = b.title ?? b.id.slice(0, 8);\n lines.push(verbosity === 2\n ? `- ${title} (${b.branch_type})`\n : `- **${title}** (${b.branch_type}) — ${b.observation_count} obs`);\n }\n lines.push('');\n }\n\n // Tool distribution only at verbose level\n if (verbosity === 3) {\n const allTools: Record<string, number> = {};\n for (const b of branches) {\n for (const [tool, count] of Object.entries(b.tool_pattern)) {\n allTools[tool] = (allTools[tool] ?? 0) + count;\n }\n }\n const toolEntries = Object.entries(allTools).sort(([, a], [, b]) => b - a);\n if (toolEntries.length > 0) {\n lines.push('### Tool Distribution');\n for (const [tool, count] of toolEntries.slice(0, 10)) {\n lines.push(`- ${tool}: ${count}`);\n }\n }\n }\n\n return withNotifications(lines.join('\\n'));\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Unknown error';\n debug('mcp', 'branch_summary: error', { error: message });\n return errorResponse(`branch_summary error: ${message}`);\n }\n },\n );\n}\n","/**\n * Infers arc stage from tool pattern counts within a branch.\n *\n * No LLM call -- deterministic based on tool usage ratios.\n *\n * Classification sources (in priority order):\n * 1. Built-in tool table (hardcoded, always correct)\n * 2. Registry-primed cache (from tool_registry descriptions at startup)\n * 3. Name-pattern fallback (regex heuristic for tools not yet in registry)\n *\n * The cache is re-primed whenever the tool registry changes (detected by\n * row count delta). BranchTracker calls `primeFromRegistry()` on startup\n * and during periodic maintenance.\n */\n\nimport type BetterSqlite3 from 'better-sqlite3';\nimport type { ArcStage } from './types.js';\nimport { debug } from '../shared/debug.js';\n\n// ---------------------------------------------------------------------------\n// Arc Category\n// ---------------------------------------------------------------------------\n\nexport type ArcCategory = 'investigation' | 'write' | 'verification' | 'planning' | 'uncategorized';\n\n// ---------------------------------------------------------------------------\n// Built-in Tool Categories (fast path, always correct)\n// ---------------------------------------------------------------------------\n\nconst BUILTIN_CATEGORY: Record<string, ArcCategory> = {\n // Investigation\n 'Read': 'investigation',\n 'Glob': 'investigation',\n 'Grep': 'investigation',\n 'WebSearch': 'investigation',\n 'WebFetch': 'investigation',\n 'Task': 'investigation',\n 'AskUserQuestion': 'investigation',\n\n // Write/execution\n 'Write': 'write',\n 'Edit': 'write',\n 'NotebookEdit': 'write',\n\n // Verification\n 'Bash': 'verification',\n\n // Planning\n 'EnterPlanMode': 'planning',\n 'ExitPlanMode': 'planning',\n 'TaskCreate': 'planning',\n 'TaskUpdate': 'planning',\n 'TaskList': 'planning',\n 'TaskGet': 'planning',\n 'Skill': 'uncategorized',\n};\n\n// ---------------------------------------------------------------------------\n// Description-keyword classification\n// ---------------------------------------------------------------------------\n\n/** Keywords matched against tool descriptions (case-insensitive). */\nconst DESCRIPTION_RULES: Array<{ category: ArcCategory; keywords: RegExp }> = [\n // Planning first (most specific)\n { category: 'planning', keywords: /\\b(plan|todo|task|roadmap|milestone|phase|design|architect)\\b/i },\n\n // Verification\n { category: 'verification', keywords: /\\b(run|test|build|execute|evaluate|validate|verify|check|assert|lint|compile)\\b/i },\n\n // Write/mutation\n { category: 'write', keywords: /\\b(write|edit|create|update|save|upload|modify|delete|remove|fill|type|click|select|drag|press|submit|install|deploy|push|commit|insert|drop|replace)\\b/i },\n\n // Investigation (broadest)\n { category: 'investigation', keywords: /\\b(read|search|query|find|list|get|fetch|browse|snapshot|screenshot|inspect|show|view|discover|status|stats|navigate|hover|recall|monitor|log|trace|debug|profile|measure|analyze|explore)\\b/i },\n];\n\n/**\n * Classify a tool from its description text.\n * Returns null if no confident match.\n */\nfunction classifyFromDescription(description: string): ArcCategory | null {\n for (const rule of DESCRIPTION_RULES) {\n if (rule.keywords.test(description)) {\n return rule.category;\n }\n }\n return null;\n}\n\n// ---------------------------------------------------------------------------\n// Name-pattern fallback (for tools not in registry)\n// ---------------------------------------------------------------------------\n\nconst NAME_RULES: Array<{ category: ArcCategory; pattern: RegExp }> = [\n { category: 'planning', pattern: /\\b(plan|todo|task|roadmap|phase|milestone)\\b/i },\n { category: 'verification', pattern: /\\b(run|test|build|exec|evaluate|validate|check|verify)\\b/i },\n { category: 'write', pattern: /\\b(write|edit|create|update|save|upload|fill|type|click|select|drag|press|install)\\b/i },\n { category: 'investigation', pattern: /\\b(search|query|find|list|get|read|fetch|browse|snapshot|screenshot|inspect|show|view|recall|discover|status|stats|console|network|navigate|tabs|hover)\\b/i },\n];\n\nfunction classifyFromName(toolName: string): ArcCategory {\n // For MCP tools, extract the action part after the last `__`\n const actionPart = toolName.includes('__')\n ? toolName.substring(toolName.lastIndexOf('__') + 2)\n : toolName;\n\n for (const rule of NAME_RULES) {\n if (rule.pattern.test(actionPart)) return rule.category;\n }\n\n // Laminark's own tools are investigation\n if (toolName.includes('laminark')) return 'investigation';\n\n return 'uncategorized';\n}\n\n// ---------------------------------------------------------------------------\n// Cache state\n// ---------------------------------------------------------------------------\n\nconst classificationCache = new Map<string, ArcCategory>();\nlet lastRegistryCount = -1;\n\n// ---------------------------------------------------------------------------\n// Public: prime cache from tool registry\n// ---------------------------------------------------------------------------\n\n/**\n * Re-reads the tool_registry table and classifies every tool by its\n * description. Only rescans when the registry row count has changed.\n *\n * Call on startup and periodically (e.g., during BranchTracker maintenance).\n */\nexport function primeFromRegistry(db: BetterSqlite3.Database, projectHash: string): void {\n try {\n const countRow = db.prepare('SELECT COUNT(*) AS cnt FROM tool_registry').get() as { cnt: number } | undefined;\n const currentCount = countRow?.cnt ?? 0;\n\n // Skip if registry hasn't changed\n if (currentCount === lastRegistryCount && lastRegistryCount >= 0) return;\n\n const rows = db.prepare(`\n SELECT name, description FROM tool_registry\n WHERE status = 'active'\n AND (scope = 'global' OR project_hash IS NULL OR project_hash = ?)\n `).all(projectHash) as Array<{ name: string; description: string | null }>;\n\n let primed = 0;\n for (const row of rows) {\n // Don't override built-in classifications\n if (BUILTIN_CATEGORY[row.name]) continue;\n\n let category: ArcCategory | null = null;\n\n // Try description first (best signal)\n if (row.description) {\n category = classifyFromDescription(row.description);\n }\n\n // Fall back to name patterns\n if (!category) {\n category = classifyFromName(row.name);\n }\n\n classificationCache.set(row.name, category);\n primed++;\n }\n\n lastRegistryCount = currentCount;\n debug('branches', 'Arc detector cache primed from registry', {\n registryTools: rows.length,\n primed,\n });\n } catch {\n // tool_registry may not exist yet (pre-migration-16)\n }\n}\n\n// ---------------------------------------------------------------------------\n// Public: classify a single tool\n// ---------------------------------------------------------------------------\n\n/**\n * Classify any tool name into an arc category.\n *\n * Priority: built-in table > registry-primed cache > name-pattern fallback.\n */\nexport function classifyTool(toolName: string): ArcCategory {\n // 1. Check cache (includes both built-in and registry-primed entries)\n const cached = classificationCache.get(toolName);\n if (cached) return cached;\n\n // 2. Built-in exact match\n const builtin = BUILTIN_CATEGORY[toolName];\n if (builtin) {\n classificationCache.set(toolName, builtin);\n return builtin;\n }\n\n // 3. Name-pattern fallback (tool not in registry yet)\n const fromName = classifyFromName(toolName);\n classificationCache.set(toolName, fromName);\n return fromName;\n}\n\n// ---------------------------------------------------------------------------\n// Public: infer arc stage\n// ---------------------------------------------------------------------------\n\n/**\n * Infers the current arc stage from tool usage pattern counts.\n *\n * Handles all tool types: builtins, MCP tools, plugins, skills, slash commands.\n * Uncategorized tools are excluded from ratio calculations so they don't\n * dilute the signal from known tools.\n *\n * @param toolPattern - Map of tool name to usage count within the branch\n * @param classification - Optional dominant observation classification\n * @returns The inferred arc stage\n */\nexport function inferArcStage(\n toolPattern: Record<string, number>,\n classification?: string | null,\n): ArcStage {\n let investigationCount = 0;\n let writeCount = 0;\n let verificationCount = 0;\n let planningCount = 0;\n let categorizedCount = 0;\n\n for (const [tool, count] of Object.entries(toolPattern)) {\n const category = classifyTool(tool);\n switch (category) {\n case 'investigation':\n investigationCount += count;\n categorizedCount += count;\n break;\n case 'write':\n writeCount += count;\n categorizedCount += count;\n break;\n case 'verification':\n verificationCount += count;\n categorizedCount += count;\n break;\n case 'planning':\n planningCount += count;\n categorizedCount += count;\n break;\n case 'uncategorized':\n // Excluded from ratios so unknown tools don't dilute the signal\n break;\n }\n }\n\n if (categorizedCount === 0) return 'investigation';\n\n // Check for verification: Bash/test commands after writes\n if (verificationCount > 0 && writeCount > 0) {\n const verificationRatio = verificationCount / categorizedCount;\n if (verificationRatio > 0.2) return 'verification';\n }\n\n // Check for execution: Write/Edit dominates\n const writeRatio = writeCount / categorizedCount;\n if (writeRatio > 0.4) return 'execution';\n\n // Check for planning: plan mode or task creation\n if (planningCount > 0) {\n const planRatio = planningCount / categorizedCount;\n if (planRatio > 0.1) return 'planning';\n }\n\n // Check for diagnosis: observations classified as 'problem' with mixed read/write\n if (classification === 'problem' && writeCount > 0 && investigationCount > 0) {\n return 'diagnosis';\n }\n\n // Default: investigation (mostly reads)\n return 'investigation';\n}\n","/**\n * Haiku configuration.\n *\n * With the Claude Agent SDK, authentication is handled by the user's\n * Claude Code subscription -- no API key needed.\n */\n\nexport interface HaikuConfig {\n model: string;\n maxTokensPerCall: number;\n}\n\nexport function loadHaikuConfig(): HaikuConfig {\n return {\n model: 'claude-haiku-4-5-20251001',\n maxTokensPerCall: 1024,\n };\n}\n","/**\n * Shared Haiku client using Claude Agent SDK V2 session.\n *\n * Routes Haiku calls through the user's Claude Code subscription\n * instead of requiring a separate API key. Uses a persistent session\n * to avoid 12s cold-start overhead on sequential calls.\n *\n * Provides the core infrastructure for all Haiku agent modules:\n * - callHaiku() helper for structured prompt/response calls\n * - extractJsonFromResponse() for defensive JSON parsing\n * - Session reuse across batch processing cycles\n */\n\nimport {\n unstable_v2_createSession,\n type SDKSession,\n} from '@anthropic-ai/claude-agent-sdk';\n\nimport { loadHaikuConfig } from '../config/haiku-config.js';\n\n// ---------------------------------------------------------------------------\n// Singleton state\n// ---------------------------------------------------------------------------\n\nlet _session: SDKSession | null = null;\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\nfunction getOrCreateSession(): SDKSession {\n if (!_session) {\n const config = loadHaikuConfig();\n _session = unstable_v2_createSession({\n model: config.model,\n permissionMode: 'bypassPermissions',\n allowedTools: [], // No tools -- pure text completion only\n });\n }\n return _session;\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Returns whether Haiku enrichment is available.\n * Always true with subscription auth -- no API key check needed.\n */\nexport function isHaikuEnabled(): boolean {\n return true;\n}\n\n/**\n * Calls Haiku with a system prompt and user content.\n * Returns the text content from the response.\n *\n * Uses a persistent V2 session to avoid cold-start overhead on sequential calls.\n * System prompt is embedded in the user message since session-level systemPrompt\n * is set at creation time and we need different prompts per agent.\n *\n * @param systemPrompt - Instructions for the model\n * @param userContent - The content to process\n * @param _maxTokens - Kept for signature compatibility (unused -- Agent SDK constrains output via prompts)\n * @throws Error if the Haiku call fails or session expires\n */\nexport async function callHaiku(\n systemPrompt: string,\n userContent: string,\n _maxTokens?: number,\n): Promise<string> {\n const session = getOrCreateSession();\n\n // Embed system prompt in user message since the session shares a single\n // system prompt but our three agents each need different instructions\n const fullPrompt = `<instructions>\\n${systemPrompt}\\n</instructions>\\n\\n${userContent}`;\n\n try {\n await session.send(fullPrompt);\n for await (const msg of session.stream()) {\n if (msg.type === 'result') {\n if (msg.subtype === 'success') {\n return msg.result;\n }\n const errors = 'errors' in msg ? (msg as { errors?: string[] }).errors : undefined;\n const errorMsg = errors?.join(', ') ?? msg.subtype;\n throw new Error(`Haiku call failed: ${errorMsg}`);\n }\n }\n return ''; // No result message received\n } catch (error) {\n // Session may have expired -- reset and rethrow so next call creates fresh session\n try {\n _session?.close();\n } catch {\n // Ignore close errors\n }\n _session = null;\n throw error;\n }\n}\n\n/**\n * Defensive JSON extraction from Haiku response text.\n *\n * Handles common LLM response quirks:\n * - Markdown code fences (```json ... ```)\n * - Explanatory text before/after JSON\n * - Both array and object JSON shapes\n *\n * @throws Error if no JSON structure found in text\n */\nexport function extractJsonFromResponse(text: string): unknown {\n // Strip markdown code fences\n const cleaned = text.replace(/```json\\s*/g, '').replace(/```\\s*/g, '');\n\n // Try to find JSON array\n const arrayMatch = cleaned.match(/\\[[\\s\\S]*\\]/);\n if (arrayMatch) return JSON.parse(arrayMatch[0]);\n\n // Try to find JSON object\n const objMatch = cleaned.match(/\\{[\\s\\S]*\\}/);\n if (objMatch) return JSON.parse(objMatch[0]);\n\n throw new Error('No JSON found in Haiku response');\n}\n\n/**\n * Resets the singleton session. Used for testing.\n */\nexport function resetHaikuClient(): void {\n try {\n _session?.close();\n } catch {\n // Ignore close errors\n }\n _session = null;\n}\n","/**\n * Haiku agent for classifying thought branch type and generating title/summary.\n *\n * Uses a single Haiku call to determine:\n * 1. Branch type (investigation, bug_fix, feature, refactor, research)\n * 2. A concise title for the branch\n * 3. An optional summary (for completed branches)\n *\n * Follows the same pattern as haiku-classifier-agent.ts.\n */\n\nimport { z } from 'zod';\n\nimport { callHaiku, extractJsonFromResponse } from '../intelligence/haiku-client.js';\nimport type { BranchType } from './types.js';\n\n// ---------------------------------------------------------------------------\n// Zod validation schemas\n// ---------------------------------------------------------------------------\n\nconst ClassifyBranchSchema = z.object({\n branch_type: z.enum(['investigation', 'bug_fix', 'feature', 'refactor', 'research']),\n title: z.string().max(100),\n});\n\nconst SummarizeBranchSchema = z.object({\n summary: z.string().max(500),\n});\n\n// ---------------------------------------------------------------------------\n// Exported types\n// ---------------------------------------------------------------------------\n\nexport type BranchClassification = {\n branch_type: BranchType;\n title: string;\n};\n\nexport type BranchSummaryResult = {\n summary: string;\n};\n\n// ---------------------------------------------------------------------------\n// System prompts\n// ---------------------------------------------------------------------------\n\nconst CLASSIFY_PROMPT = `You classify developer work branches for a knowledge management system.\n\nGiven a sequence of observations from a work session, determine:\n1. branch_type: What kind of work is this?\n - \"investigation\": Exploring code, reading docs, understanding behavior\n - \"bug_fix\": Fixing an error, test failure, or unexpected behavior\n - \"feature\": Building new functionality\n - \"refactor\": Restructuring existing code without changing behavior\n - \"research\": Looking up external resources, comparing approaches\n\n2. title: A concise title (3-8 words) describing the work unit. Use imperative form.\n Examples: \"Fix auth token refresh\", \"Add branch detection system\", \"Investigate memory leak\"\n\nReturn JSON: {\"branch_type\": \"...\", \"title\": \"...\"}\nNo markdown, no explanation, ONLY the JSON object.`;\n\nconst SUMMARIZE_PROMPT = `You summarize completed developer work branches for a knowledge management system.\n\nGiven a sequence of observations from a completed work branch, write a concise summary (1-3 sentences) that captures:\n- What was the goal\n- What was done\n- What was the outcome\n\nReturn JSON: {\"summary\": \"...\"}\nNo markdown, no explanation, ONLY the JSON object.`;\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Classifies a branch type and generates a title from observation content.\n */\nexport async function classifyBranchWithHaiku(\n observationTexts: string[],\n toolPattern: Record<string, number>,\n): Promise<BranchClassification> {\n const toolSummary = Object.entries(toolPattern)\n .sort(([, a], [, b]) => b - a)\n .map(([tool, count]) => `${tool}: ${count}`)\n .join(', ');\n\n const userContent = [\n `Tool usage: ${toolSummary}`,\n '',\n 'Observations:',\n ...observationTexts.slice(0, 10).map((t, i) => `${i + 1}. ${t.slice(0, 200)}`),\n ].join('\\n');\n\n const response = await callHaiku(CLASSIFY_PROMPT, userContent, 256);\n const parsed = extractJsonFromResponse(response);\n return ClassifyBranchSchema.parse(parsed);\n}\n\n/**\n * Generates a completion summary for a finished branch.\n */\nexport async function summarizeBranchWithHaiku(\n title: string,\n branchType: string,\n observationTexts: string[],\n): Promise<BranchSummaryResult> {\n const userContent = [\n `Branch: ${title} (${branchType})`,\n '',\n 'Observations:',\n ...observationTexts.slice(0, 15).map((t, i) => `${i + 1}. ${t.slice(0, 200)}`),\n ].join('\\n');\n\n const response = await callHaiku(SUMMARIZE_PROMPT, userContent, 256);\n const parsed = extractJsonFromResponse(response);\n return SummarizeBranchSchema.parse(parsed);\n}\n","/**\n * BranchTracker — state machine for automatic thought branch detection.\n *\n * Consumes observations from the HaikuProcessor pipeline and manages\n * the lifecycle of thought branches. Detects boundaries via:\n * - Topic shifts (from TopicShiftHandler)\n * - Project hash changes\n * - Session changes\n * - Time gaps (>15 min between observations)\n * - Manual starts\n *\n * Lives in the MCP server process and maintains in-memory state.\n * Persists branches and observations via BranchRepository.\n */\n\nimport type { BranchRepository } from './branch-repository.js';\nimport type { ArcStage, TriggerSource } from './types.js';\nimport { inferArcStage, primeFromRegistry } from './arc-detector.js';\nimport {\n classifyBranchWithHaiku,\n summarizeBranchWithHaiku,\n} from './branch-classifier-agent.js';\nimport { isHaikuEnabled } from '../intelligence/haiku-client.js';\nimport { ObservationRepository } from '../storage/observations.js';\nimport { debug } from '../shared/debug.js';\nimport type BetterSqlite3 from 'better-sqlite3';\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\nconst TIME_GAP_MS = 15 * 60 * 1000; // 15 minutes\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\ntype TrackerState = 'idle' | 'tracking';\n\nexport interface BranchObservationInput {\n id: string;\n content: string;\n source: string;\n projectHash: string;\n sessionId?: string | null;\n classification?: string | null;\n createdAt: string;\n}\n\n// ---------------------------------------------------------------------------\n// BranchTracker\n// ---------------------------------------------------------------------------\n\nexport class BranchTracker {\n private state: TrackerState = 'idle';\n private activeBranchId: string | null = null;\n private activeProjectHash: string | null = null;\n private activeSessionId: string | null = null;\n private lastObservationTime: number = 0;\n private toolPattern: Record<string, number> = {};\n\n private readonly repo: BranchRepository;\n private readonly db: BetterSqlite3.Database;\n private readonly projectHash: string;\n\n constructor(repo: BranchRepository, db: BetterSqlite3.Database, projectHash: string) {\n this.repo = repo;\n this.db = db;\n this.projectHash = projectHash;\n\n // Prime arc detector cache from tool registry descriptions\n primeFromRegistry(db, projectHash);\n\n // Recover state from DB on startup\n const activeBranch = repo.findRecentActiveBranch();\n if (activeBranch) {\n this.state = 'tracking';\n this.activeBranchId = activeBranch.id;\n this.activeProjectHash = activeBranch.project_hash;\n this.activeSessionId = activeBranch.session_id;\n this.toolPattern = activeBranch.tool_pattern;\n this.lastObservationTime = new Date(activeBranch.started_at).getTime();\n debug('branches', 'Recovered active branch from DB', { branchId: activeBranch.id });\n }\n }\n\n // ===========================================================================\n // Public API\n // ===========================================================================\n\n /**\n * Process a new observation through the boundary detection state machine.\n * Called from HaikuProcessor after classification (Step 1.6).\n */\n processObservation(obs: BranchObservationInput): void {\n const now = Date.now();\n const obsTime = new Date(obs.createdAt).getTime();\n const toolName = this.extractToolName(obs.source);\n\n // Check for boundary signals\n const boundary = this.detectBoundary(obs, obsTime);\n\n if (boundary) {\n // Complete current branch if tracking\n if (this.state === 'tracking' && this.activeBranchId) {\n this.completeBranch();\n }\n\n // Start new branch\n this.startBranch(boundary, obs);\n } else if (this.state === 'idle') {\n // First observation — start tracking\n this.startBranch('session_start', obs);\n }\n\n // Add observation to active branch\n if (this.activeBranchId) {\n const arcStage = inferArcStage(this.toolPattern, obs.classification);\n\n // Update tool pattern\n if (toolName) {\n this.toolPattern[toolName] = (this.toolPattern[toolName] ?? 0) + 1;\n this.repo.updateToolPattern(this.activeBranchId, this.toolPattern);\n }\n\n // Add observation\n this.repo.addObservation(\n this.activeBranchId,\n obs.id,\n toolName,\n arcStage,\n );\n\n // Update arc stage\n const newStage = inferArcStage(this.toolPattern, obs.classification);\n this.repo.updateArcStage(this.activeBranchId, newStage);\n }\n\n this.lastObservationTime = obsTime || now;\n this.activeProjectHash = obs.projectHash;\n this.activeSessionId = obs.sessionId ?? this.activeSessionId;\n }\n\n /**\n * Notify the tracker of a topic shift (from TopicShiftHandler).\n */\n onTopicShift(observationId: string): void {\n if (this.state === 'tracking' && this.activeBranchId) {\n this.completeBranch();\n // Start new branch with topic_shift trigger will happen on next observation\n debug('branches', 'Topic shift boundary detected', { observationId });\n }\n }\n\n /**\n * Link the active branch to a debug path (when PathTracker activates).\n */\n linkDebugPath(debugPathId: string): void {\n if (this.activeBranchId) {\n this.repo.linkDebugPath(this.activeBranchId, debugPathId);\n debug('branches', 'Linked debug path to branch', {\n branchId: this.activeBranchId,\n debugPathId,\n });\n }\n }\n\n /**\n * Get the active branch ID (for external callers).\n */\n getActiveBranchId(): string | null {\n return this.activeBranchId;\n }\n\n // ===========================================================================\n // Maintenance (called from HaikuProcessor Step 4)\n // ===========================================================================\n\n /**\n * Run periodic maintenance tasks:\n * - Classify branches with 3+ observations via Haiku\n * - Generate summaries for recently completed branches\n * - Auto-abandon stale branches (>24h)\n * - Link branches to debug paths\n */\n async runMaintenance(): Promise<void> {\n try {\n // Re-prime arc detector from registry (no-ops if registry unchanged)\n primeFromRegistry(this.db, this.projectHash);\n\n // Auto-abandon stale branches\n const stale = this.repo.findStaleBranches();\n for (const branch of stale) {\n this.repo.abandonBranch(branch.id);\n if (this.activeBranchId === branch.id) {\n this.state = 'idle';\n this.activeBranchId = null;\n this.toolPattern = {};\n }\n debug('branches', 'Auto-abandoned stale branch', { branchId: branch.id });\n }\n\n // Classify unclassified branches with Haiku\n if (isHaikuEnabled()) {\n const unclassified = this.repo.findUnclassifiedBranches(3);\n for (const branch of unclassified) {\n try {\n const observations = this.repo.getObservations(branch.id);\n const obsRepo = new ObservationRepository(this.db, branch.project_hash);\n const texts = observations\n .map(bo => {\n const obs = obsRepo.getById(bo.observation_id);\n return obs ? (obs.title ? `${obs.title}: ${obs.content}` : obs.content) : null;\n })\n .filter((t): t is string => t !== null);\n\n if (texts.length === 0) continue;\n\n const result = await classifyBranchWithHaiku(texts, branch.tool_pattern);\n this.repo.updateClassification(branch.id, result.branch_type, result.title);\n debug('branches', 'Branch classified', {\n branchId: branch.id,\n type: result.branch_type,\n title: result.title,\n });\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n debug('branches', 'Branch classification failed (non-fatal)', {\n branchId: branch.id,\n error: msg,\n });\n }\n }\n\n // Generate summaries for recently completed branches\n const unsummarized = this.repo.findRecentCompletedUnsummarized(2);\n for (const branch of unsummarized) {\n try {\n const observations = this.repo.getObservations(branch.id);\n const obsRepo = new ObservationRepository(this.db, branch.project_hash);\n const texts = observations\n .map(bo => {\n const obs = obsRepo.getById(bo.observation_id);\n return obs ? (obs.title ? `${obs.title}: ${obs.content}` : obs.content) : null;\n })\n .filter((t): t is string => t !== null);\n\n if (texts.length === 0) continue;\n\n const result = await summarizeBranchWithHaiku(\n branch.title ?? 'Untitled',\n branch.branch_type,\n texts,\n );\n this.repo.updateSummary(branch.id, result.summary);\n debug('branches', 'Branch summarized', { branchId: branch.id });\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n debug('branches', 'Branch summarization failed (non-fatal)', {\n branchId: branch.id,\n error: msg,\n });\n }\n }\n }\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n debug('branches', 'Maintenance error (non-fatal)', { error: msg });\n }\n }\n\n // ===========================================================================\n // Private Helpers\n // ===========================================================================\n\n private detectBoundary(\n obs: BranchObservationInput,\n obsTime: number,\n ): TriggerSource | null {\n // 1. Project hash change\n if (this.activeProjectHash && obs.projectHash !== this.activeProjectHash) {\n return 'project_switch';\n }\n\n // 2. Session change\n if (\n this.activeSessionId &&\n obs.sessionId &&\n obs.sessionId !== this.activeSessionId\n ) {\n return 'session_start';\n }\n\n // 3. Time gap (>15 minutes)\n if (this.lastObservationTime > 0) {\n const gap = obsTime - this.lastObservationTime;\n if (gap > TIME_GAP_MS) {\n return 'time_gap';\n }\n }\n\n return null;\n }\n\n private startBranch(\n triggerSource: TriggerSource,\n obs: BranchObservationInput,\n ): void {\n const branch = this.repo.createBranch(\n obs.sessionId ?? null,\n triggerSource,\n obs.id,\n );\n this.state = 'tracking';\n this.activeBranchId = branch.id;\n this.toolPattern = {};\n debug('branches', 'New branch started', {\n branchId: branch.id,\n trigger: triggerSource,\n });\n }\n\n private completeBranch(): void {\n if (!this.activeBranchId) return;\n this.repo.completeBranch(this.activeBranchId);\n debug('branches', 'Branch completed', { branchId: this.activeBranchId });\n this.state = 'idle';\n this.activeBranchId = null;\n this.toolPattern = {};\n }\n\n private extractToolName(source: string): string | null {\n // Source format: \"hook:Read\", \"hook:Write\", \"manual\", \"mcp:save_memory\"\n if (source.startsWith('hook:')) {\n return source.slice(5); // \"Read\", \"Write\", etc.\n }\n if (source.startsWith('mcp:')) {\n return source.slice(4);\n }\n return null;\n }\n}\n","/**\n * Main-thread bridge for the embedding worker.\n *\n * AnalysisWorker provides a Promise-based API (embed/embedBatch) that sends\n * messages to the worker thread and resolves when results arrive. All methods\n * degrade gracefully -- returning null on error/timeout rather than throwing.\n */\n\nimport { Worker } from 'node:worker_threads';\nimport { fileURLToPath } from 'node:url';\nimport { dirname, join } from 'node:path';\n\nimport { debug } from '../shared/debug.js';\n\n/** Timeout for worker startup (model loading). */\nconst STARTUP_TIMEOUT_MS = 30_000;\n\n/** Timeout for individual embed requests. */\nconst REQUEST_TIMEOUT_MS = 30_000;\n\ninterface PendingRequest<T> {\n resolve: (value: T) => void;\n timer: ReturnType<typeof setTimeout>;\n}\n\ninterface ReadyMessage {\n type: 'ready';\n engineName: string;\n dimensions: number;\n}\n\ninterface EmbedResultMessage {\n type: 'embed_result';\n id: string;\n embedding: Float32Array | null;\n}\n\ninterface EmbedBatchResultMessage {\n type: 'embed_batch_result';\n id: string;\n embeddings: (Float32Array | null)[];\n}\n\ntype WorkerResponse = ReadyMessage | EmbedResultMessage | EmbedBatchResultMessage;\n\n/**\n * Main-thread API for sending embed requests to the worker thread.\n *\n * Usage:\n * ```ts\n * const worker = new AnalysisWorker();\n * await worker.start();\n * const embedding = await worker.embed(\"some text\");\n * await worker.shutdown();\n * ```\n */\nexport class AnalysisWorker {\n private worker: Worker | null = null;\n private pending = new Map<string, PendingRequest<unknown>>();\n private nextId = 0;\n private ready = false;\n private engineName = 'unknown';\n private dimensions = 0;\n private workerPath: string;\n\n constructor(workerPath?: string) {\n if (workerPath) {\n this.workerPath = workerPath;\n } else {\n // Resolve worker.js relative to the running file's location.\n // When bundled, this file is inlined into dist/index.js so import.meta.url\n // points to dist/. The worker entry is built to dist/analysis/worker.js.\n const thisDir = dirname(fileURLToPath(import.meta.url));\n this.workerPath = join(thisDir, 'analysis', 'worker.js');\n }\n }\n\n /**\n * Starts the worker thread and waits for the 'ready' message.\n *\n * Resolves once the worker reports its engine name and dimensions.\n * Times out after 30 seconds if the worker never becomes ready.\n */\n async start(): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n const timer = setTimeout(() => {\n debug('embed', 'Worker startup timed out');\n this.ready = false;\n reject(new Error('Worker startup timed out'));\n }, STARTUP_TIMEOUT_MS);\n\n try {\n this.worker = new Worker(this.workerPath);\n } catch (err) {\n clearTimeout(timer);\n debug('embed', 'Failed to create worker', { error: String(err) });\n reject(err);\n return;\n }\n\n // One-time handler for the ready message\n const onReady = (msg: WorkerResponse) => {\n if (msg.type === 'ready') {\n clearTimeout(timer);\n this.ready = true;\n this.engineName = msg.engineName;\n this.dimensions = msg.dimensions;\n debug('embed', 'Worker ready', { engineName: msg.engineName, dimensions: msg.dimensions });\n\n // Switch to the permanent message handler\n this.worker!.off('message', onReady);\n this.worker!.on('message', (m: WorkerResponse) => this.handleMessage(m));\n resolve();\n }\n };\n\n this.worker.on('message', onReady);\n\n this.worker.on('error', (err) => {\n clearTimeout(timer);\n debug('embed', 'Worker error', { error: String(err) });\n this.resolveAllPending();\n this.ready = false;\n });\n\n this.worker.on('exit', (code) => {\n debug('embed', 'Worker exited', { code });\n this.resolveAllPending();\n this.ready = false;\n this.worker = null;\n });\n });\n }\n\n /**\n * Embeds a single text string via the worker thread.\n *\n * Returns null if the worker is not ready, not started, or if the\n * request times out.\n */\n async embed(text: string): Promise<Float32Array | null> {\n if (!this.worker || !this.ready) {\n return null;\n }\n\n const id = String(this.nextId++);\n\n return new Promise<Float32Array | null>((resolve) => {\n const timer = setTimeout(() => {\n debug('embed', 'Embed request timed out', { id });\n this.pending.delete(id);\n resolve(null);\n }, REQUEST_TIMEOUT_MS);\n\n this.pending.set(id, { resolve: resolve as (value: unknown) => void, timer });\n this.worker!.postMessage({ type: 'embed', id, text });\n });\n }\n\n /**\n * Embeds multiple texts via the worker thread.\n *\n * Returns an array of nulls if the worker is not ready or times out.\n */\n async embedBatch(texts: string[]): Promise<(Float32Array | null)[]> {\n if (!this.worker || !this.ready) {\n return texts.map(() => null);\n }\n\n const id = String(this.nextId++);\n\n return new Promise<(Float32Array | null)[]>((resolve) => {\n const timer = setTimeout(() => {\n debug('embed', 'Batch embed request timed out', { id });\n this.pending.delete(id);\n resolve(texts.map(() => null));\n }, REQUEST_TIMEOUT_MS);\n\n this.pending.set(id, { resolve: resolve as (value: unknown) => void, timer });\n this.worker!.postMessage({ type: 'embed_batch', id, texts });\n });\n }\n\n /**\n * Sends a shutdown message and waits for the worker to exit.\n */\n async shutdown(): Promise<void> {\n if (!this.worker) {\n return;\n }\n\n return new Promise<void>((resolve) => {\n const w = this.worker!;\n w.once('exit', () => {\n this.worker = null;\n this.ready = false;\n this.resolveAllPending();\n resolve();\n });\n\n w.postMessage({ type: 'shutdown' });\n\n // Safety timeout -- force terminate if shutdown hangs\n setTimeout(() => {\n if (this.worker) {\n debug('embed', 'Worker shutdown timed out, terminating');\n this.worker.terminate();\n }\n }, 5_000);\n });\n }\n\n /** Whether the worker is started and ready. */\n isReady(): boolean {\n return this.ready;\n }\n\n /** The engine name reported by the worker. */\n getEngineName(): string {\n return this.engineName;\n }\n\n /** The embedding dimensions reported by the worker. */\n getDimensions(): number {\n return this.dimensions;\n }\n\n /**\n * Dispatches worker responses to the correct pending promise.\n */\n private handleMessage(msg: WorkerResponse): void {\n if (msg.type === 'embed_result' || msg.type === 'embed_batch_result') {\n const id = msg.id;\n const req = this.pending.get(id);\n\n if (req) {\n clearTimeout(req.timer);\n this.pending.delete(id);\n\n if (msg.type === 'embed_result') {\n req.resolve(msg.embedding);\n } else {\n req.resolve(msg.embeddings);\n }\n }\n }\n }\n\n /**\n * Resolves all pending requests with null (graceful degradation).\n *\n * Called on worker error or unexpected exit.\n */\n private resolveAllPending(): void {\n for (const [id, req] of this.pending) {\n clearTimeout(req.timer);\n req.resolve(null);\n this.pending.delete(id);\n }\n }\n}\n","// ---------------------------------------------------------------------------\n// Topic Shift Handler -- Integration Layer\n// ---------------------------------------------------------------------------\n// Orchestrates topic detection and context stashing. When a new observation\n// embedding has high cosine distance from the previous one, the current\n// context thread is automatically stashed and the user is notified.\n//\n// This is the integration layer that makes topic detection active:\n// - Plan 01 built the detector (TopicShiftDetector)\n// - Plan 02 built storage (StashManager)\n// - Plan 05 built adaptive threshold (AdaptiveThresholdManager)\n// - Plan 06 adds config (TopicDetectionConfig) and logging (DecisionLogger)\n// - This module connects them into the live hook flow\n// ---------------------------------------------------------------------------\n\nimport { debug } from '../shared/debug.js';\nimport type { TopicShiftDetector } from '../intelligence/topic-detector.js';\nimport type { AdaptiveThresholdManager } from '../intelligence/adaptive-threshold.js';\nimport type { TopicShiftDecisionLogger, ShiftDecision } from '../intelligence/decision-logger.js';\nimport type { TopicDetectionConfig } from '../config/topic-detection-config.js';\nimport type { StashManager } from '../storage/stash-manager.js';\nimport type { ObservationRepository } from '../storage/observations.js';\nimport type { StashObservation } from '../types/stash.js';\nimport type { Observation } from '../shared/types.js';\n\n/**\n * Result of handling an observation through the topic shift pipeline.\n */\nexport interface TopicShiftHandlerResult {\n /** Whether a stash was created due to topic shift */\n stashed: boolean;\n /** Notification message to surface to the user, or null if no shift */\n notification: string | null;\n}\n\n/**\n * Dependencies required by TopicShiftHandler.\n *\n * Core dependencies (detector, stashManager, observationStore) are required.\n * Optional dependencies (config, decisionLogger, adaptiveManager) enable\n * additional functionality when provided. When omitted, the handler falls\n * back to simpler behavior for backward compatibility and easier test setups.\n */\nexport interface TopicShiftHandlerDeps {\n detector: TopicShiftDetector;\n stashManager: StashManager;\n observationStore: ObservationRepository;\n /** Optional: config for enable/disable, manual override, sensitivity */\n config?: TopicDetectionConfig;\n /** Optional: logs every detection decision for debugging */\n decisionLogger?: TopicShiftDecisionLogger;\n /** Optional: adaptive threshold manager for EWMA updates */\n adaptiveManager?: AdaptiveThresholdManager;\n}\n\n/**\n * Orchestrates topic shift detection and automatic stashing.\n *\n * Full pipeline when all dependencies provided:\n * 1. Check config (enabled? manual override?)\n * 2. Run detector.detect(embedding)\n * 3. If adaptive manager: update EWMA and set new threshold\n * 4. Log decision via decision logger\n * 5. If shifted: gather observations, create stash, notify\n * 6. If not shifted: return no-op result\n *\n * When optional deps are omitted, steps 1/3/4 are skipped gracefully.\n */\nexport class TopicShiftHandler {\n private readonly detector: TopicShiftDetector;\n private readonly stashManager: StashManager;\n private readonly observationStore: ObservationRepository;\n private readonly config?: TopicDetectionConfig;\n private readonly decisionLogger?: TopicShiftDecisionLogger;\n private readonly adaptiveManager?: AdaptiveThresholdManager;\n\n constructor(deps: TopicShiftHandlerDeps) {\n this.detector = deps.detector;\n this.stashManager = deps.stashManager;\n this.observationStore = deps.observationStore;\n this.config = deps.config;\n this.decisionLogger = deps.decisionLogger;\n this.adaptiveManager = deps.adaptiveManager;\n\n debug('hook', 'TopicShiftHandler initialized', {\n hasConfig: !!deps.config,\n hasDecisionLogger: !!deps.decisionLogger,\n hasAdaptiveManager: !!deps.adaptiveManager,\n });\n }\n\n /**\n * Evaluate an observation for topic shift.\n *\n * If a shift is detected, gathers recent observations from the previous\n * topic, creates a stash snapshot, and returns a notification message\n * for the user.\n */\n async handleObservation(\n observation: Observation,\n sessionId: string,\n projectId: string,\n ): Promise<TopicShiftHandlerResult> {\n // 0. Config check: if detection is disabled, return early\n if (this.config && !this.config.enabled) {\n debug('hook', 'TopicShiftHandler: detection disabled by config');\n return { stashed: false, notification: null };\n }\n\n // 1. No embedding -> skip detection\n if (!observation.embedding) {\n debug('hook', 'TopicShiftHandler: no embedding, skipping', {\n id: observation.id,\n });\n return { stashed: false, notification: null };\n }\n\n // 2. Apply manual threshold override if configured\n if (this.config?.manualThreshold !== undefined && this.config.manualThreshold !== null) {\n this.detector.setThreshold(this.config.manualThreshold);\n }\n\n // 3. Run detector -- convert Float32Array to number[] for cosine distance\n const embeddingArray = Array.from(observation.embedding);\n const result = this.detector.detect(embeddingArray);\n\n debug('hook', 'TopicShiftHandler: detection result', {\n shifted: result.shifted,\n distance: result.distance,\n threshold: result.threshold,\n });\n\n // 4. Adaptive threshold update (if adaptive manager present and no manual override)\n if (this.adaptiveManager && !(this.config?.manualThreshold !== undefined && this.config.manualThreshold !== null)) {\n const newThreshold = this.adaptiveManager.update(result.distance);\n this.detector.setThreshold(newThreshold);\n debug('hook', 'TopicShiftHandler: adaptive threshold updated', {\n newThreshold,\n });\n }\n\n // 5. Determine stash ID (only available after stash creation below)\n let stashId: string | null = null;\n\n // 6. Handle shift: gather observations and create stash\n if (result.shifted) {\n // Gather previous topic observations (include unclassified -- observations\n // may not yet be classified by the background classifier when shift runs)\n const recentObservations = this.observationStore.list({\n sessionId,\n limit: 20,\n includeUnclassified: true,\n });\n\n // Filter to observations before the current one (by timestamp)\n const previousObservations = recentObservations.filter(\n (obs) => obs.createdAt < observation.createdAt,\n );\n\n // Nothing to stash (clean context / session start) -- skip\n if (previousObservations.length === 0) {\n debug('hook', 'TopicShiftHandler: no previous observations to stash, skipping');\n return { stashed: false, notification: null };\n }\n\n // Generate topic label from previous observations\n const topicLabel = this.generateTopicLabel(previousObservations);\n\n // Generate summary\n const summary = this.generateSummary(previousObservations);\n\n // Create stash observation snapshots\n const snapshots: StashObservation[] = previousObservations.map((obs) => ({\n id: obs.id,\n content: obs.content,\n type: obs.source,\n timestamp: obs.createdAt,\n embedding: obs.embedding ? Array.from(obs.embedding) : null,\n }));\n\n // Create stash\n const stash = this.stashManager.createStash({\n projectId,\n sessionId,\n topicLabel,\n summary,\n observations: snapshots,\n });\n\n stashId = stash.id;\n\n debug('hook', 'TopicShiftHandler: stash created', { topicLabel, stashId });\n\n // 7. Log decision (if logger present)\n if (this.decisionLogger) {\n const decision: ShiftDecision = {\n projectId,\n sessionId,\n observationId: observation.id,\n distance: result.distance,\n threshold: result.threshold,\n ewmaDistance: this.adaptiveManager?.getState().ewmaDistance ?? null,\n ewmaVariance: this.adaptiveManager?.getState().ewmaVariance ?? null,\n sensitivityMultiplier: this.config?.sensitivityMultiplier ?? 1.5,\n shifted: true,\n confidence: result.confidence,\n stashId,\n };\n this.decisionLogger.log(decision);\n }\n\n // Return notification\n const notification = `Topic shift detected. Previous context stashed: \"${topicLabel}\". Use /laminark:resume to return.`;\n return { stashed: true, notification };\n }\n\n // 8. Not shifted -- log decision and return no-op\n if (this.decisionLogger) {\n const decision: ShiftDecision = {\n projectId,\n sessionId,\n observationId: observation.id,\n distance: result.distance,\n threshold: result.threshold,\n ewmaDistance: this.adaptiveManager?.getState().ewmaDistance ?? null,\n ewmaVariance: this.adaptiveManager?.getState().ewmaVariance ?? null,\n sensitivityMultiplier: this.config?.sensitivityMultiplier ?? 1.5,\n shifted: false,\n confidence: result.confidence,\n stashId: null,\n };\n this.decisionLogger.log(decision);\n }\n\n return { stashed: false, notification: null };\n }\n\n /**\n * Generate a semantic topic label from the observations.\n *\n * Priority: use observation titles (most semantic), then fall back\n * to content. Scans all observations for the best available label\n * rather than just using the first one.\n */\n private generateTopicLabel(observations: Observation[]): string {\n if (observations.length === 0) {\n return 'Unknown topic';\n }\n\n // Prefer titled observations -- titles are explicitly authored and semantic\n for (const obs of observations) {\n if (obs.title) {\n const cleaned = obs.title.replace(/\\n/g, ' ').trim();\n if (cleaned.length > 0) {\n return cleaned.slice(0, 80);\n }\n }\n }\n\n // Fallback: use the oldest observation's content (list is DESC)\n const oldest = observations[observations.length - 1];\n const raw = oldest.content.replace(/\\n/g, ' ').trim();\n return raw.slice(0, 80) || 'Unknown topic';\n }\n\n /**\n * Generate a brief summary by concatenating the first 3 observation contents,\n * truncated to 200 characters total.\n */\n private generateSummary(observations: Observation[]): string {\n if (observations.length === 0) {\n return '';\n }\n\n // Take up to 3 oldest observations (list is DESC, so take from the end)\n const oldest = observations.slice(-3).reverse();\n const joined = oldest\n .map((obs) => obs.content.replace(/\\n/g, ' ').trim())\n .join(' | ');\n\n return joined.slice(0, 200);\n }\n}\n","// ---------------------------------------------------------------------------\n// Topic Shift Detection -- Static Threshold\n// ---------------------------------------------------------------------------\n// Computes cosine distance between consecutive observation embeddings and\n// determines whether a topic shift has occurred based on a static threshold.\n// Foundation for all topic detection in Phase 6 -- adaptive EWMA layered on\n// in Plan 05.\n// ---------------------------------------------------------------------------\n\n/**\n * Result of a topic shift detection check.\n */\nexport interface TopicShiftResult {\n /** Whether a topic shift was detected */\n shifted: boolean;\n /** Cosine distance between current and previous embedding */\n distance: number;\n /** Threshold used for this detection */\n threshold: number;\n /** Confidence 0-1 -- how far past the threshold (0 if not shifted) */\n confidence: number;\n /** Previous embedding (null if first observation) */\n previousEmbedding: number[] | null;\n /** Current embedding that was evaluated */\n currentEmbedding: number[];\n}\n\n/**\n * Compute cosine distance between two vectors.\n *\n * Returns 1 - cosineSimilarity(a, b).\n * Range: [0, 2] where 0 = identical, 1 = orthogonal, 2 = opposite.\n * Handles zero vectors gracefully by returning 0 (not NaN).\n */\nexport function cosineDistance(a: number[], b: number[]): number {\n let dot = 0;\n let magA = 0;\n let magB = 0;\n\n for (let i = 0; i < a.length; i++) {\n dot += a[i] * b[i];\n magA += a[i] * a[i];\n magB += b[i] * b[i];\n }\n\n const magnitudeProduct = Math.sqrt(magA) * Math.sqrt(magB);\n\n // Zero vector: treat as no distance (graceful, no NaN)\n if (magnitudeProduct === 0) {\n return 0;\n }\n\n const similarity = dot / magnitudeProduct;\n\n // Clamp to [-1, 1] to handle floating-point rounding\n const clampedSimilarity = Math.max(-1, Math.min(1, similarity));\n\n return 1 - clampedSimilarity;\n}\n\n/**\n * Detects topic shifts by comparing consecutive observation embeddings\n * against a static cosine distance threshold.\n */\nexport class TopicShiftDetector {\n private lastEmbedding: number[] | null = null;\n private threshold: number;\n\n constructor(options?: { threshold?: number }) {\n this.threshold = options?.threshold ?? 0.3;\n }\n\n /**\n * Evaluate a new embedding for topic shift against the previous one.\n * Updates internal state with the new embedding after evaluation.\n */\n detect(embedding: number[]): TopicShiftResult {\n const previous = this.lastEmbedding;\n this.lastEmbedding = embedding;\n\n // First observation -- no prior to compare against\n if (previous === null) {\n return {\n shifted: false,\n distance: 0,\n threshold: this.threshold,\n confidence: 0,\n previousEmbedding: null,\n currentEmbedding: embedding,\n };\n }\n\n const distance = cosineDistance(previous, embedding);\n const shifted = distance > this.threshold;\n const confidence = shifted\n ? Math.min((distance - this.threshold) / this.threshold, 1.0)\n : 0;\n\n return {\n shifted,\n distance,\n threshold: this.threshold,\n confidence,\n previousEmbedding: previous,\n currentEmbedding: embedding,\n };\n }\n\n /** Clear last embedding state -- next detect is treated as first observation */\n reset(): void {\n this.lastEmbedding = null;\n }\n\n /** Get current threshold value */\n getThreshold(): number {\n return this.threshold;\n }\n\n /** Set threshold value, bounded to [0.05, 0.95] */\n setThreshold(value: number): void {\n this.threshold = Math.max(0.05, Math.min(0.95, value));\n }\n}\n","// ---------------------------------------------------------------------------\n// EWMA Adaptive Topic Threshold\n// ---------------------------------------------------------------------------\n// Adjusts the topic shift detection threshold per-session based on observed\n// cosine distances using an Exponentially Weighted Moving Average (EWMA).\n//\n// A scattered session (high distances) raises the threshold over time.\n// A focused session (low distances) lowers the threshold over time.\n// New sessions seed from historical averages for cold start handling.\n// ---------------------------------------------------------------------------\n\n/**\n * Internal state of the adaptive threshold computation.\n */\nexport interface ThresholdState {\n /** Exponentially weighted moving average of observed distances */\n ewmaDistance: number;\n /** Exponentially weighted variance of observed distances */\n ewmaVariance: number;\n /** Decay factor for EWMA (0 < alpha <= 1) */\n alpha: number;\n /** Standard deviations above the mean for threshold */\n sensitivityMultiplier: number;\n /** Number of distance observations processed */\n observationCount: number;\n}\n\n/** Default EWMA distance when no history exists */\nconst DEFAULT_EWMA_DISTANCE = 0.3;\n\n/** Default EWMA variance when no history exists */\nconst DEFAULT_EWMA_VARIANCE = 0.01;\n\n/** Default decay factor */\nconst DEFAULT_ALPHA = 0.3;\n\n/** Default sensitivity (standard deviations above mean) */\nconst DEFAULT_SENSITIVITY_MULTIPLIER = 1.5;\n\n/** Hard lower bound for threshold -- prevents over-sensitive detection */\nconst THRESHOLD_MIN = 0.15;\n\n/** Hard upper bound for threshold -- prevents ignoring real shifts */\nconst THRESHOLD_MAX = 0.6;\n\n/**\n * Manages an EWMA-based adaptive threshold for topic shift detection.\n *\n * After each topic distance observation, call `update(distance)` to refine\n * the threshold. The threshold adapts:\n * - High distances (scattered topics) push the threshold up\n * - Low distances (focused topics) push the threshold down\n * - Threshold is bounded within [0.15, 0.6] to prevent extreme drift\n *\n * For cold start, call `seedFromHistory(avgDistance, avgVariance)` with\n * averages loaded from the ThresholdStore.\n */\nexport class AdaptiveThresholdManager {\n private ewmaDistance: number;\n private ewmaVariance: number;\n private alpha: number;\n private sensitivityMultiplier: number;\n private observationCount: number;\n\n constructor(options?: {\n alpha?: number;\n sensitivityMultiplier?: number;\n }) {\n this.alpha = options?.alpha ?? DEFAULT_ALPHA;\n this.sensitivityMultiplier =\n options?.sensitivityMultiplier ?? DEFAULT_SENSITIVITY_MULTIPLIER;\n this.ewmaDistance = DEFAULT_EWMA_DISTANCE;\n this.ewmaVariance = DEFAULT_EWMA_VARIANCE;\n this.observationCount = 0;\n }\n\n /**\n * Feed a new cosine distance observation and update the EWMA state.\n *\n * EWMA update formula:\n * 1. ewmaDistance = alpha * distance + (1 - alpha) * ewmaDistance\n * 2. diff = distance - ewmaDistance (after update)\n * 3. ewmaVariance = alpha * (diff * diff) + (1 - alpha) * ewmaVariance\n * 4. threshold = clamp(ewmaDistance + sensitivityMultiplier * sqrt(ewmaVariance), 0.15, 0.6)\n *\n * @param distance - Cosine distance from the latest topic detection\n * @returns The new adaptive threshold value\n */\n update(distance: number): number {\n // Step 1: Update EWMA distance\n this.ewmaDistance =\n this.alpha * distance + (1 - this.alpha) * this.ewmaDistance;\n\n // Step 2: Compute deviation from new mean\n const diff = distance - this.ewmaDistance;\n\n // Step 3: Update EWMA variance\n this.ewmaVariance =\n this.alpha * (diff * diff) + (1 - this.alpha) * this.ewmaVariance;\n\n // Step 4: Increment observation count\n this.observationCount++;\n\n // Step 5: Return clamped threshold\n return this.getThreshold();\n }\n\n /**\n * Seed the EWMA state from historical session averages (cold start).\n * Does not reset observation count -- only updates the statistical seed.\n */\n seedFromHistory(averageDistance: number, averageVariance: number): void {\n this.ewmaDistance = averageDistance;\n this.ewmaVariance = averageVariance;\n }\n\n /**\n * Compute the current threshold from EWMA state, clamped to bounds.\n *\n * Formula: ewmaDistance + sensitivityMultiplier * sqrt(ewmaVariance)\n * Bounded to [0.15, 0.6]\n */\n getThreshold(): number {\n const raw =\n this.ewmaDistance +\n this.sensitivityMultiplier * Math.sqrt(this.ewmaVariance);\n return Math.max(THRESHOLD_MIN, Math.min(THRESHOLD_MAX, raw));\n }\n\n /**\n * Return a snapshot of the current EWMA state.\n */\n getState(): ThresholdState {\n return {\n ewmaDistance: this.ewmaDistance,\n ewmaVariance: this.ewmaVariance,\n alpha: this.alpha,\n sensitivityMultiplier: this.sensitivityMultiplier,\n observationCount: this.observationCount,\n };\n }\n\n /**\n * Reset all EWMA state to defaults.\n */\n reset(): void {\n this.ewmaDistance = DEFAULT_EWMA_DISTANCE;\n this.ewmaVariance = DEFAULT_EWMA_VARIANCE;\n this.observationCount = 0;\n }\n}\n","// ---------------------------------------------------------------------------\n// Topic Shift Decision Logger\n// ---------------------------------------------------------------------------\n// Logs every topic shift decision (shifted or not) with all inputs for\n// debugging and threshold tuning. Requirement DQ-05 demands full\n// observability of the decision pipeline.\n//\n// Each decision record captures: distance, threshold, EWMA state,\n// sensitivity multiplier, shifted boolean, confidence, and optional\n// stash ID (when a shift triggered stashing).\n// ---------------------------------------------------------------------------\n\nimport type BetterSqlite3 from 'better-sqlite3';\nimport { randomBytes } from 'node:crypto';\n\nimport { debug } from '../shared/debug.js';\n\n/**\n * A single topic shift decision with all inputs recorded.\n */\nexport interface ShiftDecision {\n /** Project scoping */\n projectId: string;\n /** Session scoping */\n sessionId: string;\n /** The observation that triggered this decision (null for synthetic events) */\n observationId: string | null;\n /** Cosine distance between consecutive embeddings */\n distance: number;\n /** Threshold used for this detection */\n threshold: number;\n /** EWMA distance at decision time (null if adaptive disabled) */\n ewmaDistance: number | null;\n /** EWMA variance at decision time (null if adaptive disabled) */\n ewmaVariance: number | null;\n /** Sensitivity multiplier in effect */\n sensitivityMultiplier: number;\n /** Whether a topic shift was detected */\n shifted: boolean;\n /** Confidence score (0-1) */\n confidence: number;\n /** Stash ID created by this shift (null if not shifted) */\n stashId: string | null;\n}\n\n/**\n * Persists topic shift decisions for debugging and threshold tuning.\n *\n * Every call to detect() in the topic shift pipeline should result in\n * a corresponding log() call here, regardless of whether a shift was\n * detected. This provides complete visibility into the decision process.\n *\n * All SQL statements are prepared once in the constructor and reused\n * for every call (better-sqlite3 performance best practice).\n */\nexport class TopicShiftDecisionLogger {\n private readonly db: BetterSqlite3.Database;\n\n // Prepared statements\n private readonly stmtInsert: BetterSqlite3.Statement;\n private readonly stmtGetSessionDecisions: BetterSqlite3.Statement;\n private readonly stmtGetShiftRate: BetterSqlite3.Statement;\n\n constructor(db: BetterSqlite3.Database) {\n this.db = db;\n\n this.stmtInsert = db.prepare(`\n INSERT INTO shift_decisions\n (id, project_id, session_id, observation_id, distance, threshold,\n ewma_distance, ewma_variance, sensitivity_multiplier, shifted,\n confidence, stash_id)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n `);\n\n this.stmtGetSessionDecisions = db.prepare(`\n SELECT * FROM shift_decisions\n WHERE project_id = ? AND session_id = ?\n ORDER BY created_at DESC\n LIMIT ?\n `);\n\n this.stmtGetShiftRate = db.prepare(`\n SELECT\n COUNT(*) AS total,\n SUM(shifted) AS shifted_count\n FROM (\n SELECT shifted\n FROM shift_decisions\n WHERE project_id = ?\n ORDER BY created_at DESC\n LIMIT ?\n )\n `);\n\n debug('db', 'TopicShiftDecisionLogger initialized');\n }\n\n /**\n * Log a topic shift decision with all inputs.\n *\n * Should be called after every detect() call, regardless of outcome.\n */\n log(decision: ShiftDecision): void {\n const id = randomBytes(16).toString('hex');\n\n this.stmtInsert.run(\n id,\n decision.projectId,\n decision.sessionId,\n decision.observationId,\n decision.distance,\n decision.threshold,\n decision.ewmaDistance,\n decision.ewmaVariance,\n decision.sensitivityMultiplier,\n decision.shifted ? 1 : 0,\n decision.confidence,\n decision.stashId,\n );\n\n debug('db', 'Shift decision logged', {\n shifted: decision.shifted,\n distance: decision.distance,\n threshold: decision.threshold,\n });\n }\n\n /**\n * Retrieve decisions for a specific session, ordered by recency.\n *\n * Useful for debugging: \"What happened in this session?\"\n */\n getSessionDecisions(\n projectId: string,\n sessionId: string,\n limit: number = 50,\n ): ShiftDecision[] {\n const rows = this.stmtGetSessionDecisions.all(\n projectId,\n sessionId,\n limit,\n ) as DecisionRow[];\n\n return rows.map(rowToDecision);\n }\n\n /**\n * Compute shift rate statistics across recent decisions for a project.\n *\n * Returns the total number of decisions, how many were shifts, and\n * the rate (0-1). Useful for tuning: a rate of 0.3 means 30% of\n * observations triggered a topic shift.\n */\n getShiftRate(\n projectId: string,\n lastN: number = 100,\n ): { total: number; shifted: number; rate: number } {\n const row = this.stmtGetShiftRate.get(projectId, lastN) as {\n total: number;\n shifted_count: number | null;\n };\n\n const total = row.total;\n const shifted = row.shifted_count ?? 0;\n const rate = total > 0 ? shifted / total : 0;\n\n return { total, shifted, rate };\n }\n}\n\n// ---------------------------------------------------------------------------\n// Internal row mapping\n// ---------------------------------------------------------------------------\n\ninterface DecisionRow {\n id: string;\n project_id: string;\n session_id: string;\n observation_id: string | null;\n distance: number;\n threshold: number;\n ewma_distance: number | null;\n ewma_variance: number | null;\n sensitivity_multiplier: number;\n shifted: number; // 0 or 1\n confidence: number;\n stash_id: string | null;\n created_at: string;\n}\n\nfunction rowToDecision(row: DecisionRow): ShiftDecision {\n return {\n projectId: row.project_id,\n sessionId: row.session_id,\n observationId: row.observation_id,\n distance: row.distance,\n threshold: row.threshold,\n ewmaDistance: row.ewma_distance,\n ewmaVariance: row.ewma_variance,\n sensitivityMultiplier: row.sensitivity_multiplier,\n shifted: row.shifted === 1,\n confidence: row.confidence,\n stashId: row.stash_id,\n };\n}\n","// ---------------------------------------------------------------------------\n// Topic Detection Configuration\n// ---------------------------------------------------------------------------\n// User-configurable sensitivity dial for topic shift detection.\n// Supports three presets (sensitive/balanced/relaxed), custom multipliers,\n// manual threshold override, and an enable/disable toggle.\n//\n// Configuration is loaded from .laminark/topic-detection.json with\n// safe defaults when the file does not exist.\n// ---------------------------------------------------------------------------\n\nimport { readFileSync } from 'node:fs';\nimport { join } from 'node:path';\n\nimport { debug } from '../shared/debug.js';\nimport { getConfigDir } from '../shared/config.js';\nimport type { TopicShiftDetector } from '../intelligence/topic-detector.js';\nimport type { AdaptiveThresholdManager } from '../intelligence/adaptive-threshold.js';\n\n/**\n * Sensitivity preset names for topic detection.\n */\nexport type SensitivityPreset = 'sensitive' | 'balanced' | 'relaxed';\n\n/**\n * Full configuration for topic detection behavior.\n */\nexport interface TopicDetectionConfig {\n /** Named sensitivity preset */\n sensitivityPreset: SensitivityPreset;\n /** Derived from preset or custom -- multiplied with EWMA stddev for threshold */\n sensitivityMultiplier: number;\n /** If set, overrides adaptive threshold entirely */\n manualThreshold: number | null;\n /** EWMA decay factor (0 < alpha <= 1) */\n ewmaAlpha: number;\n /** Hard bounds for adaptive threshold */\n thresholdBounds: { min: number; max: number };\n /** Master toggle -- when false, topic detection is disabled */\n enabled: boolean;\n}\n\n/**\n * Raw JSON shape for the configuration file.\n * All fields are optional -- missing fields use defaults.\n */\ninterface RawConfigJson {\n sensitivityPreset?: string;\n sensitivityMultiplier?: number;\n manualThreshold?: number | null;\n ewmaAlpha?: number;\n thresholdBounds?: { min?: number; max?: number };\n enabled?: boolean;\n}\n\n/**\n * Maps a sensitivity preset to its multiplier value.\n *\n * - sensitive (1.0): Detects smaller shifts -- lower bar for topic change\n * - balanced (1.5): Default -- moderate sensitivity\n * - relaxed (2.5): Only detects large shifts -- higher bar\n */\nexport function sensitivityPresetToMultiplier(preset: SensitivityPreset): number {\n switch (preset) {\n case 'sensitive':\n return 1.0;\n case 'balanced':\n return 1.5;\n case 'relaxed':\n return 2.5;\n }\n}\n\n/** Default configuration values */\nconst DEFAULTS: TopicDetectionConfig = {\n sensitivityPreset: 'balanced',\n sensitivityMultiplier: 1.5,\n manualThreshold: null,\n ewmaAlpha: 0.3,\n thresholdBounds: { min: 0.15, max: 0.6 },\n enabled: true,\n};\n\n/**\n * Loads topic detection configuration from disk.\n *\n * Reads .laminark/topic-detection.json (relative to the Laminark data\n * directory). Falls back to defaults if the file does not exist or\n * cannot be parsed. Validates threshold bounds constraints.\n */\nexport function loadTopicDetectionConfig(): TopicDetectionConfig {\n const configPath = join(getConfigDir(), 'topic-detection.json');\n\n let raw: RawConfigJson = {};\n\n try {\n const content = readFileSync(configPath, 'utf-8');\n raw = JSON.parse(content) as RawConfigJson;\n debug('config', 'Loaded topic detection config', { path: configPath });\n } catch {\n // File doesn't exist or is invalid -- use all defaults\n debug('config', 'No topic detection config found, using defaults');\n return { ...DEFAULTS };\n }\n\n // Resolve sensitivity preset\n const validPresets: SensitivityPreset[] = ['sensitive', 'balanced', 'relaxed'];\n const preset: SensitivityPreset = validPresets.includes(raw.sensitivityPreset as SensitivityPreset)\n ? (raw.sensitivityPreset as SensitivityPreset)\n : DEFAULTS.sensitivityPreset;\n\n // Multiplier: explicit value > preset-derived > default\n const multiplier =\n typeof raw.sensitivityMultiplier === 'number' && raw.sensitivityMultiplier > 0\n ? raw.sensitivityMultiplier\n : sensitivityPresetToMultiplier(preset);\n\n // Manual threshold: null means use adaptive\n const manualThreshold =\n typeof raw.manualThreshold === 'number' ? raw.manualThreshold : null;\n\n // EWMA alpha\n const ewmaAlpha =\n typeof raw.ewmaAlpha === 'number' && raw.ewmaAlpha > 0 && raw.ewmaAlpha <= 1\n ? raw.ewmaAlpha\n : DEFAULTS.ewmaAlpha;\n\n // Threshold bounds with validation\n let boundsMin =\n typeof raw.thresholdBounds?.min === 'number'\n ? raw.thresholdBounds.min\n : DEFAULTS.thresholdBounds.min;\n let boundsMax =\n typeof raw.thresholdBounds?.max === 'number'\n ? raw.thresholdBounds.max\n : DEFAULTS.thresholdBounds.max;\n\n // Validate bounds: min >= 0.05, max <= 0.95, min < max\n if (boundsMin < 0.05) boundsMin = 0.05;\n if (boundsMax > 0.95) boundsMax = 0.95;\n if (boundsMin >= boundsMax) {\n boundsMin = DEFAULTS.thresholdBounds.min;\n boundsMax = DEFAULTS.thresholdBounds.max;\n }\n\n // Enabled toggle\n const enabled = typeof raw.enabled === 'boolean' ? raw.enabled : DEFAULTS.enabled;\n\n return {\n sensitivityPreset: preset,\n sensitivityMultiplier: multiplier,\n manualThreshold,\n ewmaAlpha,\n thresholdBounds: { min: boundsMin, max: boundsMax },\n enabled,\n };\n}\n\n/**\n * Applies a TopicDetectionConfig to a detector and adaptive manager.\n *\n * - If config.enabled is false, sets detector threshold to 999 (never triggers)\n * - If config.manualThreshold is set, uses it directly (bypasses adaptive)\n * - Otherwise, configures the adaptive manager with the sensitivity multiplier\n */\nexport function applyConfig(\n config: TopicDetectionConfig,\n detector: TopicShiftDetector,\n adaptiveManager: AdaptiveThresholdManager,\n): void {\n if (!config.enabled) {\n // Disabled mode: set threshold so high that nothing triggers\n detector.setThreshold(999);\n debug('config', 'Topic detection disabled -- threshold set to 999');\n return;\n }\n\n if (config.manualThreshold !== null) {\n // Manual override: bypass adaptive entirely\n detector.setThreshold(config.manualThreshold);\n debug('config', 'Manual threshold override applied', {\n threshold: config.manualThreshold,\n });\n return;\n }\n\n // Adaptive mode: configure the manager's sensitivity\n // The adaptive manager uses sensitivityMultiplier internally via constructor,\n // but we can influence it by seeding or updating its state.\n // For now, apply the threshold from the adaptive manager to the detector.\n const adaptiveThreshold = adaptiveManager.getThreshold();\n detector.setThreshold(adaptiveThreshold);\n\n debug('config', 'Adaptive config applied', {\n preset: config.sensitivityPreset,\n multiplier: config.sensitivityMultiplier,\n threshold: adaptiveThreshold,\n });\n}\n","/**\n * Graph Extraction Configuration\n *\n * User-configurable settings for knowledge graph extraction behavior.\n * Follows the same pattern as topic-detection-config.ts.\n *\n * Configuration is loaded from .laminark/graph-extraction.json with\n * safe defaults when the file does not exist.\n */\n\nimport { readFileSync } from 'node:fs';\nimport { join } from 'node:path';\n\nimport { debug } from '../shared/debug.js';\nimport { getConfigDir } from '../shared/config.js';\nimport type { EntityType } from '../graph/types.js';\n\n// =============================================================================\n// Types\n// =============================================================================\n\nexport interface GraphExtractionConfig {\n /** Master toggle -- when false, all graph extraction is disabled */\n enabled: boolean;\n\n /** Signal classifier settings */\n signalClassifier: {\n /** Sources that get full extraction (entities + relationships) */\n highSignalSources: string[];\n /** Sources that get entities only (no relationship edges) */\n mediumSignalSources: string[];\n /** Sources that skip graph extraction entirely */\n skipSources: string[];\n /** Minimum content length to process (chars) */\n minContentLength: number;\n };\n\n /** Write-quality gate settings */\n qualityGate: {\n /** Minimum entity name length */\n minNameLength: number;\n /** Maximum entity name length */\n maxNameLength: number;\n /** Maximum File nodes per observation */\n maxFilesPerObservation: number;\n /** Per-type minimum confidence thresholds */\n typeConfidenceThresholds: Record<EntityType, number>;\n /** Confidence multiplier for File paths from non-change observations */\n fileNonChangeMultiplier: number;\n };\n\n /** Relationship detector settings */\n relationshipDetector: {\n /** Minimum edge confidence to persist */\n minEdgeConfidence: number;\n };\n\n /** Temporal decay settings */\n temporalDecay: {\n /** Half-life in days */\n halfLifeDays: number;\n /** Minimum floor weight */\n minFloor: number;\n /** Edges below this weight are deleted during curation */\n deletionThreshold: number;\n /** Maximum age in days before forced deletion */\n maxAgeDays: number;\n };\n\n /** Fuzzy deduplication settings */\n fuzzyDedup: {\n /** Maximum Levenshtein distance for typo matching */\n maxLevenshteinDistance: number;\n /** Minimum Jaccard similarity for word matching */\n jaccardThreshold: number;\n };\n}\n\n// =============================================================================\n// Defaults\n// =============================================================================\n\nconst DEFAULTS: GraphExtractionConfig = {\n enabled: true,\n\n signalClassifier: {\n highSignalSources: ['manual', 'hook:Write', 'hook:Edit', 'hook:WebFetch', 'hook:WebSearch'],\n mediumSignalSources: ['hook:Bash', 'curation:merge'],\n skipSources: [\n 'hook:TaskUpdate', 'hook:TaskCreate', 'hook:EnterPlanMode',\n 'hook:ExitPlanMode', 'hook:Read', 'hook:Glob', 'hook:Grep',\n ],\n minContentLength: 30,\n },\n\n qualityGate: {\n minNameLength: 3,\n maxNameLength: 200,\n maxFilesPerObservation: 5,\n typeConfidenceThresholds: {\n File: 0.95,\n Project: 0.8,\n Reference: 0.85,\n Decision: 0.65,\n Problem: 0.6,\n Solution: 0.6,\n },\n fileNonChangeMultiplier: 0.74,\n },\n\n relationshipDetector: {\n minEdgeConfidence: 0.45,\n },\n\n temporalDecay: {\n halfLifeDays: 30,\n minFloor: 0.05,\n deletionThreshold: 0.08,\n maxAgeDays: 180,\n },\n\n fuzzyDedup: {\n maxLevenshteinDistance: 2,\n jaccardThreshold: 0.7,\n },\n};\n\n// =============================================================================\n// Raw JSON Type\n// =============================================================================\n\ninterface RawConfigJson {\n enabled?: boolean;\n signalClassifier?: {\n highSignalSources?: string[];\n mediumSignalSources?: string[];\n skipSources?: string[];\n minContentLength?: number;\n };\n qualityGate?: {\n minNameLength?: number;\n maxNameLength?: number;\n maxFilesPerObservation?: number;\n typeConfidenceThresholds?: Partial<Record<EntityType, number>>;\n fileNonChangeMultiplier?: number;\n };\n relationshipDetector?: {\n minEdgeConfidence?: number;\n };\n temporalDecay?: {\n halfLifeDays?: number;\n minFloor?: number;\n deletionThreshold?: number;\n maxAgeDays?: number;\n };\n fuzzyDedup?: {\n maxLevenshteinDistance?: number;\n jaccardThreshold?: number;\n };\n}\n\n// =============================================================================\n// Loader\n// =============================================================================\n\n/**\n * Loads graph extraction configuration from disk.\n *\n * Reads .laminark/graph-extraction.json (relative to the Laminark data\n * directory). Falls back to defaults if the file does not exist or\n * cannot be parsed. Validates threshold constraints.\n */\nexport function loadGraphExtractionConfig(): GraphExtractionConfig {\n const configPath = join(getConfigDir(), 'graph-extraction.json');\n\n let raw: RawConfigJson = {};\n\n try {\n const content = readFileSync(configPath, 'utf-8');\n raw = JSON.parse(content) as RawConfigJson;\n debug('config', 'Loaded graph extraction config', { path: configPath });\n } catch {\n debug('config', 'No graph extraction config found, using defaults');\n return { ...DEFAULTS };\n }\n\n // Enabled toggle\n const enabled = typeof raw.enabled === 'boolean' ? raw.enabled : DEFAULTS.enabled;\n\n // Signal classifier\n const signalClassifier = {\n highSignalSources: Array.isArray(raw.signalClassifier?.highSignalSources)\n ? raw.signalClassifier!.highSignalSources\n : DEFAULTS.signalClassifier.highSignalSources,\n mediumSignalSources: Array.isArray(raw.signalClassifier?.mediumSignalSources)\n ? raw.signalClassifier!.mediumSignalSources\n : DEFAULTS.signalClassifier.mediumSignalSources,\n skipSources: Array.isArray(raw.signalClassifier?.skipSources)\n ? raw.signalClassifier!.skipSources\n : DEFAULTS.signalClassifier.skipSources,\n minContentLength: typeof raw.signalClassifier?.minContentLength === 'number'\n && raw.signalClassifier.minContentLength >= 0\n ? raw.signalClassifier.minContentLength\n : DEFAULTS.signalClassifier.minContentLength,\n };\n\n // Quality gate\n const rawQG = raw.qualityGate;\n const typeConf = { ...DEFAULTS.qualityGate.typeConfidenceThresholds };\n if (rawQG?.typeConfidenceThresholds) {\n for (const [key, val] of Object.entries(rawQG.typeConfidenceThresholds)) {\n if (typeof val === 'number' && val >= 0 && val <= 1) {\n typeConf[key as EntityType] = val;\n }\n }\n }\n\n let fileMultiplier = typeof rawQG?.fileNonChangeMultiplier === 'number'\n ? rawQG.fileNonChangeMultiplier\n : DEFAULTS.qualityGate.fileNonChangeMultiplier;\n if (fileMultiplier < 0 || fileMultiplier > 1) {\n fileMultiplier = DEFAULTS.qualityGate.fileNonChangeMultiplier;\n }\n\n const qualityGate = {\n minNameLength: typeof rawQG?.minNameLength === 'number' && rawQG.minNameLength >= 1\n ? rawQG.minNameLength\n : DEFAULTS.qualityGate.minNameLength,\n maxNameLength: typeof rawQG?.maxNameLength === 'number' && rawQG.maxNameLength >= 10\n ? rawQG.maxNameLength\n : DEFAULTS.qualityGate.maxNameLength,\n maxFilesPerObservation: typeof rawQG?.maxFilesPerObservation === 'number'\n && rawQG.maxFilesPerObservation >= 1\n ? rawQG.maxFilesPerObservation\n : DEFAULTS.qualityGate.maxFilesPerObservation,\n typeConfidenceThresholds: typeConf,\n fileNonChangeMultiplier: fileMultiplier,\n };\n\n // Relationship detector\n let minEdge = typeof raw.relationshipDetector?.minEdgeConfidence === 'number'\n ? raw.relationshipDetector.minEdgeConfidence\n : DEFAULTS.relationshipDetector.minEdgeConfidence;\n if (minEdge < 0 || minEdge > 1) {\n minEdge = DEFAULTS.relationshipDetector.minEdgeConfidence;\n }\n const relationshipDetector = { minEdgeConfidence: minEdge };\n\n // Temporal decay\n const rawTD = raw.temporalDecay;\n const temporalDecay = {\n halfLifeDays: typeof rawTD?.halfLifeDays === 'number' && rawTD.halfLifeDays > 0\n ? rawTD.halfLifeDays\n : DEFAULTS.temporalDecay.halfLifeDays,\n minFloor: typeof rawTD?.minFloor === 'number' && rawTD.minFloor >= 0 && rawTD.minFloor < 1\n ? rawTD.minFloor\n : DEFAULTS.temporalDecay.minFloor,\n deletionThreshold: typeof rawTD?.deletionThreshold === 'number'\n && rawTD.deletionThreshold >= 0 && rawTD.deletionThreshold < 1\n ? rawTD.deletionThreshold\n : DEFAULTS.temporalDecay.deletionThreshold,\n maxAgeDays: typeof rawTD?.maxAgeDays === 'number' && rawTD.maxAgeDays > 0\n ? rawTD.maxAgeDays\n : DEFAULTS.temporalDecay.maxAgeDays,\n };\n\n // Fuzzy dedup\n const rawFD = raw.fuzzyDedup;\n const fuzzyDedup = {\n maxLevenshteinDistance: typeof rawFD?.maxLevenshteinDistance === 'number'\n && rawFD.maxLevenshteinDistance >= 1\n ? rawFD.maxLevenshteinDistance\n : DEFAULTS.fuzzyDedup.maxLevenshteinDistance,\n jaccardThreshold: typeof rawFD?.jaccardThreshold === 'number'\n && rawFD.jaccardThreshold > 0 && rawFD.jaccardThreshold <= 1\n ? rawFD.jaccardThreshold\n : DEFAULTS.fuzzyDedup.jaccardThreshold,\n };\n\n return {\n enabled,\n signalClassifier,\n qualityGate,\n relationshipDetector,\n temporalDecay,\n fuzzyDedup,\n };\n}\n","/**\n * Observation merging for knowledge graph curation.\n *\n * Detects near-duplicate observations linked to the same entity using:\n * - Cosine similarity on embeddings (threshold 0.95)\n * - Jaccard similarity on tokenized words (threshold 0.85) as fallback\n *\n * Merging creates consolidated summaries, preserves audit trails via\n * soft-deletion, and computes mean embeddings for consolidated observations.\n *\n * Low-value pruning removes very short, unlinked, old, auto-captured\n * observations using conservative AND-logic (all criteria must match).\n */\n\nimport type BetterSqlite3 from 'better-sqlite3';\nimport { randomBytes } from 'node:crypto';\n\nimport { jaccardSimilarity } from '../shared/similarity.js';\n\n// =============================================================================\n// Types\n// =============================================================================\n\n/**\n * A cluster of observations that are similar enough to merge.\n */\nexport interface MergeCluster {\n entityId: string;\n observations: Array<{\n id: string;\n text: string;\n embedding: number[] | null;\n created_at: string;\n }>;\n similarity: number;\n suggestedSummary: string;\n}\n\ninterface ObsRow {\n id: string;\n content: string;\n embedding: Buffer | null;\n created_at: string;\n source: string;\n deleted_at: string | null;\n}\n\ninterface NodeRow {\n id: string;\n observation_ids: string;\n}\n\n// =============================================================================\n// Similarity Functions\n// =============================================================================\n\n/**\n * Computes cosine similarity between two number arrays.\n * Returns 0 for zero-length or zero-norm vectors.\n */\nfunction cosineSimilarity(a: number[], b: number[]): number {\n if (a.length !== b.length || a.length === 0) return 0;\n\n let dot = 0;\n let normA = 0;\n let normB = 0;\n\n for (let i = 0; i < a.length; i++) {\n dot += a[i] * b[i];\n normA += a[i] * a[i];\n normB += b[i] * b[i];\n }\n\n const denom = Math.sqrt(normA) * Math.sqrt(normB);\n if (denom === 0) return 0;\n\n return dot / denom;\n}\n\n// jaccardSimilarity imported from ../shared/similarity.js\n\n/**\n * Converts a Buffer of Float32 values to a number array.\n */\nfunction bufferToNumbers(buf: Buffer): number[] {\n const floats = new Float32Array(\n buf.buffer,\n buf.byteOffset,\n buf.byteLength / 4,\n );\n return Array.from(floats);\n}\n\n// =============================================================================\n// Clustering\n// =============================================================================\n\n/**\n * Generates a consolidated summary from a cluster of observations.\n *\n * Strategy:\n * 1. Take the longest observation as the base\n * 2. Find unique keywords in shorter observations\n * 3. Append unique info in parentheses\n * 4. Prepend \"[Consolidated from N observations]\"\n */\nfunction generateSummary(\n observations: Array<{ text: string }>,\n): string {\n if (observations.length === 0) return '';\n if (observations.length === 1) return observations[0].text;\n\n // Find longest observation as base\n const sorted = [...observations].sort(\n (a, b) => b.text.length - a.text.length,\n );\n const base = sorted[0];\n const baseWords = new Set(\n base.text\n .toLowerCase()\n .split(/\\s+/)\n .filter((w) => w.length > 2),\n );\n\n // Collect unique keywords from shorter observations\n const uniqueKeywords: string[] = [];\n for (let i = 1; i < sorted.length; i++) {\n const words = sorted[i].text\n .split(/\\s+/)\n .filter((w) => w.length > 2);\n for (const word of words) {\n if (\n !baseWords.has(word.toLowerCase()) &&\n !uniqueKeywords.includes(word.toLowerCase())\n ) {\n uniqueKeywords.push(word.toLowerCase());\n }\n }\n }\n\n let summary = base.text;\n if (uniqueKeywords.length > 0) {\n const extras = uniqueKeywords.slice(0, 10).join(', ');\n summary += ` (also: ${extras})`;\n }\n\n return `[Consolidated from ${observations.length} observations] ${summary}`;\n}\n\n/**\n * Finds clusters of similar observations for the same entity.\n *\n * For each entity with 3+ observations:\n * 1. Compute pairwise similarities (cosine on embeddings, Jaccard on text)\n * 2. Cluster observations where ALL pairwise similarities exceed threshold\n * 3. Generate suggested summaries for each cluster\n *\n * Only clusters with 2+ observations are returned, sorted by size DESC.\n *\n * @param db - better-sqlite3 Database handle\n * @param opts - threshold (default 0.95 cosine / 0.85 Jaccard), entityId filter\n * @returns Mergeable observation clusters sorted by size descending\n */\nexport function findMergeableClusters(\n db: BetterSqlite3.Database,\n opts?: { threshold?: number; entityId?: string },\n): MergeCluster[] {\n const embeddingThreshold = opts?.threshold ?? 0.95;\n const textThreshold = 0.85;\n\n // Get entity nodes with 3+ observations\n let nodes: NodeRow[];\n if (opts?.entityId) {\n const row = db\n .prepare('SELECT id, observation_ids FROM graph_nodes WHERE id = ?')\n .get(opts.entityId) as NodeRow | undefined;\n nodes = row ? [row] : [];\n } else {\n nodes = db\n .prepare('SELECT id, observation_ids FROM graph_nodes')\n .all() as NodeRow[];\n }\n\n const clusters: MergeCluster[] = [];\n\n for (const node of nodes) {\n const obsIds = JSON.parse(node.observation_ids) as string[];\n if (obsIds.length < 3) continue;\n\n // Fetch observations (only non-deleted, non-merged)\n const placeholders = obsIds.map(() => '?').join(', ');\n const rows = db\n .prepare(\n `SELECT id, content, embedding, created_at, source, deleted_at\n FROM observations\n WHERE id IN (${placeholders}) AND deleted_at IS NULL`,\n )\n .all(...obsIds) as ObsRow[];\n\n if (rows.length < 2) continue;\n\n // Build observation objects with parsed embeddings\n const observations = rows.map((r) => ({\n id: r.id,\n text: r.content,\n embedding: r.embedding ? bufferToNumbers(r.embedding) : null,\n created_at: r.created_at,\n }));\n\n // Find clusters using greedy algorithm\n const used = new Set<string>();\n\n for (let i = 0; i < observations.length; i++) {\n if (used.has(observations[i].id)) continue;\n\n const cluster = [observations[i]];\n let totalSim = 0;\n let pairCount = 0;\n\n for (let j = i + 1; j < observations.length; j++) {\n if (used.has(observations[j].id)) continue;\n\n // Check if candidate is similar to ALL members of the current cluster\n let allSimilar = true;\n let candidateSim = 0;\n let candidatePairs = 0;\n\n for (const member of cluster) {\n const sim = computeSimilarity(\n member,\n observations[j],\n embeddingThreshold,\n textThreshold,\n );\n\n if (sim === null) {\n allSimilar = false;\n break;\n }\n\n candidateSim += sim;\n candidatePairs++;\n }\n\n if (allSimilar && candidatePairs > 0) {\n cluster.push(observations[j]);\n totalSim += candidateSim;\n pairCount += candidatePairs;\n }\n }\n\n if (cluster.length >= 2) {\n // Mark all observations in this cluster as used\n for (const obs of cluster) {\n used.add(obs.id);\n }\n\n const avgSim = pairCount > 0 ? totalSim / pairCount : 0;\n\n clusters.push({\n entityId: node.id,\n observations: cluster,\n similarity: avgSim,\n suggestedSummary: generateSummary(cluster),\n });\n }\n }\n }\n\n // Sort by cluster size DESC (largest first)\n clusters.sort((a, b) => b.observations.length - a.observations.length);\n\n return clusters;\n}\n\n/**\n * Computes similarity between two observations.\n * Returns the similarity score if it exceeds the threshold, or null if not.\n */\nfunction computeSimilarity(\n a: { text: string; embedding: number[] | null },\n b: { text: string; embedding: number[] | null },\n embeddingThreshold: number,\n textThreshold: number,\n): number | null {\n // Prefer cosine similarity on embeddings\n if (a.embedding && b.embedding) {\n const sim = cosineSimilarity(a.embedding, b.embedding);\n return sim >= embeddingThreshold ? sim : null;\n }\n\n // Fallback to Jaccard similarity on text\n const sim = jaccardSimilarity(a.text, b.text);\n return sim >= textThreshold ? sim : null;\n}\n\n// =============================================================================\n// Merging\n// =============================================================================\n\n/**\n * Merges a cluster of similar observations into a consolidated observation.\n *\n * Steps:\n * 1. Create new consolidated observation with suggestedSummary text\n * 2. Store merge metadata (merged_from, merged_at, original_count)\n * 3. Update entity's observation_ids: remove old, add new merged ID\n * 4. Soft-delete originals (set deleted_at, do NOT hard delete)\n * 5. Compute mean embedding if originals have embeddings\n *\n * Runs in a transaction for atomicity.\n *\n * @param db - better-sqlite3 Database handle\n * @param cluster - The cluster to merge\n * @returns The new merged observation ID and removed IDs\n */\nexport function mergeObservationCluster(\n db: BetterSqlite3.Database,\n cluster: MergeCluster,\n): { mergedId: string; removedIds: string[] } {\n const merge = db.transaction(() => {\n const mergedId = randomBytes(16).toString('hex');\n const now = new Date().toISOString();\n const removedIds = cluster.observations.map((o) => o.id);\n\n // Step 1 & 2: Create consolidated observation with merge metadata\n const metadata = JSON.stringify({\n merged_from: removedIds,\n merged_at: now,\n original_count: cluster.observations.length,\n });\n\n // Compute mean embedding if available\n let meanEmbedding: Buffer | null = null;\n const embeddingsWithValues = cluster.observations.filter(\n (o) => o.embedding !== null,\n );\n\n if (embeddingsWithValues.length > 0) {\n const dim = embeddingsWithValues[0].embedding!.length;\n const mean = new Float32Array(dim);\n\n for (const obs of embeddingsWithValues) {\n const emb = obs.embedding!;\n for (let i = 0; i < dim; i++) {\n mean[i] += emb[i];\n }\n }\n\n for (let i = 0; i < dim; i++) {\n mean[i] /= embeddingsWithValues.length;\n }\n\n meanEmbedding = Buffer.from(mean.buffer);\n }\n\n // Get project_hash from the first original observation\n const firstObs = db\n .prepare('SELECT project_hash, source FROM observations WHERE id = ?')\n .get(cluster.observations[0].id) as\n | { project_hash: string; source: string }\n | undefined;\n\n const projectHash = firstObs?.project_hash ?? 'unknown';\n\n db.prepare(\n `INSERT INTO observations (id, project_hash, content, title, source, session_id, embedding, created_at, updated_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n ).run(\n mergedId,\n projectHash,\n cluster.suggestedSummary,\n `[Merged] ${metadata}`,\n 'curation:merge',\n null,\n meanEmbedding,\n now,\n now,\n );\n\n // Step 3: Update entity's observation_ids\n const nodeRow = db\n .prepare('SELECT observation_ids FROM graph_nodes WHERE id = ?')\n .get(cluster.entityId) as { observation_ids: string } | undefined;\n\n if (nodeRow) {\n const currentIds = JSON.parse(nodeRow.observation_ids) as string[];\n const removedSet = new Set(removedIds);\n const updatedIds = currentIds.filter((id) => !removedSet.has(id));\n updatedIds.push(mergedId);\n\n db.prepare(\n `UPDATE graph_nodes SET observation_ids = ?, updated_at = datetime('now') WHERE id = ?`,\n ).run(JSON.stringify(updatedIds), cluster.entityId);\n }\n\n // Step 4: Soft-delete original observations\n const softDeleteStmt = db.prepare(\n `UPDATE observations SET deleted_at = ? WHERE id = ?`,\n );\n for (const obsId of removedIds) {\n softDeleteStmt.run(now, obsId);\n }\n\n return { mergedId, removedIds };\n });\n\n return merge();\n}\n\n// =============================================================================\n// Low-Value Pruning\n// =============================================================================\n\n/**\n * Prunes low-value observations using conservative AND-logic.\n *\n * An observation is pruned ONLY if ALL of:\n * a. Very short (< minTextLength characters, default 20)\n * b. No linked entities (not in any graph_node's observation_ids)\n * c. Older than maxAge days (default 90)\n * d. Auto-captured (source is NOT 'mcp:save_memory' or 'slash:remember')\n * e. Not already deleted\n *\n * Pruning is soft-delete only -- sets deleted_at, never hard deletes.\n *\n * @param db - better-sqlite3 Database handle\n * @param opts - Configurable thresholds\n * @returns Count of pruned observations\n */\nexport function pruneLowValue(\n db: BetterSqlite3.Database,\n opts?: { minTextLength?: number; maxAge?: number },\n): { pruned: number } {\n const minTextLength = opts?.minTextLength ?? 20;\n const maxAgeDays = opts?.maxAge ?? 90;\n\n const now = new Date();\n const cutoffDate = new Date(\n now.getTime() - maxAgeDays * 24 * 60 * 60 * 1000,\n );\n const cutoffISO = cutoffDate.toISOString();\n\n // Find candidate observations: short, old, auto-captured, not deleted\n const candidates = db\n .prepare(\n `SELECT id, content, source, created_at\n FROM observations\n WHERE deleted_at IS NULL\n AND LENGTH(content) < ?\n AND created_at < ?\n AND source NOT IN ('mcp:save_memory', 'slash:remember')`,\n )\n .all(minTextLength, cutoffISO) as Array<{\n id: string;\n content: string;\n source: string;\n created_at: string;\n }>;\n\n if (candidates.length === 0) return { pruned: 0 };\n\n // Check which candidates have NO linked entities\n // Build a set of all observation IDs referenced by any graph node\n const allNodeObsIds = new Set<string>();\n const nodes = db\n .prepare('SELECT observation_ids FROM graph_nodes')\n .all() as Array<{ observation_ids: string }>;\n\n for (const node of nodes) {\n const ids = JSON.parse(node.observation_ids) as string[];\n for (const id of ids) {\n allNodeObsIds.add(id);\n }\n }\n\n // Filter: only prune candidates that are NOT linked to any entity\n const toPrune = candidates.filter((c) => !allNodeObsIds.has(c.id));\n\n if (toPrune.length === 0) return { pruned: 0 };\n\n // Soft-delete\n const nowISO = now.toISOString();\n const softDeleteStmt = db.prepare(\n 'UPDATE observations SET deleted_at = ? WHERE id = ?',\n );\n\n const prune = db.transaction(() => {\n for (const obs of toPrune) {\n softDeleteStmt.run(nowISO, obs.id);\n }\n return toPrune.length;\n });\n\n const pruned = prune();\n\n return { pruned };\n}\n","/**\n * Fuzzy deduplication strategies for knowledge graph entities.\n *\n * Extends the existing exact-match, abbreviation, and path normalization\n * strategies with:\n * - Levenshtein distance (max 2 chars) for typo tolerance\n * - Jaccard word similarity (0.7 threshold) on tokenized names\n * - Path suffix matching for File type\n *\n * These are integrated into the entity deduplication pipeline via\n * findFuzzyDuplicates(), called from constraints.ts.\n */\n\nimport type { GraphNode } from './types.js';\nimport type { GraphExtractionConfig } from '../config/graph-extraction-config.js';\n\n// =============================================================================\n// Constants\n// =============================================================================\n\nconst DEFAULT_MAX_LEVENSHTEIN = 2;\nconst DEFAULT_JACCARD_THRESHOLD = 0.7;\n\n// =============================================================================\n// Levenshtein Distance\n// =============================================================================\n\n/**\n * Computes Levenshtein edit distance between two strings.\n * Uses the iterative matrix approach with O(min(m,n)) space.\n */\nexport function levenshteinDistance(a: string, b: string): number {\n if (a === b) return 0;\n if (a.length === 0) return b.length;\n if (b.length === 0) return a.length;\n\n // Ensure a is the shorter string for space optimization\n if (a.length > b.length) {\n [a, b] = [b, a];\n }\n\n const aLen = a.length;\n const bLen = b.length;\n\n let prev = new Array<number>(aLen + 1);\n let curr = new Array<number>(aLen + 1);\n\n for (let i = 0; i <= aLen; i++) {\n prev[i] = i;\n }\n\n for (let j = 1; j <= bLen; j++) {\n curr[0] = j;\n for (let i = 1; i <= aLen; i++) {\n const cost = a[i - 1] === b[j - 1] ? 0 : 1;\n curr[i] = Math.min(\n prev[i] + 1, // deletion\n curr[i - 1] + 1, // insertion\n prev[i - 1] + cost, // substitution\n );\n }\n [prev, curr] = [curr, prev];\n }\n\n return prev[aLen];\n}\n\n// =============================================================================\n// Jaccard Word Similarity\n// =============================================================================\n\n/**\n * Tokenizes a name by splitting on common delimiters: / . _ -\n * and lowercasing all tokens.\n */\nexport function tokenizeName(name: string): Set<string> {\n const tokens = name.toLowerCase().split(/[/._\\-\\s]+/).filter(t => t.length > 0);\n return new Set(tokens);\n}\n\n/**\n * Computes Jaccard similarity between two token sets.\n * J(A,B) = |A ∩ B| / |A ∪ B|\n * Returns 0 if both sets are empty.\n */\nexport function jaccardSimilarity(a: Set<string>, b: Set<string>): number {\n if (a.size === 0 && b.size === 0) return 0;\n\n let intersection = 0;\n for (const token of a) {\n if (b.has(token)) intersection++;\n }\n\n const union = a.size + b.size - intersection;\n return union === 0 ? 0 : intersection / union;\n}\n\n// =============================================================================\n// Path Suffix Matching\n// =============================================================================\n\n/**\n * Checks if two File paths refer to the same file via suffix matching.\n * For example: \"src/graph/types.ts\" and \"graph/types.ts\" match because\n * one is a suffix of the other.\n */\nexport function isPathSuffixMatch(path1: string, path2: string): boolean {\n // Normalize paths\n const norm1 = path1.replace(/^\\.\\//, '').replace(/\\\\/g, '/').toLowerCase();\n const norm2 = path2.replace(/^\\.\\//, '').replace(/\\\\/g, '/').toLowerCase();\n\n if (norm1 === norm2) return false; // Exact match handled elsewhere\n\n // Check if one is a suffix of the other\n return norm1.endsWith('/' + norm2) || norm2.endsWith('/' + norm1);\n}\n\n// =============================================================================\n// Fuzzy Duplicate Detection\n// =============================================================================\n\n/**\n * Finds fuzzy duplicates among a list of same-type nodes.\n *\n * Strategies applied:\n * 1. Levenshtein distance ≤ max (default 2) for typo tolerance\n * 2. Jaccard word similarity ≥ threshold (default 0.7)\n * 3. Path suffix matching for File type\n *\n * Only compares nodes of the same type. Returns grouped duplicate\n * candidates with reasons.\n *\n * @param nodes - Nodes to check (should be same type for best results)\n * @param config - Optional configuration overrides\n * @returns Array of duplicate groups with entities and reason\n */\nexport function findFuzzyDuplicates(\n nodes: GraphNode[],\n config?: GraphExtractionConfig,\n): Array<{ entities: GraphNode[]; reason: string }> {\n const maxLev = config?.fuzzyDedup?.maxLevenshteinDistance ?? DEFAULT_MAX_LEVENSHTEIN;\n const jaccardThresh = config?.fuzzyDedup?.jaccardThreshold ?? DEFAULT_JACCARD_THRESHOLD;\n\n const duplicates: Array<{ entities: GraphNode[]; reason: string }> = [];\n const seen = new Set<string>(); // Track already-grouped pairs\n\n for (let i = 0; i < nodes.length; i++) {\n for (let j = i + 1; j < nodes.length; j++) {\n const a = nodes[i];\n const b = nodes[j];\n\n // Only compare same-type entities\n if (a.type !== b.type) continue;\n\n const pairKey = [a.id, b.id].sort().join(',');\n if (seen.has(pairKey)) continue;\n\n const aLower = a.name.toLowerCase();\n const bLower = b.name.toLowerCase();\n\n // Skip exact case-insensitive matches (handled by existing strategy)\n if (aLower === bLower) continue;\n\n // Strategy 1: Levenshtein distance for short names (avoid expensive computation on long strings)\n if (aLower.length <= 50 && bLower.length <= 50) {\n // Only apply if lengths are similar (within maxLev difference)\n if (Math.abs(aLower.length - bLower.length) <= maxLev) {\n const dist = levenshteinDistance(aLower, bLower);\n if (dist > 0 && dist <= maxLev) {\n seen.add(pairKey);\n duplicates.push({\n entities: [a, b],\n reason: `Fuzzy match (Levenshtein distance ${dist}): \"${a.name}\" ↔ \"${b.name}\"`,\n });\n continue;\n }\n }\n }\n\n // Strategy 2: Jaccard word similarity\n const tokensA = tokenizeName(a.name);\n const tokensB = tokenizeName(b.name);\n // Only apply if both have multiple tokens (single-token names use Levenshtein)\n if (tokensA.size >= 2 && tokensB.size >= 2) {\n const similarity = jaccardSimilarity(tokensA, tokensB);\n if (similarity >= jaccardThresh) {\n seen.add(pairKey);\n duplicates.push({\n entities: [a, b],\n reason: `Fuzzy match (Jaccard similarity ${similarity.toFixed(2)}): \"${a.name}\" ↔ \"${b.name}\"`,\n });\n continue;\n }\n }\n\n // Strategy 3: Path suffix matching (File type only)\n if (a.type === 'File') {\n if (isPathSuffixMatch(a.name, b.name)) {\n seen.add(pairKey);\n duplicates.push({\n entities: [a, b],\n reason: `Path suffix match: \"${a.name}\" ↔ \"${b.name}\"`,\n });\n }\n }\n }\n }\n\n return duplicates;\n}\n","/**\n * Graph constraint enforcement.\n *\n * Maintains graph health through:\n * - Entity type taxonomy validation (defense-in-depth with SQL CHECK)\n * - Relationship type taxonomy validation\n * - Max degree enforcement (prune lowest-weight edges when cap exceeded)\n * - Entity deduplication detection and merging\n * - Graph health dashboard metrics\n *\n * All enforcement functions use transactions for atomicity.\n * Significant actions (pruning, merging) are logged with [laminark:graph] prefix.\n */\n\nimport type BetterSqlite3 from 'better-sqlite3';\n\nimport type { EntityType, RelationshipType, GraphNode, GraphEdge } from './types.js';\nimport {\n ENTITY_TYPES,\n RELATIONSHIP_TYPES,\n MAX_NODE_DEGREE,\n} from './types.js';\nimport {\n getEdgesForNode,\n countEdgesForNode,\n getNodesByType,\n} from './schema.js';\nimport { findFuzzyDuplicates } from './fuzzy-dedup.js';\nimport type { GraphExtractionConfig } from '../config/graph-extraction-config.js';\n\n// =============================================================================\n// Type Validation\n// =============================================================================\n\n/**\n * Runtime validation for entity types. Defense-in-depth alongside SQL CHECK.\n */\nexport function validateEntityType(type: string): type is EntityType {\n return (ENTITY_TYPES as readonly string[]).includes(type);\n}\n\n/**\n * Runtime validation for relationship types. Defense-in-depth alongside SQL CHECK.\n */\nexport function validateRelationshipType(type: string): type is RelationshipType {\n return (RELATIONSHIP_TYPES as readonly string[]).includes(type);\n}\n\n// =============================================================================\n// Max Degree Enforcement\n// =============================================================================\n\n/**\n * Enforces maximum edge count on a node by pruning lowest-weight edges.\n *\n * When a node exceeds maxDegree edges:\n * 1. Get all edges for the node\n * 2. Sort by weight ascending (lowest first)\n * 3. Delete lowest-weight edges until count <= maxDegree\n * 4. Log pruned count with [laminark:graph] prefix\n *\n * Runs in a transaction to prevent race conditions.\n *\n * @param db - Database handle\n * @param nodeId - The node to enforce degree cap on\n * @param maxDegree - Maximum allowed edges (default: MAX_NODE_DEGREE = 50)\n * @returns Object with pruned count and remaining count\n */\nexport function enforceMaxDegree(\n db: BetterSqlite3.Database,\n nodeId: string,\n maxDegree: number = MAX_NODE_DEGREE,\n): { pruned: number; remaining: number } {\n const enforce = db.transaction(() => {\n const currentCount = countEdgesForNode(db, nodeId);\n\n if (currentCount <= maxDegree) {\n return { pruned: 0, remaining: currentCount };\n }\n\n // Get all edges, sorted by weight ascending (lowest first)\n const edges = getEdgesForNode(db, nodeId);\n edges.sort((a, b) => a.weight - b.weight);\n\n const toPrune = currentCount - maxDegree;\n const edgesToDelete = edges.slice(0, toPrune);\n\n const deleteStmt = db.prepare('DELETE FROM graph_edges WHERE id = ?');\n for (const edge of edgesToDelete) {\n deleteStmt.run(edge.id);\n }\n\n const remaining = currentCount - toPrune;\n\n process.stderr.write(\n `[laminark:graph] Pruned ${toPrune} lowest-weight edges from node ${nodeId} (${remaining} remaining)\\n`,\n );\n\n return { pruned: toPrune, remaining };\n });\n\n return enforce();\n}\n\n// =============================================================================\n// Entity Merging\n// =============================================================================\n\n/**\n * Merges one entity node into another. The keepId node survives.\n *\n * Steps:\n * 1. Union observation_ids from both nodes (no duplicates)\n * 2. Reroute all edges from mergeId to keepId\n * 3. Handle duplicate edge conflicts (keep higher weight)\n * 4. Delete the mergeId node\n *\n * Runs in a transaction for atomicity.\n *\n * @param db - Database handle\n * @param keepId - The node to keep (survives merge)\n * @param mergeId - The node to merge and delete\n */\nexport function mergeEntities(\n db: BetterSqlite3.Database,\n keepId: string,\n mergeId: string,\n): void {\n const merge = db.transaction(() => {\n // Step 1: Get both nodes and merge observation_ids\n const keepRow = db\n .prepare('SELECT * FROM graph_nodes WHERE id = ?')\n .get(keepId) as { observation_ids: string; metadata: string } | undefined;\n const mergeRow = db\n .prepare('SELECT * FROM graph_nodes WHERE id = ?')\n .get(mergeId) as { observation_ids: string; metadata: string } | undefined;\n\n if (!keepRow || !mergeRow) {\n throw new Error(`Cannot merge: one or both nodes not found (keep=${keepId}, merge=${mergeId})`);\n }\n\n const keepObsIds = JSON.parse(keepRow.observation_ids) as string[];\n const mergeObsIds = JSON.parse(mergeRow.observation_ids) as string[];\n const mergedObsIds = [...new Set([...keepObsIds, ...mergeObsIds])];\n\n // Merge metadata (merge node values fill gaps, keep node values take priority)\n const keepMeta = JSON.parse(keepRow.metadata) as Record<string, unknown>;\n const mergeMeta = JSON.parse(mergeRow.metadata) as Record<string, unknown>;\n const mergedMeta = { ...mergeMeta, ...keepMeta };\n\n db.prepare(\n `UPDATE graph_nodes SET observation_ids = ?, metadata = ?, updated_at = datetime('now') WHERE id = ?`,\n ).run(JSON.stringify(mergedObsIds), JSON.stringify(mergedMeta), keepId);\n\n // Step 2: Get all edges connected to the merge node\n const mergeEdges = getEdgesForNode(db, mergeId);\n\n // Step 3: Reroute edges from mergeId to keepId\n for (const edge of mergeEdges) {\n let newSourceId = edge.source_id;\n let newTargetId = edge.target_id;\n\n if (edge.source_id === mergeId) {\n newSourceId = keepId;\n }\n if (edge.target_id === mergeId) {\n newTargetId = keepId;\n }\n\n // Skip self-loops (would happen if both source and target are the merge/keep pair)\n if (newSourceId === newTargetId) {\n db.prepare('DELETE FROM graph_edges WHERE id = ?').run(edge.id);\n continue;\n }\n\n // Check if rerouted edge would create a duplicate\n const existing = db\n .prepare(\n 'SELECT id, weight FROM graph_edges WHERE source_id = ? AND target_id = ? AND type = ?',\n )\n .get(newSourceId, newTargetId, edge.type) as\n | { id: string; weight: number }\n | undefined;\n\n if (existing && existing.id !== edge.id) {\n // Duplicate: keep higher weight, delete the lower one\n if (edge.weight > existing.weight) {\n db.prepare('UPDATE graph_edges SET weight = ? WHERE id = ?').run(\n edge.weight,\n existing.id,\n );\n }\n db.prepare('DELETE FROM graph_edges WHERE id = ?').run(edge.id);\n } else if (!existing) {\n // No duplicate: update the edge to point to keepId\n db.prepare(\n 'UPDATE graph_edges SET source_id = ?, target_id = ? WHERE id = ?',\n ).run(newSourceId, newTargetId, edge.id);\n }\n // If existing.id === edge.id, it's already correct (no-op)\n }\n\n // Step 4: Delete the merged node\n db.prepare('DELETE FROM graph_nodes WHERE id = ?').run(mergeId);\n\n process.stderr.write(\n `[laminark:graph] Merged entity ${mergeId} into ${keepId} (${mergeEdges.length} edges rerouted)\\n`,\n );\n });\n\n merge();\n}\n\n// =============================================================================\n// Duplicate Detection\n// =============================================================================\n\n/**\n * Common abbreviation mappings for duplicate detection.\n * Maps lowercase abbreviation -> lowercase full name.\n */\nconst ABBREVIATION_MAP: Record<string, string> = {\n // File extension abbreviations (for File entity dedup)\n ts: 'typescript',\n js: 'javascript',\n py: 'python',\n rb: 'ruby',\n rs: 'rust',\n};\n\n/**\n * Finds potential duplicate entities in the graph.\n *\n * Detection strategies:\n * a. Case-insensitive name match (e.g., \"React\" and \"react\")\n * b. Common abbreviation match (e.g., \"TS\" and \"TypeScript\")\n * c. Path normalization for Files (strip ./, normalize separators)\n *\n * Returns grouped duplicate candidates with reasons. This is a\n * SUGGESTION function -- use mergeEntities() to act on results.\n *\n * @param db - Database handle\n * @param opts - Optional filter by entity type\n * @returns Array of duplicate groups with entities and reason\n */\nexport function findDuplicateEntities(\n db: BetterSqlite3.Database,\n opts?: { type?: EntityType; graphConfig?: GraphExtractionConfig },\n): Array<{ entities: GraphNode[]; reason: string }> {\n // Get all nodes, optionally filtered by type\n let nodes: GraphNode[];\n if (opts?.type) {\n nodes = getNodesByType(db, opts.type);\n } else {\n const allTypes = ENTITY_TYPES;\n nodes = [];\n for (const type of allTypes) {\n nodes.push(...getNodesByType(db, type));\n }\n }\n\n const duplicates: Array<{ entities: GraphNode[]; reason: string }> = [];\n const seen = new Set<string>(); // Track already-grouped node IDs\n\n // Strategy A: Case-insensitive name match within same type\n const byTypeAndLowerName = new Map<string, GraphNode[]>();\n for (const node of nodes) {\n const key = `${node.type}:${node.name.toLowerCase()}`;\n const group = byTypeAndLowerName.get(key) ?? [];\n group.push(node);\n byTypeAndLowerName.set(key, group);\n }\n\n for (const [, group] of byTypeAndLowerName) {\n if (group.length > 1) {\n const ids = group.map((n) => n.id).sort().join(',');\n if (!seen.has(ids)) {\n seen.add(ids);\n duplicates.push({\n entities: group,\n reason: `Case-insensitive name match: \"${group[0].name}\" and \"${group[1].name}\"`,\n });\n }\n }\n }\n\n // Strategy B: Common abbreviation match within same type\n const byTypeAndCanonical = new Map<string, GraphNode[]>();\n for (const node of nodes) {\n const lower = node.name.toLowerCase();\n const canonical = ABBREVIATION_MAP[lower] ?? lower;\n const key = `${node.type}:${canonical}`;\n const group = byTypeAndCanonical.get(key) ?? [];\n group.push(node);\n byTypeAndCanonical.set(key, group);\n }\n\n for (const [, group] of byTypeAndCanonical) {\n if (group.length > 1) {\n // Check if this is a genuine abbreviation match (not just case match already found)\n const names = new Set(group.map((n) => n.name.toLowerCase()));\n if (names.size > 1) {\n const ids = group.map((n) => n.id).sort().join(',');\n if (!seen.has(ids)) {\n seen.add(ids);\n duplicates.push({\n entities: group,\n reason: `Common abbreviation match: \"${group[0].name}\" and \"${group[1].name}\"`,\n });\n }\n }\n }\n }\n\n // Strategy C: Path normalization for File type\n if (!opts?.type || opts.type === 'File') {\n const fileNodes = nodes.filter((n) => n.type === 'File');\n const byNormalizedPath = new Map<string, GraphNode[]>();\n\n for (const node of fileNodes) {\n let normalized = node.name;\n // Strip leading ./\n if (normalized.startsWith('./')) normalized = normalized.slice(2);\n // Normalize separators\n normalized = normalized.replace(/\\\\/g, '/');\n // Collapse double slashes\n normalized = normalized.replace(/\\/\\//g, '/');\n // Lowercase for comparison\n normalized = normalized.toLowerCase();\n\n const key = `File:${normalized}`;\n const group = byNormalizedPath.get(key) ?? [];\n group.push(node);\n byNormalizedPath.set(key, group);\n }\n\n for (const [, group] of byNormalizedPath) {\n if (group.length > 1) {\n const ids = group.map((n) => n.id).sort().join(',');\n if (!seen.has(ids)) {\n seen.add(ids);\n duplicates.push({\n entities: group,\n reason: `Path normalization match: \"${group[0].name}\" and \"${group[1].name}\"`,\n });\n }\n }\n }\n }\n\n // Strategy D: Fuzzy deduplication (Levenshtein, Jaccard, path suffix)\n // Group nodes by type for efficient comparison\n const byType = new Map<string, GraphNode[]>();\n for (const node of nodes) {\n const group = byType.get(node.type) ?? [];\n group.push(node);\n byType.set(node.type, group);\n }\n\n for (const [, typeNodes] of byType) {\n const fuzzyResults = findFuzzyDuplicates(typeNodes, opts?.graphConfig);\n for (const result of fuzzyResults) {\n const ids = result.entities.map((n) => n.id).sort().join(',');\n if (!seen.has(ids)) {\n seen.add(ids);\n duplicates.push(result);\n }\n }\n }\n\n return duplicates;\n}\n\n// =============================================================================\n// Graph Health Dashboard\n// =============================================================================\n\ninterface NodeRow {\n id: string;\n type: string;\n name: string;\n metadata: string;\n observation_ids: string;\n created_at: string;\n updated_at: string;\n}\n\n/**\n * Returns dashboard-style health metrics for the knowledge graph.\n *\n * Metrics:\n * - totalNodes: total entity count\n * - totalEdges: total relationship count\n * - avgDegree: average edges per node\n * - maxDegree: highest edge count on any single node\n * - hotspots: nodes with degree > 0.8 * MAX_NODE_DEGREE\n * - duplicateCandidates: number of detected duplicate groups\n */\nexport function getGraphHealth(db: BetterSqlite3.Database): {\n totalNodes: number;\n totalEdges: number;\n avgDegree: number;\n maxDegree: number;\n hotspots: Array<{ node: GraphNode; degree: number }>;\n duplicateCandidates: number;\n} {\n const totalNodes =\n (db.prepare('SELECT COUNT(*) as cnt FROM graph_nodes').get() as { cnt: number }).cnt;\n const totalEdges =\n (db.prepare('SELECT COUNT(*) as cnt FROM graph_edges').get() as { cnt: number }).cnt;\n\n const avgDegree = totalNodes > 0 ? (totalEdges * 2) / totalNodes : 0;\n\n // Find max degree and hotspot nodes\n const hotspotThreshold = Math.floor(0.8 * MAX_NODE_DEGREE);\n let maxDeg = 0;\n const hotspots: Array<{ node: GraphNode; degree: number }> = [];\n\n if (totalNodes > 0) {\n // Get degree for each node using a correlated subquery\n const degreeRows = db\n .prepare(\n `SELECT n.*,\n (SELECT COUNT(*) FROM graph_edges WHERE source_id = n.id OR target_id = n.id) as degree\n FROM graph_nodes n\n WHERE (SELECT COUNT(*) FROM graph_edges WHERE source_id = n.id OR target_id = n.id) > 0\n ORDER BY degree DESC`,\n )\n .all() as Array<NodeRow & { degree: number }>;\n\n for (const row of degreeRows) {\n if (row.degree > maxDeg) maxDeg = row.degree;\n\n if (row.degree > hotspotThreshold) {\n hotspots.push({\n node: {\n id: row.id,\n type: row.type as EntityType,\n name: row.name,\n metadata: JSON.parse(row.metadata) as Record<string, unknown>,\n observation_ids: JSON.parse(row.observation_ids) as string[],\n created_at: row.created_at,\n updated_at: row.updated_at,\n },\n degree: row.degree,\n });\n }\n }\n }\n\n // Count duplicate candidates\n const dupes = findDuplicateEntities(db);\n\n return {\n totalNodes,\n totalEdges,\n avgDegree,\n maxDegree: maxDeg,\n hotspots,\n duplicateCandidates: dupes.length,\n };\n}\n","/**\n * Temporal decay for graph edge weights.\n *\n * Edge weights decay over time using exponential function inspired by\n * memento-mcp and agent-memory servers. Edges that aren't reinforced\n * (re-detected) gradually lose weight and eventually get deleted.\n *\n * Formula: weight * e^(-ln(2)/halfLife * ageDays)\n *\n * This maintains a living graph where recent knowledge is prominent\n * and old, unreinforced knowledge fades naturally.\n */\n\nimport type BetterSqlite3 from 'better-sqlite3';\n\nimport type { GraphExtractionConfig } from '../config/graph-extraction-config.js';\n\n// =============================================================================\n// Types\n// =============================================================================\n\nexport interface TemporalDecayConfig {\n /** Half-life in days. After this many days, unreinforced edges are at 50% weight. */\n halfLifeDays: number;\n /** Minimum floor weight. Edges never decay below this value (until deletion). */\n minFloor: number;\n /** Edges below this weight are deleted during curation. */\n deletionThreshold: number;\n /** Maximum age in days. Edges older than this are always deleted. */\n maxAgeDays: number;\n}\n\nexport interface DecayResult {\n updated: number;\n deleted: number;\n}\n\n// =============================================================================\n// Constants\n// =============================================================================\n\nconst DEFAULTS: TemporalDecayConfig = {\n halfLifeDays: 30,\n minFloor: 0.05,\n deletionThreshold: 0.08,\n maxAgeDays: 180,\n};\n\n// =============================================================================\n// Core Decay Functions\n// =============================================================================\n\n/**\n * Calculates the decayed weight for an edge based on its age.\n *\n * Uses exponential decay: weight * e^(-ln(2)/halfLife * ageDays)\n * Result is clamped to the minimum floor.\n *\n * @param originalWeight - The edge's current stored weight\n * @param ageDays - Age of the edge in days\n * @param config - Decay parameters\n * @returns The decayed weight value\n */\nexport function calculateDecayedWeight(\n originalWeight: number,\n ageDays: number,\n config?: Partial<TemporalDecayConfig>,\n): number {\n const halfLife = config?.halfLifeDays ?? DEFAULTS.halfLifeDays;\n const minFloor = config?.minFloor ?? DEFAULTS.minFloor;\n\n if (ageDays <= 0) return originalWeight;\n\n const decayRate = Math.LN2 / halfLife;\n const decayed = originalWeight * Math.exp(-decayRate * ageDays);\n\n return Math.max(decayed, minFloor);\n}\n\n/**\n * Applies temporal decay to all edges in the graph.\n *\n * For each edge:\n * 1. Calculate age from created_at timestamp\n * 2. Apply exponential decay formula\n * 3. Delete edges below deletion threshold or older than max age\n * 4. Update remaining edges with new decayed weights\n *\n * Runs in a transaction for atomicity.\n *\n * @param db - Database handle\n * @param graphConfig - Optional configuration from graph-extraction-config\n * @returns Count of updated and deleted edges\n */\nexport function applyTemporalDecay(\n db: BetterSqlite3.Database,\n graphConfig?: GraphExtractionConfig,\n): DecayResult {\n const halfLife = graphConfig?.temporalDecay?.halfLifeDays ?? DEFAULTS.halfLifeDays;\n const minFloor = graphConfig?.temporalDecay?.minFloor ?? DEFAULTS.minFloor;\n const deletionThreshold = graphConfig?.temporalDecay?.deletionThreshold ?? DEFAULTS.deletionThreshold;\n const maxAgeDays = graphConfig?.temporalDecay?.maxAgeDays ?? DEFAULTS.maxAgeDays;\n\n let updated = 0;\n let deleted = 0;\n\n const run = db.transaction(() => {\n // Get all edges with their age in days\n const edges = db.prepare(`\n SELECT id, weight,\n julianday('now') - julianday(created_at) as age_days\n FROM graph_edges\n `).all() as Array<{ id: string; weight: number; age_days: number }>;\n\n const deleteStmt = db.prepare('DELETE FROM graph_edges WHERE id = ?');\n const updateStmt = db.prepare('UPDATE graph_edges SET weight = ? WHERE id = ?');\n\n for (const edge of edges) {\n // Delete edges older than max age\n if (edge.age_days > maxAgeDays) {\n deleteStmt.run(edge.id);\n deleted++;\n continue;\n }\n\n // Calculate decayed weight\n const decayed = calculateDecayedWeight(edge.weight, edge.age_days, {\n halfLifeDays: halfLife,\n minFloor,\n });\n\n // Delete edges below deletion threshold\n if (decayed < deletionThreshold) {\n deleteStmt.run(edge.id);\n deleted++;\n continue;\n }\n\n // Update weight if it changed meaningfully (avoid unnecessary writes)\n if (Math.abs(decayed - edge.weight) > 0.001) {\n updateStmt.run(decayed, edge.id);\n updated++;\n }\n }\n });\n\n run();\n return { updated, deleted };\n}\n","/**\n * Background curation agent for knowledge graph maintenance.\n *\n * Runs during quiet periods (session end, long pauses) to keep the\n * knowledge base high-quality as it grows. Performs:\n * 1. Observation merging (near-duplicate consolidation)\n * 2. Entity deduplication (case-insensitive, abbreviation, path)\n * 3. Graph constraint enforcement (approaching degree cap)\n * 4. Staleness sweep (contradiction flagging)\n * 5. Low-value pruning (short + unlinked + old + auto-captured)\n *\n * Each step is isolated -- one failure does not stop others.\n * The agent is idempotent: running twice produces the same result.\n * Curation NEVER crashes the main process.\n */\n\nimport type BetterSqlite3 from 'better-sqlite3';\n\nimport {\n findMergeableClusters,\n mergeObservationCluster,\n pruneLowValue,\n} from './observation-merger.js';\nimport {\n findDuplicateEntities,\n mergeEntities,\n enforceMaxDegree,\n} from './constraints.js';\nimport { countEdgesForNode } from './schema.js';\nimport {\n detectStaleness,\n flagStaleObservation,\n initStalenessSchema,\n} from './staleness.js';\nimport { MAX_NODE_DEGREE } from './types.js';\nimport { applyTemporalDecay } from './temporal-decay.js';\nimport type { GraphExtractionConfig } from '../config/graph-extraction-config.js';\n\n// =============================================================================\n// Types\n// =============================================================================\n\n/**\n * Report of a completed curation cycle.\n */\nexport interface CurationReport {\n startedAt: string;\n completedAt: string;\n observationsMerged: number;\n entitiesDeduplicated: number;\n stalenessFlagsAdded: number;\n lowValuePruned: number;\n temporalDecayUpdated: number;\n temporalDecayDeleted: number;\n errors: string[];\n}\n\n// =============================================================================\n// Standalone Curation Function\n// =============================================================================\n\n/**\n * Runs a single curation cycle on the knowledge graph.\n *\n * Executes five steps in order:\n * 1. Merge similar observations\n * 2. Deduplicate entities\n * 3. Enforce graph constraints (approaching degree cap)\n * 4. Staleness sweep\n * 5. Low-value pruning\n *\n * Each step is wrapped in try/catch -- if one fails, the rest continue.\n * Returns a CurationReport documenting all actions taken.\n *\n * This function is idempotent: running it twice in a row produces the\n * same result (merged observations do not re-merge, already-flagged\n * stale observations do not get re-flagged, etc.)\n *\n * @param db - better-sqlite3 Database handle\n * @returns CurationReport with counts and any errors\n */\nexport async function runCuration(\n db: BetterSqlite3.Database,\n graphConfig?: GraphExtractionConfig,\n): Promise<CurationReport> {\n const startedAt = new Date().toISOString();\n const errors: string[] = [];\n let observationsMerged = 0;\n let entitiesDeduplicated = 0;\n let stalenessFlagsAdded = 0;\n let lowValuePruned = 0;\n let temporalDecayUpdated = 0;\n let temporalDecayDeleted = 0;\n\n // Ensure staleness schema exists\n try {\n initStalenessSchema(db);\n } catch (err) {\n errors.push(`Schema init: ${err instanceof Error ? err.message : String(err)}`);\n }\n\n // -----------------------------------------------------------------------\n // Step 1: Merge similar observations\n // -----------------------------------------------------------------------\n try {\n const clusters = findMergeableClusters(db);\n for (const cluster of clusters) {\n try {\n const result = mergeObservationCluster(db, cluster);\n observationsMerged += result.removedIds.length;\n } catch (err) {\n errors.push(\n `Merge cluster (entity ${cluster.entityId}): ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n }\n } catch (err) {\n errors.push(\n `Step 1 (merge): ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n\n // -----------------------------------------------------------------------\n // Step 2: Deduplicate entities\n // -----------------------------------------------------------------------\n try {\n const duplicates = findDuplicateEntities(db);\n for (const group of duplicates) {\n if (group.entities.length < 2) continue;\n\n // Keep the entity with more observation_ids\n const sorted = [...group.entities].sort(\n (a, b) => b.observation_ids.length - a.observation_ids.length,\n );\n const keepId = sorted[0].id;\n\n for (let i = 1; i < sorted.length; i++) {\n try {\n mergeEntities(db, keepId, sorted[i].id);\n entitiesDeduplicated++;\n } catch (err) {\n errors.push(\n `Dedup (${sorted[0].name} <- ${sorted[i].name}): ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n }\n }\n } catch (err) {\n errors.push(\n `Step 2 (dedup): ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n\n // -----------------------------------------------------------------------\n // Step 3: Enforce graph constraints (nodes approaching degree cap)\n // -----------------------------------------------------------------------\n try {\n const threshold = Math.floor(MAX_NODE_DEGREE * 0.9);\n const nodeRows = db\n .prepare('SELECT id FROM graph_nodes')\n .all() as Array<{ id: string }>;\n\n for (const row of nodeRows) {\n try {\n const degree = countEdgesForNode(db, row.id);\n if (degree > threshold) {\n enforceMaxDegree(db, row.id);\n }\n } catch (err) {\n errors.push(\n `Constraint (node ${row.id}): ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n }\n } catch (err) {\n errors.push(\n `Step 3 (constraints): ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n\n // -----------------------------------------------------------------------\n // Step 4: Staleness sweep\n // -----------------------------------------------------------------------\n try {\n // Check recently updated entities for contradictions\n const recentNodes = db\n .prepare(\n `SELECT id FROM graph_nodes WHERE updated_at >= datetime('now', '-24 hours')`,\n )\n .all() as Array<{ id: string }>;\n\n // Get already-flagged observation IDs to avoid re-flagging\n const existingFlags = new Set<string>();\n try {\n const flagRows = db\n .prepare('SELECT observation_id FROM staleness_flags WHERE resolved = 0')\n .all() as Array<{ observation_id: string }>;\n for (const row of flagRows) {\n existingFlags.add(row.observation_id);\n }\n } catch {\n // Table might not exist yet -- that's fine\n }\n\n for (const node of recentNodes) {\n try {\n const reports = detectStaleness(db, node.id);\n for (const report of reports) {\n // Only flag if not already flagged (idempotency)\n if (!existingFlags.has(report.olderObservation.id)) {\n flagStaleObservation(db, report.olderObservation.id, report.reason);\n existingFlags.add(report.olderObservation.id);\n stalenessFlagsAdded++;\n }\n }\n } catch (err) {\n errors.push(\n `Staleness (node ${node.id}): ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n }\n } catch (err) {\n errors.push(\n `Step 4 (staleness): ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n\n // -----------------------------------------------------------------------\n // Step 5: Low-value pruning\n // -----------------------------------------------------------------------\n try {\n const result = pruneLowValue(db);\n lowValuePruned = result.pruned;\n } catch (err) {\n errors.push(\n `Step 5 (prune): ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n\n // -----------------------------------------------------------------------\n // Step 6: Temporal decay of edge weights\n // -----------------------------------------------------------------------\n try {\n const decayResult = applyTemporalDecay(db, graphConfig);\n temporalDecayUpdated = decayResult.updated;\n temporalDecayDeleted = decayResult.deleted;\n } catch (err) {\n errors.push(\n `Step 6 (temporal decay): ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n\n const completedAt = new Date().toISOString();\n\n const report: CurationReport = {\n startedAt,\n completedAt,\n observationsMerged,\n entitiesDeduplicated,\n stalenessFlagsAdded,\n lowValuePruned,\n temporalDecayUpdated,\n temporalDecayDeleted,\n errors,\n };\n\n process.stderr.write(\n `[laminark:curation] Cycle complete: ${observationsMerged} merged, ${entitiesDeduplicated} deduped, ${stalenessFlagsAdded} flagged stale, ${lowValuePruned} pruned, ${temporalDecayUpdated} decayed, ${temporalDecayDeleted} decay-deleted\\n`,\n );\n\n return report;\n}\n\n// =============================================================================\n// CurationAgent Class\n// =============================================================================\n\n/**\n * Background curation agent that runs periodically or on-demand.\n *\n * Manages scheduling, lifecycle, and reporting. Uses the standalone\n * runCuration() function for the actual curation logic.\n */\nexport class CurationAgent {\n private db: BetterSqlite3.Database;\n private intervalMs: number;\n private onComplete?: (report: CurationReport) => void;\n private graphConfig?: GraphExtractionConfig;\n private running: boolean = false;\n private cycling: boolean = false;\n private lastRun: string | null = null;\n private timer: ReturnType<typeof setInterval> | null = null;\n\n constructor(\n db: BetterSqlite3.Database,\n opts?: {\n intervalMs?: number;\n onComplete?: (report: CurationReport) => void;\n graphConfig?: GraphExtractionConfig;\n },\n ) {\n this.db = db;\n this.intervalMs = opts?.intervalMs ?? 300_000; // 5 minutes default\n this.onComplete = opts?.onComplete;\n this.graphConfig = opts?.graphConfig;\n }\n\n /**\n * Start periodic curation on setInterval.\n */\n start(): void {\n if (this.running) return;\n\n this.running = true;\n this.timer = setInterval(() => {\n void this.runOnce();\n }, this.intervalMs);\n\n process.stderr.write(\n `[laminark:curation] Agent started, interval: ${this.intervalMs}ms\\n`,\n );\n }\n\n /**\n * Stop the periodic curation timer.\n */\n stop(): void {\n if (this.timer) {\n clearInterval(this.timer);\n this.timer = null;\n }\n this.running = false;\n\n process.stderr.write('[laminark:curation] Agent stopped\\n');\n }\n\n /**\n * Execute one curation cycle. This is the main entry point.\n */\n async runOnce(): Promise<CurationReport> {\n if (this.cycling) return { startedAt: '', completedAt: '', observationsMerged: 0, entitiesDeduplicated: 0, stalenessFlagsAdded: 0, lowValuePruned: 0, temporalDecayUpdated: 0, temporalDecayDeleted: 0, errors: ['skipped: previous cycle still running'] };\n this.cycling = true;\n try {\n const report = await runCuration(this.db, this.graphConfig);\n this.lastRun = report.completedAt;\n\n if (this.onComplete) {\n this.onComplete(report);\n }\n\n return report;\n } finally {\n this.cycling = false;\n }\n }\n\n /**\n * Whether the agent is currently running.\n */\n isRunning(): boolean {\n return this.running;\n }\n\n /**\n * Timestamp of the last completed curation run.\n */\n getLastRun(): string | null {\n return this.lastRun;\n }\n}\n\n// =============================================================================\n// Integration Trigger Functions\n// =============================================================================\n\n/**\n * Triggered at session end. Runs a targeted curation cycle\n * focusing on the current session's observations only (faster\n * than a full sweep).\n *\n * Note: Currently runs the full cycle since targeted per-session\n * filtering would require session_id awareness in all curation steps.\n * The full cycle is fast enough for session-end triggers.\n */\nexport async function onSessionEnd(\n db: BetterSqlite3.Database,\n): Promise<CurationReport> {\n return runCuration(db);\n}\n\n/**\n * Triggered when no activity detected for 5+ minutes.\n * Runs the full curation cycle.\n */\nexport async function onQuietPeriod(\n db: BetterSqlite3.Database,\n): Promise<CurationReport> {\n return runCuration(db);\n}\n","/**\n * Combined noise/signal and observation classification agent.\n *\n * Uses a single Haiku call to determine:\n * 1. Whether an observation is noise or signal\n * 2. If signal, what kind of observation it is (discovery/problem/solution)\n * 3. Whether the observation contains debug signals (error/resolution detection)\n *\n * Replaces both the regex-based noise-patterns.ts/signal-classifier.ts and the\n * broken MCP sampling observation-classifier.ts with a single focused LLM call.\n */\n\nimport { z } from 'zod';\n\nimport type { ObservationClassification } from '../shared/types.js';\nimport { callHaiku, extractJsonFromResponse } from './haiku-client.js';\n\n// ---------------------------------------------------------------------------\n// Zod validation schema\n// ---------------------------------------------------------------------------\n\nconst DebugSignalSchema = z.object({\n is_error: z.boolean(),\n is_resolution: z.boolean(),\n waypoint_hint: z.enum([\n 'error', 'attempt', 'failure', 'success',\n 'pivot', 'revert', 'discovery', 'resolution',\n ]).nullable(),\n confidence: z.number(),\n}).nullable();\n\nconst ClassificationSchema = z.object({\n signal: z.enum(['noise', 'signal']),\n classification: z.enum(['discovery', 'problem', 'solution']).nullable(),\n reason: z.string(),\n debug_signal: DebugSignalSchema.default(null),\n});\n\n// ---------------------------------------------------------------------------\n// Exported types\n// ---------------------------------------------------------------------------\n\nexport type DebugSignal = {\n is_error: boolean;\n is_resolution: boolean;\n waypoint_hint: 'error' | 'attempt' | 'failure' | 'success' | 'pivot' | 'revert' | 'discovery' | 'resolution' | null;\n confidence: number;\n};\n\nexport type ClassificationResult = {\n signal: 'noise' | 'signal';\n classification: ObservationClassification | null;\n reason: string;\n debug_signal: DebugSignal | null;\n};\n\n// ---------------------------------------------------------------------------\n// System prompt\n// ---------------------------------------------------------------------------\n\nconst SYSTEM_PROMPT = `You classify developer observations for a knowledge management system.\n\nFor each observation, determine:\n1. signal: Is this noise or signal?\n - \"noise\": build output, linter spam, package install logs, empty/trivial content, routine navigation, repeated boilerplate, test runner output with no failures\n - \"signal\": meaningful findings, decisions, problems, solutions, reference material, architectural insights\n\n2. classification (only if signal): What kind of observation is this?\n - \"discovery\": new understanding, finding, insight, or reference material\n - \"problem\": error, bug, failure, or obstacle encountered\n - \"solution\": fix, resolution, workaround, or decision that resolved something\n\n3. debug_signal (always, even for noise): Is this related to ACTIVE debugging (the developer hit an actual error)?\n - is_error: Did an actual error/failure OCCUR in this observation? An error message, stack trace, test failure, or build failure that happened RIGHT NOW. NOT research about errors — searching for \"reconnection problems\" or reading docs about error handling is NOT is_error. The tool itself must have failed or produced an error.\n - is_resolution: Does this indicate a successful fix, passing test, or resolved error?\n - waypoint_hint: If debug-related, what type? \"error\" (an actual error occurred), \"attempt\" (trying a fix), \"failure\" (fix didn't work), \"success\" (something passed), \"pivot\" (changing approach), \"revert\" (undoing a change), \"discovery\" (learned something new), \"resolution\" (final fix). null if not debug-related. WebSearch/WebFetch/AskUserQuestion are typically \"discovery\" or null, NOT \"error\".\n - confidence: 0.0-1.0 how confident this is debug activity\n\nReturn JSON: {\"signal\": \"noise\"|\"signal\", \"classification\": \"discovery\"|\"problem\"|\"solution\"|null, \"reason\": \"brief\", \"debug_signal\": {\"is_error\": bool, \"is_resolution\": bool, \"waypoint_hint\": \"type\"|null, \"confidence\": 0.0-1.0}|null}\nIf noise, classification must be null. debug_signal can be non-null even for noise (e.g., build failure output is noise but debug-relevant).\nNo markdown, no explanation, ONLY the JSON object.`;\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Classifies an observation as noise/signal and determines its kind using Haiku.\n *\n * @param text - The observation content to classify\n * @param source - Optional source context (e.g., \"PostToolUse:Read\", \"UserMessage\")\n * @returns Classification result with signal/noise determination and observation kind\n */\nexport async function classifyWithHaiku(\n text: string,\n source?: string,\n): Promise<ClassificationResult> {\n let userContent = text;\n if (source) {\n userContent = `Source: ${source}\\n\\nObservation:\\n${text}`;\n }\n\n const response = await callHaiku(SYSTEM_PROMPT, userContent, 512);\n const parsed = extractJsonFromResponse(response);\n return ClassificationSchema.parse(parsed) as ClassificationResult;\n}\n","/**\n * Entity extraction agent.\n *\n * Uses Haiku to extract typed entities from observation text.\n * Replaces the regex-based extraction-rules.ts with LLM-powered analysis.\n * Returns entities validated against the fixed 6-type taxonomy from graph/types.ts.\n */\n\nimport { z } from 'zod';\n\nimport { ENTITY_TYPES, type EntityType } from '../graph/types.js';\nimport { callHaiku, extractJsonFromResponse } from './haiku-client.js';\n\n// ---------------------------------------------------------------------------\n// Zod validation schemas\n// ---------------------------------------------------------------------------\n\nconst EntitySchema = z.object({\n name: z.string().min(1),\n type: z.enum(ENTITY_TYPES),\n confidence: z.number().min(0).max(1),\n});\n\nconst EntityArraySchema = z.array(EntitySchema);\n\n// ---------------------------------------------------------------------------\n// System prompt\n// ---------------------------------------------------------------------------\n\nconst SYSTEM_PROMPT = `You extract structured entities from developer observations.\n\nEntity types (use ONLY these exact strings):\n- File: file paths (src/foo/bar.ts, package.json, ./config.yml)\n- Project: repository names (org/repo), npm packages (@scope/pkg)\n- Reference: URLs (https://...)\n- Decision: explicit choices made (\"decided to use X\", \"chose Y over Z\")\n- Problem: bugs, errors, failures, obstacles encountered\n- Solution: fixes, resolutions, workarounds applied\n\nRules:\n- Extract ALL entities present in the text\n- For Decision/Problem/Solution, extract the descriptive phrase (not just the keyword)\n- Confidence: 0.9+ for unambiguous (file paths, URLs), 0.7-0.8 for clear context, 0.5-0.6 for inferred\n- Return a JSON array: [{\"name\": \"...\", \"type\": \"...\", \"confidence\": 0.0-1.0}]\n- Return [] if no entities found\n- No markdown, no explanation, ONLY the JSON array`;\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Extracts entities from observation text using Haiku.\n *\n * @param text - The observation content to analyze\n * @returns Validated array of extracted entities with type and confidence\n */\nexport async function extractEntitiesWithHaiku(\n text: string,\n): Promise<Array<{ name: string; type: EntityType; confidence: number }>> {\n const response = await callHaiku(SYSTEM_PROMPT, text, 512);\n const parsed = extractJsonFromResponse(response);\n return EntityArraySchema.parse(parsed);\n}\n","/**\n * Relationship inference agent.\n *\n * Uses Haiku to infer typed relationships between entities extracted from\n * observation text. Replaces the regex-based relationship-detector.ts with\n * LLM-powered contextual inference.\n * Returns relationships validated against the fixed 8-type taxonomy from graph/types.ts.\n */\n\nimport { z } from 'zod';\n\nimport { RELATIONSHIP_TYPES, type RelationshipType, type EntityType } from '../graph/types.js';\nimport { callHaiku, extractJsonFromResponse } from './haiku-client.js';\n\n// ---------------------------------------------------------------------------\n// Zod validation schemas\n// ---------------------------------------------------------------------------\n\nconst RelationshipSchema = z.object({\n source: z.string(),\n target: z.string(),\n type: z.enum(RELATIONSHIP_TYPES),\n confidence: z.number().min(0).max(1),\n});\n\nconst RelationshipArraySchema = z.array(RelationshipSchema);\n\n// ---------------------------------------------------------------------------\n// System prompt\n// ---------------------------------------------------------------------------\n\nconst SYSTEM_PROMPT = `You infer relationships between entities extracted from a developer observation.\n\nGiven observation text and a list of entities, determine which entities are related and how.\n\nRelationship types (use ONLY these exact strings):\n- modifies: entity A changed/edited/created entity B\n- informed_by: entity A was researched/consulted using entity B\n- verified_by: entity A was tested/confirmed by entity B\n- caused_by: entity A was caused by entity B\n- solved_by: entity A was resolved by entity B\n- references: entity A references/links to entity B\n- preceded_by: entity A came after entity B temporally\n- related_to: generic relationship (use sparingly, prefer specific types)\n\nRules:\n- Only infer relationships with clear textual evidence\n- Source and target must both be in the provided entity list\n- Confidence: 0.8+ for explicit language, 0.5-0.7 for implied\n- Return JSON array: [{\"source\": \"entity name\", \"target\": \"entity name\", \"type\": \"...\", \"confidence\": 0.0-1.0}]\n- Return [] if no relationships found\n- No markdown, no explanation, ONLY the JSON array`;\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Infers relationships between entities using Haiku.\n *\n * @param text - The observation content providing context\n * @param entities - Array of entities extracted from the same observation\n * @returns Validated array of inferred relationships with type and confidence\n */\nexport async function inferRelationshipsWithHaiku(\n text: string,\n entities: Array<{ name: string; type: EntityType }>,\n): Promise<Array<{ source: string; target: string; type: RelationshipType; confidence: number }>> {\n const entityList = JSON.stringify(entities.map((e) => ({ name: e.name, type: e.type })));\n const userContent = `Observation:\\n${text}\\n\\nEntities found:\\n${entityList}`;\n\n const response = await callHaiku(SYSTEM_PROMPT, userContent, 512);\n const parsed = extractJsonFromResponse(response);\n return RelationshipArraySchema.parse(parsed);\n}\n","/**\n * Write-quality gate for entity extraction filtering.\n *\n * Filters individual entities before they enter the knowledge graph\n * to prevent low-quality nodes from accumulating. Applies:\n * - Name length bounds (min 3, max 200 chars)\n * - Vague/filler name rejection\n * - Per-type minimum confidence thresholds\n * - File node cap per observation\n * - Context-aware confidence adjustment (File paths from non-change\n * observations get confidence reduced)\n */\n\nimport type { EntityType } from './types.js';\nimport type { GraphExtractionConfig } from '../config/graph-extraction-config.js';\n\n// =============================================================================\n// Types\n// =============================================================================\n\nexport interface QualityGateEntity {\n name: string;\n type: EntityType;\n confidence: number;\n}\n\nexport interface QualityGateResult {\n passed: QualityGateEntity[];\n rejected: Array<{ entity: QualityGateEntity; reason: string }>;\n}\n\n// =============================================================================\n// Constants\n// =============================================================================\n\nconst DEFAULT_MIN_NAME_LENGTH = 3;\nconst DEFAULT_MAX_NAME_LENGTH = 200;\nconst DEFAULT_MAX_FILES_PER_OBSERVATION = 5;\n\n/**\n * Vague name prefixes that indicate low-quality entity names.\n * Case-insensitive match against the start of the entity name.\n */\nconst VAGUE_PREFIXES = [\n 'the ', 'this ', 'that ', 'it ', 'some ', 'a ', 'an ',\n 'here ', 'there ', 'now ', 'just ',\n 'ok ', 'yes ', 'no ', 'maybe ', 'done ', 'tmp ',\n];\n\n/**\n * Per-type minimum confidence thresholds.\n * High-signal types (Decision, Problem, Solution) have lower thresholds\n * to capture more of the valuable knowledge. File has the highest\n * threshold to reduce noise.\n */\nconst DEFAULT_TYPE_CONFIDENCE: Record<EntityType, number> = {\n File: 0.95,\n Project: 0.8,\n Reference: 0.85,\n Decision: 0.65,\n Problem: 0.6,\n Solution: 0.6,\n};\n\n/**\n * Context-aware confidence multiplier for File paths from non-change\n * observations. Reduces 0.95 -> ~0.70, below the 0.95 File threshold.\n */\nconst DEFAULT_FILE_NON_CHANGE_MULTIPLIER = 0.74;\n\n// =============================================================================\n// Core Quality Gate\n// =============================================================================\n\n/**\n * Applies quality gate filters to a list of extracted entities.\n *\n * Steps:\n * 1. Apply context-aware confidence adjustment (File paths in non-change obs)\n * 2. Reject entities with names outside length bounds\n * 3. Reject entities with vague/filler name prefixes\n * 4. Apply per-type confidence thresholds\n * 5. Cap File nodes to max per observation (keep highest confidence)\n *\n * @param entities - Extracted entities to filter\n * @param isChangeObservation - Whether the source observation is a change/write\n * @param config - Optional configuration overrides\n * @returns Entities that passed the gate, plus rejected entities with reasons\n */\nexport function applyQualityGate(\n entities: QualityGateEntity[],\n isChangeObservation: boolean,\n config?: GraphExtractionConfig,\n): QualityGateResult {\n const minNameLen = config?.qualityGate?.minNameLength ?? DEFAULT_MIN_NAME_LENGTH;\n const maxNameLen = config?.qualityGate?.maxNameLength ?? DEFAULT_MAX_NAME_LENGTH;\n const maxFiles = config?.qualityGate?.maxFilesPerObservation ?? DEFAULT_MAX_FILES_PER_OBSERVATION;\n const typeConfidence = config?.qualityGate?.typeConfidenceThresholds ?? DEFAULT_TYPE_CONFIDENCE;\n const fileMultiplier = config?.qualityGate?.fileNonChangeMultiplier ?? DEFAULT_FILE_NON_CHANGE_MULTIPLIER;\n\n const passed: QualityGateEntity[] = [];\n const rejected: Array<{ entity: QualityGateEntity; reason: string }> = [];\n\n for (const entity of entities) {\n // Step 1: Context-aware confidence adjustment\n let adjustedConfidence = entity.confidence;\n if (entity.type === 'File' && !isChangeObservation) {\n adjustedConfidence = entity.confidence * fileMultiplier;\n }\n\n const adjusted = { ...entity, confidence: adjustedConfidence };\n\n // Step 2: Name length bounds\n if (adjusted.name.length < minNameLen) {\n rejected.push({ entity: adjusted, reason: `Name too short (${adjusted.name.length} < ${minNameLen})` });\n continue;\n }\n if (adjusted.name.length > maxNameLen) {\n rejected.push({ entity: adjusted, reason: `Name too long (${adjusted.name.length} > ${maxNameLen})` });\n continue;\n }\n\n // Step 3: Vague name rejection\n const lowerName = adjusted.name.toLowerCase();\n const isVague = VAGUE_PREFIXES.some(prefix => lowerName.startsWith(prefix));\n if (isVague) {\n rejected.push({ entity: adjusted, reason: `Vague name prefix: \"${adjusted.name}\"` });\n continue;\n }\n\n // Step 4: Per-type confidence threshold\n const threshold = typeConfidence[adjusted.type] ?? DEFAULT_TYPE_CONFIDENCE[adjusted.type] ?? 0.5;\n if (adjusted.confidence < threshold) {\n rejected.push({\n entity: adjusted,\n reason: `Below ${adjusted.type} confidence threshold (${adjusted.confidence.toFixed(2)} < ${threshold})`,\n });\n continue;\n }\n\n passed.push(adjusted);\n }\n\n // Step 5: Cap File nodes per observation\n const fileEntities = passed.filter(e => e.type === 'File');\n if (fileEntities.length > maxFiles) {\n // Sort by confidence descending, keep top N\n fileEntities.sort((a, b) => b.confidence - a.confidence);\n const toRemove = new Set(\n fileEntities.slice(maxFiles).map(e => e.name),\n );\n const finalPassed: QualityGateEntity[] = [];\n for (const e of passed) {\n if (e.type === 'File' && toRemove.has(e.name)) {\n rejected.push({ entity: e, reason: `File cap exceeded (max ${maxFiles} per observation)` });\n } else {\n finalPassed.push(e);\n }\n }\n return { passed: finalPassed, rejected };\n }\n\n return { passed, rejected };\n}\n","/**\n * Server-Sent Events endpoint for live updates.\n *\n * Maintains a set of connected SSE clients and provides a broadcast\n * function for pushing real-time events to all connected browsers.\n * Includes a ring buffer for event replay on reconnection via\n * Last-Event-ID header support.\n *\n * Supported event types:\n * - connected: initial handshake\n * - heartbeat: keepalive ping (every 30s)\n * - new_observation: new observation stored\n * - topic_shift: topic shift detected\n * - entity_updated: graph entity created/modified\n * - session_start: new session started\n * - session_end: session ended\n *\n * @module web/routes/sse\n */\n\nimport { Hono } from 'hono';\nimport type { Context } from 'hono';\nimport { debug } from '../../shared/debug.js';\n\n// ---------------------------------------------------------------------------\n// Client management\n// ---------------------------------------------------------------------------\n\ninterface SSEClient {\n id: string;\n controller: ReadableStreamDefaultController;\n heartbeatTimer: ReturnType<typeof setInterval>;\n}\n\nconst clients = new Set<SSEClient>();\n\nlet clientIdCounter = 0;\n\n// ---------------------------------------------------------------------------\n// Event ID counter and ring buffer for replay\n// ---------------------------------------------------------------------------\n\nlet lastEventId = 0;\n\ninterface BufferedEvent {\n id: number;\n event: string;\n data: string;\n}\n\nconst RING_BUFFER_SIZE = 100;\nconst eventRingBuffer: BufferedEvent[] = [];\n\n/**\n * Adds an event to the ring buffer, evicting the oldest if full.\n */\nfunction pushToRingBuffer(entry: BufferedEvent): void {\n if (eventRingBuffer.length >= RING_BUFFER_SIZE) {\n eventRingBuffer.shift();\n }\n eventRingBuffer.push(entry);\n}\n\n/**\n * Returns all events with id > sinceId from the ring buffer.\n */\nfunction getEventsSince(sinceId: number): BufferedEvent[] {\n return eventRingBuffer.filter((e) => e.id > sinceId);\n}\n\n// ---------------------------------------------------------------------------\n// SSE formatting helpers\n// ---------------------------------------------------------------------------\n\nfunction formatSSE(event: string, data: string, id?: number): string {\n let msg = '';\n if (id !== undefined) {\n msg += `id: ${id}\\n`;\n }\n msg += `event: ${event}\\ndata: ${data}\\n\\n`;\n return msg;\n}\n\nfunction sendToClient(client: SSEClient, event: string, data: string, id?: number): boolean {\n try {\n const message = formatSSE(event, data, id);\n client.controller.enqueue(new TextEncoder().encode(message));\n return true;\n } catch {\n // Client disconnected or stream errored\n return false;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Route group\n// ---------------------------------------------------------------------------\n\nexport const sseRoutes = new Hono();\n\n/**\n * GET /api/sse\n *\n * Server-Sent Events endpoint. Keeps the connection alive with heartbeats\n * and receives broadcast events for live UI updates.\n *\n * Supports Last-Event-ID header for replay of missed events on reconnection.\n */\nsseRoutes.get('/sse', (c: Context) => {\n const clientId = String(++clientIdCounter);\n const lastEventIdHeader = c.req.header('Last-Event-ID');\n const replayFromId = lastEventIdHeader ? parseInt(lastEventIdHeader, 10) : 0;\n\n let client: SSEClient;\n\n const stream = new ReadableStream({\n start(controller) {\n // Create client with heartbeat\n const heartbeatTimer = setInterval(() => {\n const ok = sendToClient(client, 'heartbeat', JSON.stringify({ timestamp: Date.now() }));\n if (!ok) {\n clearInterval(heartbeatTimer);\n clients.delete(client);\n debug('db', 'SSE client heartbeat failed, removed', { clientId });\n }\n }, 30_000);\n\n client = { id: clientId, controller, heartbeatTimer };\n clients.add(client);\n\n debug('db', 'SSE client connected', { clientId, total: clients.size });\n\n // Send initial connected event\n sendToClient(client, 'connected', JSON.stringify({\n timestamp: Date.now(),\n clientId,\n }));\n\n // Replay missed events if client is reconnecting with Last-Event-ID\n if (replayFromId > 0) {\n const missed = getEventsSince(replayFromId);\n for (const entry of missed) {\n sendToClient(client, entry.event, entry.data, entry.id);\n }\n if (missed.length > 0) {\n debug('db', 'SSE replayed missed events', { clientId, count: missed.length, sinceId: replayFromId });\n }\n }\n },\n cancel() {\n // Client disconnected\n if (client) {\n clearInterval(client.heartbeatTimer);\n clients.delete(client);\n debug('db', 'SSE client disconnected', { clientId, total: clients.size });\n }\n },\n });\n\n return new Response(stream, {\n headers: {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n 'Connection': 'keep-alive',\n 'X-Accel-Buffering': 'no', // Disable nginx buffering if proxied\n },\n });\n});\n\n// ---------------------------------------------------------------------------\n// Broadcast\n// ---------------------------------------------------------------------------\n\n/**\n * Broadcasts an event to all connected SSE clients.\n *\n * Each broadcast increments a monotonic event ID that is included in the\n * SSE `id:` field. Events are stored in an in-memory ring buffer (last 100)\n * so reconnecting clients can replay missed events via Last-Event-ID.\n *\n * Automatically removes disconnected clients that fail to receive\n * the message.\n *\n * @param event - Event name (e.g., 'new_observation', 'topic_shift')\n * @param data - Data object to serialize as JSON\n */\nexport function broadcast(event: string, data: object): void {\n const eventId = ++lastEventId;\n const json = JSON.stringify(data);\n\n // Store in ring buffer for replay\n pushToRingBuffer({ id: eventId, event, data: json });\n\n if (clients.size === 0) return;\n\n const dead: SSEClient[] = [];\n\n for (const client of clients) {\n const ok = sendToClient(client, event, json, eventId);\n if (!ok) {\n dead.push(client);\n }\n }\n\n // Clean up dead clients\n for (const client of dead) {\n clearInterval(client.heartbeatTimer);\n clients.delete(client);\n }\n\n if (dead.length > 0) {\n debug('db', 'SSE broadcast cleaned dead clients', { dead: dead.length, remaining: clients.size });\n }\n}\n\n/**\n * Returns the current number of connected SSE clients.\n * Useful for health/stats endpoints.\n */\nexport function getClientCount(): number {\n return clients.size;\n}\n","/**\n * Background Haiku processing orchestrator.\n *\n * Runs on a timer, picks up unclassified observations, and processes them\n * through the Haiku agent pipeline:\n * 1. Classify (noise/signal + discovery/problem/solution)\n * 2. Extract entities via Haiku\n * 3. Infer relationships via Haiku\n *\n * Noise observations are soft-deleted after classification (store-then-soft-delete).\n * Replaces the broken MCP sampling ObservationClassifier and the regex-based\n * entity extraction / relationship detection in the embedding loop.\n */\n\nimport type BetterSqlite3 from 'better-sqlite3';\n\nimport { classifyWithHaiku } from './haiku-classifier-agent.js';\nimport { extractEntitiesWithHaiku } from './haiku-entity-agent.js';\nimport { inferRelationshipsWithHaiku } from './haiku-relationship-agent.js';\nimport { isHaikuEnabled } from './haiku-client.js';\nimport { ObservationRepository } from '../storage/observations.js';\nimport { upsertNode, getNodeByNameAndType, insertEdge } from '../graph/schema.js';\nimport { applyQualityGate } from '../graph/write-quality-gate.js';\nimport { enforceMaxDegree } from '../graph/constraints.js';\nimport { broadcast } from '../web/routes/sse.js';\nimport { debug } from '../shared/debug.js';\nimport type { EntityType } from '../graph/types.js';\nimport type { PathTracker } from '../paths/path-tracker.js';\nimport type { BranchTracker } from '../branches/branch-tracker.js';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\ninterface HaikuProcessorOptions {\n intervalMs?: number;\n batchSize?: number;\n concurrency?: number;\n pathTracker?: PathTracker;\n branchTracker?: BranchTracker;\n}\n\n// ---------------------------------------------------------------------------\n// HaikuProcessor\n// ---------------------------------------------------------------------------\n\nexport class HaikuProcessor {\n private readonly db: BetterSqlite3.Database;\n private readonly projectHash: string;\n private readonly intervalMs: number;\n private readonly batchSize: number;\n private readonly concurrency: number;\n private readonly pathTracker: PathTracker | null;\n private readonly branchTracker: BranchTracker | null;\n private timer: ReturnType<typeof setInterval> | null = null;\n\n constructor(\n db: BetterSqlite3.Database,\n projectHash: string,\n opts?: HaikuProcessorOptions,\n ) {\n this.db = db;\n this.projectHash = projectHash;\n this.intervalMs = opts?.intervalMs ?? 30_000;\n this.batchSize = opts?.batchSize ?? 10;\n this.concurrency = opts?.concurrency ?? 3;\n this.pathTracker = opts?.pathTracker ?? null;\n this.branchTracker = opts?.branchTracker ?? null;\n }\n\n start(): void {\n if (this.timer) return;\n debug('haiku', 'HaikuProcessor started', {\n intervalMs: this.intervalMs,\n batchSize: this.batchSize,\n concurrency: this.concurrency,\n });\n this.timer = setInterval(() => {\n this.processOnce().catch((err) => {\n const msg = err instanceof Error ? err.message : String(err);\n debug('haiku', 'HaikuProcessor cycle error', { error: msg });\n });\n }, this.intervalMs);\n }\n\n stop(): void {\n if (this.timer) {\n clearInterval(this.timer);\n this.timer = null;\n debug('haiku', 'HaikuProcessor stopped');\n }\n }\n\n async processOnce(): Promise<void> {\n if (!isHaikuEnabled()) return;\n\n // Query unclassified observations across ALL projects to avoid missing\n // observations when the MCP server's project hash doesn't match the\n // actual project (e.g., server started from plugin install directory).\n const unclassified = ObservationRepository.listAllUnclassified(this.db, this.batchSize);\n\n if (unclassified.length === 0) return;\n\n debug('haiku', 'Processing unclassified observations', {\n count: unclassified.length,\n });\n\n // Group by project hash so each gets the correct ObservationRepository\n const byProject = new Map<string, typeof unclassified>();\n for (const obs of unclassified) {\n const hash = obs.projectHash;\n if (!byProject.has(hash)) byProject.set(hash, []);\n byProject.get(hash)!.push(obs);\n }\n\n for (const [hash, projectObs] of byProject) {\n const repo = new ObservationRepository(this.db, hash);\n for (let i = 0; i < projectObs.length; i += this.concurrency) {\n const batch = projectObs.slice(i, i + this.concurrency);\n await Promise.all(batch.map((obs) => this.processOne(obs, repo, hash)));\n }\n }\n\n // Step 4: Branch maintenance\n if (this.branchTracker) {\n try {\n await this.branchTracker.runMaintenance();\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n debug('haiku', 'Branch maintenance error (non-fatal)', { error: msg });\n }\n }\n }\n\n private async processOne(\n obs: { id: string; content: string; source: string },\n repo: ObservationRepository,\n obsProjectHash?: string,\n ): Promise<void> {\n const projectHash = obsProjectHash ?? this.projectHash;\n try {\n // Step 1: Classify via Haiku\n let classification: string;\n try {\n const result = await classifyWithHaiku(obs.content, obs.source);\n\n // Step 1.5: Feed debug signal to path tracker (between classify and extract)\n // Runs BEFORE noise early-return — even noise can contain debug-relevant errors\n if (this.pathTracker && result.debug_signal) {\n try {\n this.pathTracker.processSignal(result.debug_signal, obs.id, obs.content);\n } catch (pathErr) {\n const msg = pathErr instanceof Error ? pathErr.message : String(pathErr);\n debug('haiku', 'Path tracking failed (non-fatal)', { id: obs.id, error: msg });\n }\n }\n\n // Step 1.6: Feed to branch tracker\n if (this.branchTracker) {\n try {\n this.branchTracker.processObservation({\n id: obs.id,\n content: obs.content,\n source: obs.source,\n projectHash: obsProjectHash ?? this.projectHash,\n sessionId: undefined,\n classification: result.classification,\n createdAt: new Date().toISOString(),\n });\n } catch (branchErr) {\n const msg = branchErr instanceof Error ? branchErr.message : String(branchErr);\n debug('haiku', 'Branch tracking failed (non-fatal)', { id: obs.id, error: msg });\n }\n }\n\n if (result.signal === 'noise') {\n // Mark as noise and soft-delete\n repo.updateClassification(obs.id, 'noise');\n repo.softDelete(obs.id);\n debug('haiku', 'Observation classified as noise, soft-deleted', { id: obs.id });\n return;\n }\n\n classification = result.classification ?? 'discovery';\n repo.updateClassification(\n obs.id,\n classification as 'discovery' | 'problem' | 'solution',\n );\n debug('haiku', 'Observation classified', {\n id: obs.id,\n classification,\n });\n } catch (classifyErr) {\n const msg = classifyErr instanceof Error ? classifyErr.message : String(classifyErr);\n debug('haiku', 'Classification failed, will retry next cycle', {\n id: obs.id,\n error: msg,\n });\n // Leave unclassified for retry\n return;\n }\n\n // Step 2: Extract entities via Haiku\n let entities: Array<{ name: string; type: EntityType; confidence: number }> = [];\n try {\n entities = await extractEntitiesWithHaiku(obs.content);\n } catch (entityErr) {\n const msg = entityErr instanceof Error ? entityErr.message : String(entityErr);\n debug('haiku', 'Entity extraction failed (non-fatal)', {\n id: obs.id,\n error: msg,\n });\n // Classification succeeded, entity extraction failed -- acceptable\n return;\n }\n\n if (entities.length === 0) return;\n\n // Apply quality gate and persist entities\n const isChange = obs.source === 'hook:Write' || obs.source === 'hook:Edit';\n const gateResult = applyQualityGate(entities, isChange);\n\n const persistedNodes: Array<{ id: string; name: string; type: string }> = [];\n for (const entity of gateResult.passed) {\n try {\n const node = upsertNode(this.db, {\n type: entity.type,\n name: entity.name,\n metadata: { confidence: entity.confidence },\n observation_ids: [String(obs.id)],\n project_hash: projectHash,\n });\n persistedNodes.push(node);\n } catch {\n // Skip individual entity failures\n continue;\n }\n }\n\n if (persistedNodes.length > 0) {\n // Broadcast entity updates to SSE clients\n for (const node of persistedNodes) {\n broadcast('entity_updated', {\n id: node.name,\n label: node.name,\n type: node.type,\n observationCount: 1,\n createdAt: new Date().toISOString(),\n projectHash,\n });\n }\n\n debug('haiku', 'Entities persisted', {\n id: obs.id,\n count: persistedNodes.length,\n });\n }\n\n // Step 3: Infer relationships via Haiku (only if 2+ entities)\n if (persistedNodes.length >= 2) {\n try {\n const entityPairs = persistedNodes.map((n) => ({\n name: n.name,\n type: n.type as EntityType,\n }));\n const relationships = await inferRelationshipsWithHaiku(\n obs.content,\n entityPairs,\n );\n\n const affectedNodeIds = new Set<string>();\n for (const rel of relationships) {\n const sourceNode = getNodeByNameAndType(this.db, rel.source, entityPairs.find((e) => e.name === rel.source)?.type ?? 'File');\n const targetNode = getNodeByNameAndType(this.db, rel.target, entityPairs.find((e) => e.name === rel.target)?.type ?? 'File');\n\n if (!sourceNode || !targetNode) continue;\n\n try {\n insertEdge(this.db, {\n source_id: sourceNode.id,\n target_id: targetNode.id,\n type: rel.type,\n weight: rel.confidence,\n metadata: { source: 'haiku' },\n project_hash: projectHash,\n });\n affectedNodeIds.add(sourceNode.id);\n affectedNodeIds.add(targetNode.id);\n } catch {\n // Edge may already exist, skip\n }\n }\n\n // Enforce max degree on affected nodes\n for (const nodeId of affectedNodeIds) {\n enforceMaxDegree(this.db, nodeId);\n }\n\n debug('haiku', 'Relationships persisted', {\n id: obs.id,\n count: relationships.length,\n });\n } catch (relErr) {\n const msg = relErr instanceof Error ? relErr.message : String(relErr);\n debug('haiku', 'Relationship inference failed (non-fatal)', {\n id: obs.id,\n error: msg,\n });\n }\n }\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n debug('haiku', 'processOne failed (non-fatal)', {\n id: obs.id,\n error: msg,\n });\n }\n }\n}\n","/**\n * KISS summary agent — generates actionable \"next time, just do X\" summaries.\n *\n * When a debug path resolves, this agent analyzes the waypoints (errors,\n * attempts, failures, resolution) and produces a multi-layer summary:\n * - kiss_summary: The one-liner takeaway\n * - root_cause: What actually caused the issue\n * - what_fixed_it: The specific fix that resolved it\n * - dimensions: logical, programmatic, development perspectives\n *\n * Uses the shared Haiku client (callHaiku + extractJsonFromResponse) following\n * the same pattern as haiku-classifier-agent.ts.\n */\n\nimport { z } from 'zod';\n\nimport { callHaiku, extractJsonFromResponse } from '../intelligence/haiku-client.js';\nimport type { PathWaypoint } from './types.js';\n\n// ---------------------------------------------------------------------------\n// Zod validation schema\n// ---------------------------------------------------------------------------\n\nconst KissSummarySchema = z.object({\n kiss_summary: z.string(),\n root_cause: z.string(),\n what_fixed_it: z.string(),\n dimensions: z.object({\n logical: z.string(),\n programmatic: z.string(),\n development: z.string(),\n }),\n});\n\n// ---------------------------------------------------------------------------\n// Exported types\n// ---------------------------------------------------------------------------\n\nexport type KissSummary = z.infer<typeof KissSummarySchema>;\n\n// ---------------------------------------------------------------------------\n// System prompt\n// ---------------------------------------------------------------------------\n\nconst SYSTEM_PROMPT = `You analyze completed debug resolution paths and produce actionable summaries.\n\nGiven a debug path with its trigger, waypoints (errors, attempts, failures, resolution), and resolution summary, generate:\n\n1. kiss_summary: A \"Next time, just do X\" one-liner. This is the actionable takeaway a developer should remember.\n2. root_cause: What actually caused the issue (1-2 sentences max).\n3. what_fixed_it: The specific fix or change that resolved it (1-2 sentences max).\n4. dimensions:\n - logical: What mental model was wrong? What assumption led the developer astray? (1-2 sentences)\n - programmatic: What code-level change fixed it? Be specific about files, functions, or patterns. (1-2 sentences)\n - development: What workflow improvement would catch this faster next time? (1-2 sentences)\n\nKeep every field concise. Developers will scan these quickly.\nReturn ONLY JSON, no markdown, no explanation.`;\n\n// ---------------------------------------------------------------------------\n// Key waypoint types worth including in the summary prompt\n// ---------------------------------------------------------------------------\n\nconst KEY_WAYPOINT_TYPES = new Set([\n 'error',\n 'failure',\n 'success',\n 'resolution',\n 'discovery',\n]);\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Generates a KISS summary for a resolved debug path.\n *\n * Pre-filters waypoints to key types (error, failure, success, resolution,\n * discovery) and caps at 10 to keep the prompt small. Returns a structured\n * KissSummary with multi-layer dimensions.\n *\n * @param triggerSummary - What started the debug path\n * @param waypoints - All waypoints from the path\n * @param resolutionSummary - How the path was resolved\n * @returns Structured KISS summary with dimensions\n */\nexport async function generateKissSummary(\n triggerSummary: string,\n waypoints: PathWaypoint[],\n resolutionSummary: string,\n): Promise<KissSummary> {\n // Pre-filter to key waypoint types, skip 'attempt' noise\n const filtered = waypoints\n .filter((w) => KEY_WAYPOINT_TYPES.has(w.waypoint_type))\n .slice(0, 10);\n\n // Format waypoints for the prompt\n const waypointLines = filtered\n .map((w) => `- [${w.waypoint_type}] ${w.summary}`)\n .join('\\n');\n\n const userContent = `Trigger: ${triggerSummary}\n\nWaypoints:\n${waypointLines}\n\nResolution: ${resolutionSummary}`;\n\n const response = await callHaiku(SYSTEM_PROMPT, userContent);\n const parsed = extractJsonFromResponse(response);\n return KissSummarySchema.parse(parsed);\n}\n","/**\n * PathTracker — state machine for automatic debug path detection.\n *\n * Consumes DebugSignal from the Haiku classifier and manages the lifecycle\n * of debug paths: idle -> potential_debug -> active_debug -> resolved.\n *\n * Lives in the MCP server process (not the ephemeral hook handler) so it\n * can maintain in-memory state across observations. Persists paths and\n * waypoints via PathRepository for restart recovery.\n *\n * Implements:\n * PATH-01: Auto-detect debug sessions from error patterns\n * PATH-02: Capture waypoints during active debug paths\n * PATH-03: Detect resolution via consecutive success signals\n * PATH-04: Persistence across restarts (via SQLite recovery)\n * PATH-05: Dead end tracking via failure waypoint type\n */\n\nimport type { DebugSignal } from '../intelligence/haiku-classifier-agent.js';\nimport type { PathRepository } from './path-repository.js';\nimport type { WaypointType } from './types.js';\nimport { debug } from '../shared/debug.js';\nimport { generateKissSummary } from './kiss-summary-agent.js';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\ntype TrackerState = 'idle' | 'potential_debug' | 'active_debug' | 'resolved';\n\ninterface ErrorBufferEntry {\n timestamp: number;\n summary: string;\n}\n\ninterface PathTrackerOptions {\n /** Number of errors needed to confirm debug session (default: 3) */\n errorThreshold?: number;\n /** Time window for error threshold in ms (default: 5 minutes) */\n windowMs?: number;\n /** Consecutive successes needed to auto-resolve (default: 3) */\n resolutionThreshold?: number;\n /** Maximum waypoints per path (default: 30) */\n maxWaypoints?: number;\n}\n\n// ---------------------------------------------------------------------------\n// PathTracker\n// ---------------------------------------------------------------------------\n\nexport class PathTracker {\n private state: TrackerState = 'idle';\n private errorBuffer: ErrorBufferEntry[] = [];\n private consecutiveSuccesses: number = 0;\n private currentPathId: string | null = null;\n\n private readonly errorThreshold: number;\n private readonly windowMs: number;\n private readonly resolutionThreshold: number;\n private readonly maxWaypoints: number;\n\n constructor(\n private readonly repo: PathRepository,\n opts?: PathTrackerOptions,\n ) {\n this.errorThreshold = opts?.errorThreshold ?? 3;\n this.windowMs = opts?.windowMs ?? 5 * 60 * 1000;\n this.resolutionThreshold = opts?.resolutionThreshold ?? 3;\n this.maxWaypoints = opts?.maxWaypoints ?? 30;\n\n // PATH-04: Recover active path from SQLite on server restart\n const activePath = this.repo.getActivePath();\n if (activePath) {\n this.state = 'active_debug';\n this.currentPathId = activePath.id;\n debug('paths', 'Recovered active path from SQLite', { pathId: activePath.id });\n }\n }\n\n /**\n * Process a debug signal from the Haiku classifier.\n *\n * Called for every classified observation (both noise and signal).\n * Drives state transitions and persists waypoints when in active_debug.\n */\n processSignal(\n signal: DebugSignal,\n observationId: string,\n observationContent: string,\n ): void {\n // Filter: skip low-confidence signals entirely\n if (signal.confidence < 0.3) {\n return;\n }\n\n const summary = observationContent.substring(0, 200).trim();\n\n switch (this.state) {\n case 'idle':\n this.handleIdle(signal, summary);\n break;\n\n case 'potential_debug':\n this.handlePotentialDebug(signal, summary, observationId);\n break;\n\n case 'active_debug':\n this.handleActiveDebug(signal, summary, observationId);\n break;\n\n case 'resolved':\n // Resolved is transient — immediately return to idle\n this.state = 'idle';\n debug('paths', 'Transitioned resolved -> idle');\n this.handleIdle(signal, summary);\n break;\n }\n }\n\n // -------------------------------------------------------------------------\n // State handlers\n // -------------------------------------------------------------------------\n\n private handleIdle(signal: DebugSignal, summary: string): void {\n if (signal.is_error && signal.confidence >= 0.5) {\n this.errorBuffer.push({ timestamp: Date.now(), summary });\n this.state = 'potential_debug';\n debug('paths', 'Transitioned idle -> potential_debug', {\n bufferSize: this.errorBuffer.length,\n });\n }\n }\n\n private handlePotentialDebug(\n signal: DebugSignal,\n summary: string,\n observationId: string,\n ): void {\n if (signal.is_error && signal.confidence >= 0.5) {\n this.errorBuffer.push({ timestamp: Date.now(), summary });\n }\n\n // Prune entries older than windowMs\n const cutoff = Date.now() - this.windowMs;\n this.errorBuffer = this.errorBuffer.filter((e) => e.timestamp >= cutoff);\n\n // All expired — back to idle\n if (this.errorBuffer.length === 0) {\n this.state = 'idle';\n debug('paths', 'Error buffer expired, potential_debug -> idle');\n return;\n }\n\n // Threshold met — transition to active_debug\n if (this.errorBuffer.length >= this.errorThreshold) {\n const triggerSummary = this.errorBuffer[0].summary;\n const path = this.repo.createPath(triggerSummary);\n this.currentPathId = path.id;\n this.state = 'active_debug';\n this.consecutiveSuccesses = 0;\n\n debug('paths', 'Debug path confirmed, potential_debug -> active_debug', {\n pathId: path.id,\n errorCount: this.errorBuffer.length,\n });\n\n // Add waypoints for all buffered errors\n for (const entry of this.errorBuffer) {\n this.repo.addWaypoint(path.id, 'error', entry.summary, observationId);\n }\n\n // Clear buffer — errors are now waypoints\n this.errorBuffer = [];\n }\n }\n\n private handleActiveDebug(\n signal: DebugSignal,\n summary: string,\n observationId: string,\n ): void {\n if (!this.currentPathId) return;\n\n // Cap enforcement\n if (this.repo.countWaypoints(this.currentPathId) >= this.maxWaypoints) {\n debug('paths', 'Waypoint cap reached, skipping', {\n pathId: this.currentPathId,\n cap: this.maxWaypoints,\n });\n // Still process resolution detection even if we can't add waypoints\n this.updateResolutionCounter(signal, summary, observationId);\n return;\n }\n\n // Determine waypoint type\n let waypointType: WaypointType;\n if (signal.waypoint_hint) {\n waypointType = signal.waypoint_hint;\n } else if (signal.is_error) {\n waypointType = 'error';\n } else if (signal.is_resolution) {\n waypointType = 'success';\n } else {\n waypointType = 'attempt';\n }\n\n // Add waypoint\n this.repo.addWaypoint(this.currentPathId, waypointType, summary, observationId);\n\n debug('paths', 'Waypoint added', {\n pathId: this.currentPathId,\n type: waypointType,\n observationId,\n });\n\n // Resolution detection\n this.updateResolutionCounter(signal, summary, observationId);\n }\n\n private updateResolutionCounter(\n signal: DebugSignal,\n summary: string,\n observationId: string,\n ): void {\n if (!this.currentPathId) return;\n\n if (signal.is_resolution) {\n this.consecutiveSuccesses++;\n\n if (this.consecutiveSuccesses >= this.resolutionThreshold) {\n // Add final resolution waypoint (if under cap)\n if (this.repo.countWaypoints(this.currentPathId) < this.maxWaypoints) {\n this.repo.addWaypoint(this.currentPathId, 'resolution', summary, observationId);\n }\n\n // Resolve the path\n this.repo.resolvePath(this.currentPathId, summary);\n\n debug('paths', 'Path auto-resolved', {\n pathId: this.currentPathId,\n consecutiveSuccesses: this.consecutiveSuccesses,\n });\n\n // Fire-and-forget KISS generation (non-blocking)\n const savedPathId = this.currentPathId;\n const savedResolutionSummary = summary;\n this.generateAndStoreKiss(savedPathId, savedResolutionSummary).catch((err) => {\n debug('paths', 'KISS generation failed (fire-and-forget)', { error: String(err) });\n });\n\n // Reset state\n this.state = 'idle';\n this.currentPathId = null;\n this.consecutiveSuccesses = 0;\n this.errorBuffer = [];\n }\n } else if (signal.is_error) {\n // Error resets the consecutive success counter\n this.consecutiveSuccesses = 0;\n }\n }\n\n // -------------------------------------------------------------------------\n // KISS summary generation\n // -------------------------------------------------------------------------\n\n /**\n * Generates and stores a KISS summary for a resolved path.\n * Non-fatal — failures are logged but do not affect path resolution.\n */\n private async generateAndStoreKiss(\n pathId: string,\n resolutionSummary: string,\n ): Promise<void> {\n try {\n const path = this.repo.getPath(pathId);\n if (!path) return;\n\n const waypoints = this.repo.getWaypoints(pathId);\n const kiss = await generateKissSummary(\n path.trigger_summary,\n waypoints,\n resolutionSummary,\n );\n\n this.repo.updateKissSummary(pathId, JSON.stringify(kiss));\n debug('paths', 'KISS summary stored', { pathId });\n } catch (err) {\n debug('paths', 'KISS generation failed', {\n pathId,\n error: String(err),\n });\n }\n }\n\n // -------------------------------------------------------------------------\n // Manual control (for MCP tools — Plan 02)\n // -------------------------------------------------------------------------\n\n /**\n * Manually starts a debug path. Used by MCP tools.\n * If already tracking, returns the existing path ID.\n */\n startManually(triggerSummary: string): string | null {\n if (this.state === 'active_debug' && this.currentPathId) {\n return this.currentPathId;\n }\n\n const path = this.repo.createPath(triggerSummary);\n this.state = 'active_debug';\n this.currentPathId = path.id;\n this.consecutiveSuccesses = 0;\n this.errorBuffer = [];\n\n debug('paths', 'Path started manually', { pathId: path.id });\n return path.id;\n }\n\n /**\n * Manually resolves the active debug path. Used by MCP tools.\n * Adds a resolution waypoint, resolves the path, and fires KISS generation.\n */\n resolveManually(resolutionSummary: string): void {\n if (!this.currentPathId || this.state !== 'active_debug') return;\n\n // Add resolution waypoint\n this.repo.addWaypoint(this.currentPathId, 'resolution', resolutionSummary);\n\n // Resolve the path\n this.repo.resolvePath(this.currentPathId, resolutionSummary);\n\n // Fire-and-forget KISS generation\n const savedPathId = this.currentPathId;\n this.generateAndStoreKiss(savedPathId, resolutionSummary).catch((err) => {\n debug('paths', 'KISS generation failed (fire-and-forget)', { error: String(err) });\n });\n\n // Reset state\n this.state = 'idle';\n this.currentPathId = null;\n this.consecutiveSuccesses = 0;\n this.errorBuffer = [];\n\n debug('paths', 'Path resolved manually', { pathId: savedPathId });\n }\n\n /**\n * Returns the active path ID, or null if no path is being tracked.\n */\n getActivePathId(): string | null {\n return this.currentPathId;\n }\n}\n","/**\n * REST API routes for the Laminark visualization.\n *\n * Provides endpoints for graph data, timeline data, and individual node\n * details. All endpoints read from the better-sqlite3 database instance\n * set on the Hono context by the server middleware.\n *\n * @module web/routes/api\n */\n\nimport { Hono } from 'hono';\nimport type BetterSqlite3 from 'better-sqlite3';\nimport { PathRepository } from '../../paths/path-repository.js';\nimport type { PathWaypoint } from '../../paths/types.js';\n\ntype AppEnv = {\n Variables: {\n db: BetterSqlite3.Database;\n defaultProject: string;\n };\n};\n\n// ---------------------------------------------------------------------------\n// Raw row interfaces for SQL results\n// ---------------------------------------------------------------------------\n\ninterface GraphNodeRow {\n id: string;\n name: string;\n type: string;\n observation_ids: string; // JSON array\n created_at: string;\n}\n\ninterface GraphEdgeRow {\n id: string;\n source_id: string;\n target_id: string;\n type: string;\n name?: string; // joined from graph_nodes for label\n weight: number;\n}\n\ninterface SessionRow {\n id: string;\n started_at: string;\n ended_at: string | null;\n summary: string | null;\n}\n\ninterface ObservationRow {\n id: string;\n content: string;\n title: string | null;\n source: string;\n created_at: string;\n session_id: string | null;\n}\n\ninterface ShiftDecisionRow {\n id: string;\n session_id: string;\n distance: number;\n threshold: number;\n confidence: number | null;\n created_at: string;\n}\n\n// ---------------------------------------------------------------------------\n// Helper: get db from Hono context\n// ---------------------------------------------------------------------------\n\nfunction getDb(c: { get: (key: 'db') => BetterSqlite3.Database }): BetterSqlite3.Database {\n return c.get('db');\n}\n\nfunction getProjectHash(c: { get: (key: 'defaultProject') => string; req: { query: (key: string) => string | undefined } }): string | null {\n return c.req.query('project') || c.get('defaultProject') || null;\n}\n\n// ---------------------------------------------------------------------------\n// Route group\n// ---------------------------------------------------------------------------\n\nexport const apiRoutes = new Hono<AppEnv>();\n\n/**\n * GET /api/projects\n *\n * Returns list of known projects from project_metadata table.\n */\napiRoutes.get('/projects', (c) => {\n const db = getDb(c);\n const defaultProject = c.get('defaultProject') || null;\n\n interface ProjectRow {\n project_hash: string;\n project_path: string;\n display_name: string | null;\n last_seen_at: string;\n }\n\n let projects: ProjectRow[] = [];\n try {\n projects = db.prepare(\n 'SELECT project_hash, project_path, display_name, last_seen_at FROM project_metadata ORDER BY last_seen_at DESC'\n ).all() as ProjectRow[];\n } catch { /* table may not exist yet */ }\n\n // Prefer the most recently active project as default (first in list, sorted by last_seen_at DESC)\n const resolvedDefault = (projects.length > 0 ? projects[0].project_hash : null) || defaultProject;\n\n return c.json({\n projects: projects.map(p => ({\n hash: p.project_hash,\n path: p.project_path,\n displayName: p.display_name || p.project_path.split('/').pop() || p.project_hash.substring(0, 8),\n lastSeenAt: p.last_seen_at,\n })),\n defaultProject: resolvedDefault,\n });\n});\n\n/**\n * GET /api/graph\n *\n * Returns the knowledge graph as JSON with nodes and edges arrays.\n * Accepts optional query params:\n * ?type=File,Decision - comma-separated entity types to include\n * ?since=ISO8601 - only entities created after this timestamp\n */\napiRoutes.get('/graph', (c) => {\n const db = getDb(c);\n const typeFilter = c.req.query('type');\n const sinceFilter = c.req.query('since');\n const untilFilter = c.req.query('until');\n const projectFilter = getProjectHash(c);\n\n // Build nodes query\n let nodesSql = 'SELECT id, name, type, observation_ids, created_at FROM graph_nodes';\n const nodeParams: unknown[] = [];\n const nodeConditions: string[] = [];\n\n if (projectFilter) {\n nodeConditions.push('project_hash = ?');\n nodeParams.push(projectFilter);\n }\n\n if (typeFilter) {\n const types = typeFilter.split(',').map(t => t.trim()).filter(Boolean);\n if (types.length > 0) {\n nodeConditions.push(`type IN (${types.map(() => '?').join(', ')})`);\n nodeParams.push(...types);\n }\n }\n\n if (sinceFilter) {\n nodeConditions.push('created_at >= ?');\n nodeParams.push(sinceFilter);\n }\n\n if (untilFilter) {\n nodeConditions.push('created_at <= ?');\n nodeParams.push(untilFilter);\n }\n\n if (nodeConditions.length > 0) {\n nodesSql += ' WHERE ' + nodeConditions.join(' AND ');\n }\n\n nodesSql += ' ORDER BY created_at DESC';\n\n let nodeRows: GraphNodeRow[];\n try {\n nodeRows = db.prepare(nodesSql).all(...nodeParams) as GraphNodeRow[];\n } catch {\n nodeRows = [];\n }\n\n const nodes = nodeRows.map(row => ({\n id: row.id,\n label: row.name,\n type: row.type,\n observationCount: safeParseJsonArray(row.observation_ids).length,\n createdAt: row.created_at,\n }));\n\n // Build edges query -- only include edges where both nodes are in the result set\n let edgeRows: GraphEdgeRow[];\n try {\n let edgesSql = `\n SELECT e.id, e.source_id, e.target_id, e.type, e.weight,\n tn.name AS name\n FROM graph_edges e\n LEFT JOIN graph_nodes tn ON tn.id = e.target_id`;\n const edgeParams: unknown[] = [];\n if (projectFilter) {\n edgesSql += ' WHERE e.project_hash = ?';\n edgeParams.push(projectFilter);\n }\n edgesSql += ' ORDER BY e.created_at DESC';\n edgeRows = db.prepare(edgesSql).all(...edgeParams) as GraphEdgeRow[];\n } catch {\n edgeRows = [];\n }\n\n // Only include edges where both endpoints exist in the node set\n const nodeIdSet = new Set(nodes.map(n => n.id));\n const filteredEdges = edgeRows.filter(e => nodeIdSet.has(e.source_id) && nodeIdSet.has(e.target_id));\n\n const edges = filteredEdges.map(row => ({\n id: row.id,\n source: row.source_id,\n target: row.target_id,\n type: row.type,\n label: row.name ?? row.type,\n }));\n\n return c.json({ nodes, edges });\n});\n\n/**\n * GET /api/timeline\n *\n * Returns timeline data: sessions, observations, and topic shifts.\n * Accepts optional query params:\n * ?from=ISO8601 - start of time range\n * ?to=ISO8601 - end of time range\n * ?limit=N - max observations (default 500)\n */\napiRoutes.get('/timeline', (c) => {\n const db = getDb(c);\n const from = c.req.query('from');\n const to = c.req.query('to');\n const limitStr = c.req.query('limit');\n const limit = limitStr ? Math.min(parseInt(limitStr, 10) || 500, 2000) : 500;\n const offsetStr = c.req.query('offset');\n const offset = offsetStr ? Math.max(parseInt(offsetStr, 10) || 0, 0) : 0;\n const projectFilter = getProjectHash(c);\n\n // Sessions\n let sessions: Array<{ id: string; startedAt: string; endedAt: string | null; observationCount: number; summary: string | null }> = [];\n try {\n let sessionsSql = 'SELECT id, started_at, ended_at, summary FROM sessions';\n const sessionParams: unknown[] = [];\n const sessionConds: string[] = [];\n\n if (projectFilter) {\n sessionConds.push('project_hash = ?');\n sessionParams.push(projectFilter);\n }\n\n if (from) {\n sessionConds.push('started_at >= ?');\n sessionParams.push(from);\n }\n if (to) {\n sessionConds.push('(ended_at IS NULL OR ended_at <= ?)');\n sessionParams.push(to);\n }\n\n if (sessionConds.length > 0) {\n sessionsSql += ' WHERE ' + sessionConds.join(' AND ');\n }\n sessionsSql += ' ORDER BY started_at DESC LIMIT 50 OFFSET ?';\n sessionParams.push(offset);\n\n const sessionRows = db.prepare(sessionsSql).all(...sessionParams) as SessionRow[];\n\n // Count observations per session\n const countStmt = db.prepare(\n 'SELECT COUNT(*) AS cnt FROM observations WHERE session_id = ? AND deleted_at IS NULL'\n );\n\n sessions = sessionRows.map(row => {\n let obsCount = 0;\n try {\n const countRow = countStmt.get(row.id) as { cnt: number } | undefined;\n obsCount = countRow?.cnt ?? 0;\n } catch { /* empty */ }\n\n return {\n id: row.id,\n startedAt: row.started_at,\n endedAt: row.ended_at,\n observationCount: obsCount,\n summary: row.summary,\n };\n });\n } catch { /* tables may not exist yet */ }\n\n // Observations\n let observations: Array<{ id: string; text: string; createdAt: string; sessionId: string | null; type: string }> = [];\n try {\n let obsSql = 'SELECT id, content, title, source, created_at, session_id FROM observations WHERE deleted_at IS NULL';\n const obsParams: unknown[] = [];\n\n if (projectFilter) {\n obsSql += ' AND project_hash = ?';\n obsParams.push(projectFilter);\n }\n\n if (from) {\n obsSql += ' AND created_at >= ?';\n obsParams.push(from);\n }\n if (to) {\n obsSql += ' AND created_at <= ?';\n obsParams.push(to);\n }\n\n obsSql += ' ORDER BY created_at DESC LIMIT ? OFFSET ?';\n obsParams.push(limit);\n obsParams.push(offset);\n\n const obsRows = db.prepare(obsSql).all(...obsParams) as ObservationRow[];\n\n observations = obsRows.map(row => ({\n id: row.id,\n text: row.title ? `${row.title}: ${row.content}` : row.content,\n createdAt: row.created_at,\n sessionId: row.session_id,\n type: row.source,\n }));\n } catch { /* table may not exist yet */ }\n\n // Topic shifts\n let topicShifts: Array<{ id: string; fromTopic: string | null; toTopic: string | null; timestamp: string; confidence: number | null }> = [];\n try {\n let shiftSql = 'SELECT id, session_id, distance, threshold, confidence, created_at FROM shift_decisions WHERE shifted = 1';\n const shiftParams: unknown[] = [];\n\n if (projectFilter) {\n shiftSql += ' AND project_id = ?';\n shiftParams.push(projectFilter);\n }\n\n if (from) {\n shiftSql += ' AND created_at >= ?';\n shiftParams.push(from);\n }\n if (to) {\n shiftSql += ' AND created_at <= ?';\n shiftParams.push(to);\n }\n\n shiftSql += ' ORDER BY created_at DESC LIMIT 100';\n\n const shiftRows = db.prepare(shiftSql).all(...shiftParams) as ShiftDecisionRow[];\n\n topicShifts = shiftRows.map(row => ({\n id: row.id,\n fromTopic: null, // shift_decisions doesn't store topic labels directly\n toTopic: null,\n timestamp: row.created_at,\n confidence: row.confidence,\n }));\n } catch { /* table may not exist yet */ }\n\n return c.json({ sessions, observations, topicShifts });\n});\n\n/**\n * GET /api/node/:id\n *\n * Returns details for a single entity node including its observations\n * and relationships. Powers the detail panel.\n */\napiRoutes.get('/node/:id', (c) => {\n const db = getDb(c);\n const nodeId = c.req.param('id');\n\n // Get the entity node\n interface FullNodeRow {\n id: string;\n name: string;\n type: string;\n observation_ids: string;\n metadata: string;\n created_at: string;\n updated_at: string;\n }\n\n let nodeRow: FullNodeRow | undefined;\n try {\n nodeRow = db.prepare(\n 'SELECT id, name, type, observation_ids, metadata, created_at, updated_at FROM graph_nodes WHERE id = ?'\n ).get(nodeId) as FullNodeRow | undefined;\n } catch { /* table may not exist */ }\n\n if (!nodeRow) {\n return c.json({ error: 'Node not found' }, 404);\n }\n\n const entity = {\n id: nodeRow.id,\n label: nodeRow.name,\n type: nodeRow.type,\n createdAt: nodeRow.created_at,\n updatedAt: nodeRow.updated_at,\n metadata: safeParseJson(nodeRow.metadata),\n };\n\n // Get observations for this entity\n const observationIds = safeParseJsonArray(nodeRow.observation_ids);\n let nodeObservations: Array<{ id: string; text: string; createdAt: string }> = [];\n\n if (observationIds.length > 0) {\n try {\n const placeholders = observationIds.map(() => '?').join(', ');\n const obsRows = db.prepare(\n `SELECT id, content, title, created_at FROM observations WHERE id IN (${placeholders}) AND deleted_at IS NULL ORDER BY created_at DESC`\n ).all(...observationIds) as Array<{ id: string; content: string; title: string | null; created_at: string }>;\n\n nodeObservations = obsRows.map(row => ({\n id: row.id,\n text: row.title ? `${row.title}: ${row.content}` : row.content,\n createdAt: row.created_at,\n }));\n } catch { /* table may not exist */ }\n }\n\n // Get relationships\n interface RelRow {\n id: string;\n source_id: string;\n target_id: string;\n type: string;\n weight: number;\n target_name: string | null;\n target_type: string | null;\n source_name: string | null;\n source_type: string | null;\n }\n\n let relationships: Array<{ id: string; targetId: string; targetLabel: string; type: string; direction: string }> = [];\n try {\n const relRows = db.prepare(`\n SELECT\n e.id, e.source_id, e.target_id, e.type, e.weight,\n tn.name AS target_name, tn.type AS target_type,\n sn.name AS source_name, sn.type AS source_type\n FROM graph_edges e\n LEFT JOIN graph_nodes tn ON tn.id = e.target_id\n LEFT JOIN graph_nodes sn ON sn.id = e.source_id\n WHERE e.source_id = ? OR e.target_id = ?\n ORDER BY e.weight DESC\n `).all(nodeId, nodeId) as RelRow[];\n\n relationships = relRows.map(row => {\n const isSource = row.source_id === nodeId;\n return {\n id: row.id,\n targetId: isSource ? row.target_id : row.source_id,\n targetLabel: isSource ? (row.target_name ?? row.target_id) : (row.source_name ?? row.source_id),\n type: row.type,\n direction: isSource ? 'outgoing' : 'incoming',\n };\n });\n } catch { /* table may not exist */ }\n\n return c.json({ entity, observations: nodeObservations, relationships });\n});\n\n/**\n * GET /api/node/:id/neighborhood\n *\n * Returns the N-hop subgraph around a node. Powers the focus/drill-down view.\n * Query params:\n * ?depth=1 - hop count (1 or 2, default 1)\n */\napiRoutes.get('/node/:id/neighborhood', (c) => {\n const db = getDb(c);\n const centerId = c.req.param('id');\n const depthParam = c.req.query('depth');\n const depth = Math.min(Math.max(parseInt(depthParam || '1', 10) || 1, 1), 2);\n\n // Verify the center node exists\n let centerRow: GraphNodeRow | undefined;\n try {\n centerRow = db.prepare(\n 'SELECT id, name, type, observation_ids, created_at FROM graph_nodes WHERE id = ?'\n ).get(centerId) as GraphNodeRow | undefined;\n } catch { /* table may not exist */ }\n\n if (!centerRow) {\n return c.json({ error: 'Node not found' }, 404);\n }\n\n // Collect node IDs at each depth level\n const visitedNodeIds = new Set<string>([centerId]);\n let frontier = new Set<string>([centerId]);\n\n interface EdgeRow {\n id: string;\n source_id: string;\n target_id: string;\n type: string;\n weight: number;\n }\n\n const allEdgeRows: EdgeRow[] = [];\n const seenEdgeIds = new Set<string>();\n\n for (let d = 0; d < depth; d++) {\n if (frontier.size === 0) break;\n\n const frontierIds = Array.from(frontier);\n const placeholders = frontierIds.map(() => '?').join(', ');\n const nextFrontier = new Set<string>();\n\n try {\n const edgeRows = db.prepare(\n `SELECT id, source_id, target_id, type, weight FROM graph_edges\n WHERE source_id IN (${placeholders}) OR target_id IN (${placeholders})`\n ).all(...frontierIds, ...frontierIds) as EdgeRow[];\n\n for (const edge of edgeRows) {\n if (!seenEdgeIds.has(edge.id)) {\n seenEdgeIds.add(edge.id);\n allEdgeRows.push(edge);\n }\n\n if (!visitedNodeIds.has(edge.source_id)) {\n visitedNodeIds.add(edge.source_id);\n nextFrontier.add(edge.source_id);\n }\n if (!visitedNodeIds.has(edge.target_id)) {\n visitedNodeIds.add(edge.target_id);\n nextFrontier.add(edge.target_id);\n }\n }\n } catch { /* table may not exist */ }\n\n frontier = nextFrontier;\n }\n\n // Fetch full node data for all collected node IDs\n const nodeIds = Array.from(visitedNodeIds);\n let nodeRows: GraphNodeRow[] = [];\n if (nodeIds.length > 0) {\n try {\n const placeholders = nodeIds.map(() => '?').join(', ');\n nodeRows = db.prepare(\n `SELECT id, name, type, observation_ids, created_at FROM graph_nodes WHERE id IN (${placeholders})`\n ).all(...nodeIds) as GraphNodeRow[];\n } catch { /* table may not exist */ }\n }\n\n const nodes = nodeRows.map(row => ({\n id: row.id,\n label: row.name,\n type: row.type,\n observationCount: safeParseJsonArray(row.observation_ids).length,\n createdAt: row.created_at,\n }));\n\n // Only include edges where both endpoints are in our node set\n const nodeIdSet = new Set(nodeIds);\n const edges = allEdgeRows\n .filter(e => nodeIdSet.has(e.source_id) && nodeIdSet.has(e.target_id))\n .map(row => ({\n id: row.id,\n source: row.source_id,\n target: row.target_id,\n type: row.type,\n }));\n\n return c.json({ center: centerId, nodes, edges });\n});\n\n/**\n * GET /api/graph/search\n *\n * Two-tier search: name-based LIKE matching on graph_nodes, then FTS fallback\n * on observations_fts for richer content matching.\n * Query params:\n * ?q= - search query (required)\n * ?type= - entity type filter\n * ?limit=20 - max results\n * ?project= - project hash filter\n */\napiRoutes.get('/graph/search', (c) => {\n const db = getDb(c);\n const query = (c.req.query('q') || '').trim();\n const typeFilter = c.req.query('type') || null;\n const limitStr = c.req.query('limit');\n const limit = limitStr ? Math.min(parseInt(limitStr, 10) || 20, 50) : 20;\n const projectFilter = getProjectHash(c);\n\n if (!query) {\n return c.json({ results: [] });\n }\n\n interface SearchResult {\n id: string;\n label: string;\n type: string;\n observationCount: number;\n matchSource: 'exact' | 'prefix' | 'contains' | 'fts';\n snippet: string | null;\n }\n\n const results: SearchResult[] = [];\n const seenIds = new Set<string>();\n\n // Pass 1: Name-based matching ranked by exact > prefix > contains\n try {\n let nameSql = `SELECT id, name, type, observation_ids FROM graph_nodes WHERE name LIKE ? COLLATE NOCASE`;\n const nameParams: unknown[] = [`%${query}%`];\n\n if (projectFilter) {\n nameSql += ' AND project_hash = ?';\n nameParams.push(projectFilter);\n }\n if (typeFilter) {\n nameSql += ' AND type = ?';\n nameParams.push(typeFilter);\n }\n\n nameSql += ' LIMIT 100';\n\n const rows = db.prepare(nameSql).all(...nameParams) as GraphNodeRow[];\n\n // Rank results: exact > prefix > contains\n const lowerQuery = query.toLowerCase();\n const ranked = rows.map(row => {\n const lowerName = row.name.toLowerCase();\n let rank: 'exact' | 'prefix' | 'contains';\n if (lowerName === lowerQuery) {\n rank = 'exact';\n } else if (lowerName.startsWith(lowerQuery)) {\n rank = 'prefix';\n } else {\n rank = 'contains';\n }\n return { row, rank };\n });\n\n const rankOrder = { exact: 0, prefix: 1, contains: 2 };\n ranked.sort((a, b) => rankOrder[a.rank] - rankOrder[b.rank]);\n\n for (const { row, rank } of ranked) {\n if (results.length >= limit) break;\n seenIds.add(row.id);\n results.push({\n id: row.id,\n label: row.name,\n type: row.type,\n observationCount: safeParseJsonArray(row.observation_ids).length,\n matchSource: rank,\n snippet: null,\n });\n }\n } catch { /* graph_nodes may not exist */ }\n\n // Pass 2: FTS fallback if name matching returned sparse results\n if (results.length < limit) {\n try {\n // Check if observations_fts table exists\n const ftsCheck = db.prepare(\n \"SELECT name FROM sqlite_master WHERE type='table' AND name='observations_fts'\"\n ).get();\n\n if (ftsCheck) {\n let ftsSql = `\n SELECT o.id AS obs_id, o.content, o.title,\n gn.id AS node_id, gn.name, gn.type, gn.observation_ids\n FROM observations_fts fts\n JOIN observations o ON o.id = fts.rowid\n JOIN graph_nodes gn ON EXISTS (\n SELECT 1 FROM json_each(gn.observation_ids) je WHERE je.value = o.id\n )\n WHERE observations_fts MATCH ?\n AND o.deleted_at IS NULL`;\n const ftsParams: unknown[] = [query + '*'];\n\n if (projectFilter) {\n ftsSql += ' AND gn.project_hash = ?';\n ftsParams.push(projectFilter);\n }\n if (typeFilter) {\n ftsSql += ' AND gn.type = ?';\n ftsParams.push(typeFilter);\n }\n\n ftsSql += ' LIMIT 50';\n\n interface FtsRow {\n obs_id: string;\n content: string;\n title: string | null;\n node_id: string;\n name: string;\n type: string;\n observation_ids: string;\n }\n\n const ftsRows = db.prepare(ftsSql).all(...ftsParams) as FtsRow[];\n\n for (const row of ftsRows) {\n if (results.length >= limit) break;\n if (seenIds.has(row.node_id)) continue;\n seenIds.add(row.node_id);\n\n // Build snippet from matching observation content\n const text = row.title ? `${row.title}: ${row.content}` : row.content;\n const snippet = text.length > 120 ? text.substring(0, 120) + '...' : text;\n\n results.push({\n id: row.node_id,\n label: row.name,\n type: row.type,\n observationCount: safeParseJsonArray(row.observation_ids).length,\n matchSource: 'fts',\n snippet,\n });\n }\n }\n } catch { /* FTS table may not exist */ }\n }\n\n return c.json({ results });\n});\n\n/**\n * GET /api/graph/analysis\n *\n * Returns graph analysis insights: type distributions, top entities by degree,\n * connected components, and recent activity stats.\n * 30-second in-memory cache to avoid recomputation.\n */\n\nlet analysisCache: { key: string; data: unknown; expiry: number } | null = null;\n\napiRoutes.get('/graph/analysis', (c) => {\n const db = getDb(c);\n const projectFilter = getProjectHash(c);\n const cacheKey = `analysis:${projectFilter || 'all'}`;\n const now = Date.now();\n\n // Check cache\n if (analysisCache && analysisCache.key === cacheKey && analysisCache.expiry > now) {\n return c.json(analysisCache.data as Record<string, unknown>);\n }\n\n // Entity type distribution\n let entityTypes: Array<{ type: string; count: number }> = [];\n try {\n let sql = 'SELECT type, COUNT(*) as count FROM graph_nodes';\n const params: unknown[] = [];\n if (projectFilter) {\n sql += ' WHERE project_hash = ?';\n params.push(projectFilter);\n }\n sql += ' GROUP BY type ORDER BY count DESC';\n entityTypes = db.prepare(sql).all(...params) as Array<{ type: string; count: number }>;\n } catch { /* table may not exist */ }\n\n // Relationship type distribution\n let relationshipTypes: Array<{ type: string; count: number }> = [];\n try {\n let sql = 'SELECT type, COUNT(*) as count FROM graph_edges';\n const params: unknown[] = [];\n if (projectFilter) {\n sql += ' WHERE project_hash = ?';\n params.push(projectFilter);\n }\n sql += ' GROUP BY type ORDER BY count DESC';\n relationshipTypes = db.prepare(sql).all(...params) as Array<{ type: string; count: number }>;\n } catch { /* table may not exist */ }\n\n // Top 10 entities by degree (most connected)\n let topEntities: Array<{ id: string; label: string; type: string; degree: number }> = [];\n try {\n let sql = `\n SELECT gn.id, gn.name AS label, gn.type,\n (SELECT COUNT(*) FROM graph_edges e WHERE e.source_id = gn.id${projectFilter ? ' AND e.project_hash = ?' : ''})\n + (SELECT COUNT(*) FROM graph_edges e WHERE e.target_id = gn.id${projectFilter ? ' AND e.project_hash = ?' : ''})\n AS degree\n FROM graph_nodes gn`;\n const params: unknown[] = [];\n if (projectFilter) {\n sql += ' WHERE gn.project_hash = ?';\n params.push(projectFilter);\n // Two extra params for the subqueries\n params.unshift(projectFilter, projectFilter);\n }\n sql += ' ORDER BY degree DESC LIMIT 10';\n topEntities = db.prepare(sql).all(...params) as Array<{ id: string; label: string; type: string; degree: number }>;\n } catch { /* table may not exist */ }\n\n // Connected components via shared BFS helper\n let components: Array<{ id: number; label: string; nodeIds: string[]; nodeCount: number; edgeCount: number }> = [];\n try {\n const bfs = findConnectedComponents(db, projectFilter);\n components = bfs.components.map((comp, i) => ({\n id: i,\n label: comp.label,\n nodeIds: comp.nodeIds,\n nodeCount: comp.nodeIds.length,\n edgeCount: comp.edgeCount,\n }));\n } catch { /* tables may not exist */ }\n\n // Recent activity stats\n let recentActivity = { lastDay: 0, lastWeek: 0 };\n try {\n const dayAgo = new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString();\n const weekAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString();\n\n let daySql = 'SELECT COUNT(*) as count FROM graph_nodes WHERE created_at >= ?';\n let weekSql = 'SELECT COUNT(*) as count FROM graph_nodes WHERE created_at >= ?';\n const dayParams: unknown[] = [dayAgo];\n const weekParams: unknown[] = [weekAgo];\n\n if (projectFilter) {\n daySql += ' AND project_hash = ?';\n weekSql += ' AND project_hash = ?';\n dayParams.push(projectFilter);\n weekParams.push(projectFilter);\n }\n\n const dayRow = db.prepare(daySql).get(...dayParams) as { count: number } | undefined;\n const weekRow = db.prepare(weekSql).get(...weekParams) as { count: number } | undefined;\n recentActivity = {\n lastDay: dayRow?.count ?? 0,\n lastWeek: weekRow?.count ?? 0,\n };\n } catch { /* table may not exist */ }\n\n const result = {\n entityTypes,\n relationshipTypes,\n topEntities,\n components,\n recentActivity,\n };\n\n // Cache for 30 seconds\n analysisCache = { key: cacheKey, data: result, expiry: now + 30_000 };\n\n return c.json(result);\n});\n\n/**\n * GET /api/graph/communities\n *\n * Returns community assignments with colors from a 10-color palette.\n * Builds on the same BFS component detection as analysis.\n */\napiRoutes.get('/graph/communities', (c) => {\n const db = getDb(c);\n const projectFilter = getProjectHash(c);\n\n const COMMUNITY_COLORS = [\n '#58a6ff', '#3fb950', '#d2a8ff', '#f0883e', '#f85149',\n '#79c0ff', '#d29922', '#7ee787', '#f778ba', '#a5d6ff',\n ];\n\n interface Community {\n id: number;\n label: string;\n color: string;\n nodeIds: string[];\n }\n\n const communities: Community[] = [];\n let isolatedNodes: string[] = [];\n\n try {\n const bfs = findConnectedComponents(db, projectFilter);\n isolatedNodes = bfs.isolatedNodes;\n for (let i = 0; i < bfs.components.length; i++) {\n const comp = bfs.components[i];\n communities.push({\n id: i,\n label: comp.label,\n color: COMMUNITY_COLORS[i % COMMUNITY_COLORS.length],\n nodeIds: comp.nodeIds,\n });\n }\n } catch { /* tables may not exist */ }\n\n return c.json({ communities, isolatedNodes });\n});\n\n// ---------------------------------------------------------------------------\n// Debug Path endpoints\n// ---------------------------------------------------------------------------\n\n/**\n * GET /api/paths\n *\n * Returns a list of recent debug paths for the current project.\n * Query params:\n * ?limit=20 - max results (default 20, max 50)\n */\napiRoutes.get('/paths', (c) => {\n const db = getDb(c);\n const projectHash = getProjectHash(c);\n\n if (!projectHash) {\n return c.json({ paths: [] });\n }\n\n const limitStr = c.req.query('limit');\n const limit = limitStr ? Math.min(Math.max(parseInt(limitStr, 10) || 20, 1), 50) : 20;\n\n try {\n const repo = new PathRepository(db, projectHash);\n const paths = repo.listPaths(limit);\n return c.json({ paths });\n } catch (err) {\n console.error('[laminark] Failed to list paths:', err);\n return c.json({ paths: [] });\n }\n});\n\n/**\n * GET /api/paths/active\n *\n * Returns the currently active debug path for the current project.\n */\napiRoutes.get('/paths/active', (c) => {\n const db = getDb(c);\n const projectHash = getProjectHash(c);\n\n if (!projectHash) {\n return c.json({ path: null });\n }\n\n try {\n const repo = new PathRepository(db, projectHash);\n const path = repo.getActivePath();\n return c.json({ path });\n } catch (err) {\n console.error('[laminark] Failed to get active path:', err);\n return c.json({ path: null });\n }\n});\n\n/**\n * GET /api/paths/:id\n *\n * Returns a single debug path with its waypoints.\n */\napiRoutes.get('/paths/:id', (c) => {\n const db = getDb(c);\n const projectHash = getProjectHash(c);\n const pathId = c.req.param('id');\n\n if (!projectHash) {\n return c.json({ error: 'Path not found' }, 404);\n }\n\n try {\n const repo = new PathRepository(db, projectHash);\n const path = repo.getPath(pathId);\n\n if (!path) {\n return c.json({ error: 'Path not found' }, 404);\n }\n\n const waypoints: PathWaypoint[] = repo.getWaypoints(pathId);\n\n // Parse kiss_summary from JSON string back to object if present\n let kissSummary: unknown = null;\n if (path.kiss_summary) {\n try {\n kissSummary = JSON.parse(path.kiss_summary);\n } catch {\n kissSummary = path.kiss_summary;\n }\n }\n\n return c.json({\n path: { ...path, kiss_summary: kissSummary },\n waypoints,\n });\n } catch (err) {\n console.error('[laminark] Failed to get path:', err);\n return c.json({ error: 'Path not found' }, 404);\n }\n});\n\n// ---------------------------------------------------------------------------\n// Tool topology endpoints\n// ---------------------------------------------------------------------------\n\n/**\n * GET /api/tools\n *\n * Returns all tools from tool_registry with usage stats.\n */\napiRoutes.get('/tools', (c) => {\n const db = getDb(c);\n\n interface ToolRow {\n id: number;\n name: string;\n tool_type: string;\n scope: string;\n status: string;\n usage_count: number;\n server_name: string | null;\n description: string | null;\n last_used_at: string | null;\n discovered_at: string;\n }\n\n let tools: ToolRow[] = [];\n try {\n tools = db.prepare(`\n SELECT id, name, tool_type, scope, status, usage_count, server_name, description, last_used_at, discovered_at\n FROM tool_registry\n ORDER BY usage_count DESC, discovered_at DESC\n `).all() as ToolRow[];\n } catch { /* table may not exist */ }\n\n return c.json({\n tools: tools.map(t => ({\n id: t.id,\n name: t.name,\n toolType: t.tool_type,\n scope: t.scope,\n status: t.status,\n usageCount: t.usage_count,\n serverName: t.server_name,\n description: t.description,\n lastUsedAt: t.last_used_at,\n discoveredAt: t.discovered_at,\n })),\n });\n});\n\n/**\n * GET /api/tools/flows\n *\n * Returns edges for the tool topology graph:\n * 1. Pre-computed routing_patterns (preceding_tools -> target_tool)\n * 2. Pairwise co-occurrence from tool_usage_events session sequences\n */\napiRoutes.get('/tools/flows', (c) => {\n const db = getDb(c);\n const projectFilter = getProjectHash(c);\n\n interface FlowEdge {\n source: string;\n target: string;\n frequency: number;\n edgeType: 'pattern' | 'session';\n }\n\n const edges: FlowEdge[] = [];\n const edgeKey = new Set<string>();\n\n // 1. routing_patterns edges\n try {\n let sql = 'SELECT target_tool, preceding_tools, frequency FROM routing_patterns';\n const params: unknown[] = [];\n if (projectFilter) {\n sql += ' WHERE project_hash = ?';\n params.push(projectFilter);\n }\n sql += ' ORDER BY frequency DESC LIMIT 200';\n\n const rows = db.prepare(sql).all(...params) as Array<{\n target_tool: string;\n preceding_tools: string;\n frequency: number;\n }>;\n\n for (const row of rows) {\n let preceding: string[];\n try {\n preceding = JSON.parse(row.preceding_tools);\n } catch {\n preceding = [];\n }\n for (const src of preceding) {\n const key = src + '->' + row.target_tool;\n if (!edgeKey.has(key)) {\n edgeKey.add(key);\n edges.push({\n source: src,\n target: row.target_tool,\n frequency: row.frequency,\n edgeType: 'pattern',\n });\n }\n }\n }\n } catch { /* table may not exist */ }\n\n // 2. Session co-occurrence: consecutive tool pairs within sessions\n try {\n let sql = `\n SELECT session_id, tool_name, created_at\n FROM tool_usage_events\n WHERE session_id IS NOT NULL\n `;\n const params: unknown[] = [];\n if (projectFilter) {\n sql += ' AND project_hash = ?';\n params.push(projectFilter);\n }\n sql += ' ORDER BY session_id, created_at ASC LIMIT 5000';\n\n const rows = db.prepare(sql).all(...params) as Array<{\n session_id: string;\n tool_name: string;\n created_at: string;\n }>;\n\n // Group by session and extract consecutive pairs\n const pairFreq = new Map<string, number>();\n let prevSession = '';\n let prevTool = '';\n for (const row of rows) {\n if (row.session_id === prevSession && prevTool && prevTool !== row.tool_name) {\n const key = prevTool + '->' + row.tool_name;\n pairFreq.set(key, (pairFreq.get(key) || 0) + 1);\n }\n prevSession = row.session_id;\n prevTool = row.tool_name;\n }\n\n for (const [key, freq] of pairFreq) {\n if (!edgeKey.has(key) && freq >= 2) {\n edgeKey.add(key);\n const [source, target] = key.split('->');\n edges.push({ source, target, frequency: freq, edgeType: 'session' });\n }\n }\n } catch { /* table may not exist */ }\n\n return c.json({ edges });\n});\n\n/**\n * GET /api/tools/:name/stats\n *\n * Returns detailed stats for a single tool.\n */\napiRoutes.get('/tools/:name/stats', (c) => {\n const db = getDb(c);\n const toolName = c.req.param('name');\n const projectFilter = getProjectHash(c);\n\n // Base tool info\n interface ToolRow {\n id: number;\n name: string;\n tool_type: string;\n scope: string;\n status: string;\n usage_count: number;\n server_name: string | null;\n description: string | null;\n last_used_at: string | null;\n discovered_at: string;\n }\n\n let tool: ToolRow | undefined;\n try {\n tool = db.prepare(\n 'SELECT id, name, tool_type, scope, status, usage_count, server_name, description, last_used_at, discovered_at FROM tool_registry WHERE name = ? ORDER BY usage_count DESC LIMIT 1'\n ).get(toolName) as ToolRow | undefined;\n } catch { /* table may not exist */ }\n\n if (!tool) {\n return c.json({ error: 'Tool not found' }, 404);\n }\n\n // Success rate from recent events\n let successRate: number | null = null;\n let totalEvents = 0;\n try {\n let sql = 'SELECT success FROM tool_usage_events WHERE tool_name = ?';\n const params: unknown[] = [toolName];\n if (projectFilter) {\n sql += ' AND project_hash = ?';\n params.push(projectFilter);\n }\n sql += ' ORDER BY created_at DESC LIMIT 50';\n const events = db.prepare(sql).all(...params) as Array<{ success: number }>;\n totalEvents = events.length;\n if (totalEvents > 0) {\n const successes = events.filter(e => e.success === 1).length;\n successRate = successes / totalEvents;\n }\n } catch { /* table may not exist */ }\n\n // Sessions used in\n let sessionsUsedIn = 0;\n try {\n let sql = 'SELECT COUNT(DISTINCT session_id) as cnt FROM tool_usage_events WHERE tool_name = ? AND session_id IS NOT NULL';\n const params: unknown[] = [toolName];\n if (projectFilter) {\n sql += ' AND project_hash = ?';\n params.push(projectFilter);\n }\n const row = db.prepare(sql).get(...params) as { cnt: number } | undefined;\n sessionsUsedIn = row?.cnt ?? 0;\n } catch { /* table may not exist */ }\n\n // Top co-occurring tools (tools used in same sessions)\n let coOccurring: Array<{ name: string; count: number }> = [];\n try {\n let sql = `\n SELECT e2.tool_name as name, COUNT(*) as count\n FROM tool_usage_events e1\n JOIN tool_usage_events e2\n ON e1.session_id = e2.session_id AND e1.tool_name != e2.tool_name\n WHERE e1.tool_name = ? AND e1.session_id IS NOT NULL\n `;\n const params: unknown[] = [toolName];\n if (projectFilter) {\n sql += ' AND e1.project_hash = ?';\n params.push(projectFilter);\n }\n sql += ' GROUP BY e2.tool_name ORDER BY count DESC LIMIT 10';\n coOccurring = db.prepare(sql).all(...params) as Array<{ name: string; count: number }>;\n } catch { /* table may not exist */ }\n\n return c.json({\n tool: {\n id: tool.id,\n name: tool.name,\n toolType: tool.tool_type,\n scope: tool.scope,\n status: tool.status,\n usageCount: tool.usage_count,\n serverName: tool.server_name,\n description: tool.description,\n lastUsedAt: tool.last_used_at,\n discoveredAt: tool.discovered_at,\n },\n successRate,\n totalEvents,\n sessionsUsedIn,\n coOccurring,\n });\n});\n\n/**\n * GET /api/tools/sessions\n *\n * Returns recent session tool sequences for the flow strip.\n */\napiRoutes.get('/tools/sessions', (c) => {\n const db = getDb(c);\n const projectFilter = getProjectHash(c);\n const limitStr = c.req.query('limit');\n const limit = limitStr ? Math.min(parseInt(limitStr, 10) || 10, 30) : 10;\n\n interface SessionToolRow {\n session_id: string;\n tool_name: string;\n created_at: string;\n }\n\n let sessions: Array<{ sessionId: string; tools: Array<{ name: string; time: string }> }> = [];\n try {\n // Get recent sessions that have tool events\n let sessionSql = `\n SELECT DISTINCT session_id FROM tool_usage_events\n WHERE session_id IS NOT NULL\n `;\n const sessionParams: unknown[] = [];\n if (projectFilter) {\n sessionSql += ' AND project_hash = ?';\n sessionParams.push(projectFilter);\n }\n sessionSql += ' ORDER BY created_at DESC LIMIT ?';\n sessionParams.push(limit);\n\n const sessionIds = db.prepare(sessionSql).all(...sessionParams) as Array<{ session_id: string }>;\n\n if (sessionIds.length > 0) {\n const placeholders = sessionIds.map(() => '?').join(', ');\n const ids = sessionIds.map(s => s.session_id);\n\n const eventRows = db.prepare(`\n SELECT session_id, tool_name, created_at\n FROM tool_usage_events\n WHERE session_id IN (${placeholders})\n ORDER BY session_id, created_at ASC\n `).all(...ids) as SessionToolRow[];\n\n // Group by session\n const sessionMap = new Map<string, Array<{ name: string; time: string }>>();\n for (const row of eventRows) {\n if (!sessionMap.has(row.session_id)) {\n sessionMap.set(row.session_id, []);\n }\n sessionMap.get(row.session_id)!.push({\n name: row.tool_name,\n time: row.created_at,\n });\n }\n\n sessions = sessionIds\n .filter(s => sessionMap.has(s.session_id))\n .map(s => ({\n sessionId: s.session_id,\n tools: sessionMap.get(s.session_id)!,\n }));\n }\n } catch { /* table may not exist */ }\n\n return c.json({ sessions });\n});\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\ninterface BfsComponent {\n nodeIds: string[];\n label: string;\n edgeCount: number;\n}\n\n/**\n * Finds connected components in the graph via BFS.\n * Shared by /api/graph/analysis and /api/graph/communities.\n */\nfunction findConnectedComponents(\n db: BetterSqlite3.Database,\n projectFilter: string | null,\n): { components: BfsComponent[]; isolatedNodes: string[]; adj: Map<string, Set<string>> } {\n // Fetch all nodes\n let nodesSql = 'SELECT id, name FROM graph_nodes';\n const nodesParams: unknown[] = [];\n if (projectFilter) {\n nodesSql += ' WHERE project_hash = ?';\n nodesParams.push(projectFilter);\n }\n const allNodes = db.prepare(nodesSql).all(...nodesParams) as Array<{ id: string; name: string }>;\n const nodeNameMap = new Map(allNodes.map(n => [n.id, n.name]));\n\n // Fetch all edges\n let edgesSql = 'SELECT source_id, target_id FROM graph_edges';\n const edgesParams: unknown[] = [];\n if (projectFilter) {\n edgesSql += ' WHERE project_hash = ?';\n edgesParams.push(projectFilter);\n }\n const allEdges = db.prepare(edgesSql).all(...edgesParams) as Array<{ source_id: string; target_id: string }>;\n\n // Build adjacency list\n const adj = new Map<string, Set<string>>();\n for (const node of allNodes) {\n adj.set(node.id, new Set());\n }\n for (const edge of allEdges) {\n if (adj.has(edge.source_id)) adj.get(edge.source_id)!.add(edge.target_id);\n if (adj.has(edge.target_id)) adj.get(edge.target_id)!.add(edge.source_id);\n }\n\n // BFS to find connected components\n const visited = new Set<string>();\n const components: BfsComponent[] = [];\n const isolatedNodes: string[] = [];\n\n for (const nodeId of adj.keys()) {\n if (visited.has(nodeId)) continue;\n\n const queue = [nodeId];\n visited.add(nodeId);\n const compNodes: string[] = [];\n\n while (queue.length > 0) {\n const current = queue.shift()!;\n compNodes.push(current);\n for (const neighbor of adj.get(current) || []) {\n if (!visited.has(neighbor)) {\n visited.add(neighbor);\n queue.push(neighbor);\n }\n }\n }\n\n // Detect isolated nodes (single node, no edges)\n if (compNodes.length === 1 && (adj.get(compNodes[0])?.size ?? 0) === 0) {\n isolatedNodes.push(compNodes[0]);\n continue;\n }\n\n // Count edges within this component\n const compSet = new Set(compNodes);\n let edgeCount = 0;\n for (const edge of allEdges) {\n if (compSet.has(edge.source_id) && compSet.has(edge.target_id)) {\n edgeCount++;\n }\n }\n\n // Label by highest-degree node\n let maxDeg = -1;\n let labelNodeId = compNodes[0];\n for (const nid of compNodes) {\n const deg = (adj.get(nid) || new Set()).size;\n if (deg > maxDeg) {\n maxDeg = deg;\n labelNodeId = nid;\n }\n }\n\n components.push({\n nodeIds: compNodes,\n label: nodeNameMap.get(labelNodeId) || labelNodeId,\n edgeCount,\n });\n }\n\n // Sort by size descending\n components.sort((a, b) => b.nodeIds.length - a.nodeIds.length);\n\n return { components, isolatedNodes, adj };\n}\n\nfunction safeParseJsonArray(json: string): string[] {\n try {\n const parsed = JSON.parse(json);\n return Array.isArray(parsed) ? parsed : [];\n } catch {\n return [];\n }\n}\n\nfunction safeParseJson(json: string): Record<string, unknown> {\n try {\n return JSON.parse(json) as Record<string, unknown>;\n } catch {\n return {};\n }\n}\n","/**\n * Admin API routes for database statistics and reset operations.\n *\n * @module web/routes/admin\n */\n\nimport { existsSync, readFileSync, statSync, unlinkSync, writeFileSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nimport { Hono } from 'hono';\nimport type BetterSqlite3 from 'better-sqlite3';\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\nconst LAMINARK_VERSION = (() => {\n try {\n const pkg = JSON.parse(readFileSync(join(__dirname, '..', '..', 'package.json'), 'utf-8'));\n return pkg.version || 'unknown';\n } catch { return 'unknown'; }\n})();\n\nimport { getConfigDir } from '../../shared/config.js';\nimport { analyzeObservations, executePurge, findAnalysis } from '../../graph/hygiene-analyzer.js';\nimport { loadHygieneConfig, saveHygieneConfig, resetHygieneConfig } from '../../config/hygiene-config.js';\nimport { loadTopicDetectionConfig } from '../../config/topic-detection-config.js';\nimport { loadGraphExtractionConfig } from '../../config/graph-extraction-config.js';\nimport { loadCrossAccessConfig, saveCrossAccessConfig, resetCrossAccessConfig } from '../../config/cross-access.js';\nimport { loadToolVerbosityConfig, saveToolVerbosityConfig, resetToolVerbosityConfig } from '../../config/tool-verbosity-config.js';\n\ntype AppEnv = {\n Variables: {\n db: BetterSqlite3.Database;\n defaultProject: string;\n };\n};\n\nfunction getDb(c: { get: (key: 'db') => BetterSqlite3.Database }): BetterSqlite3.Database {\n return c.get('db');\n}\n\nfunction getProjectHash(c: { get: (key: 'defaultProject') => string; req: { query: (key: string) => string | undefined } }): string | null {\n return c.req.query('project') || c.get('defaultProject') || null;\n}\n\nconst ALLOWED_TABLES = new Set([\n 'observations', 'observations_fts', 'observation_embeddings', 'staleness_flags',\n 'graph_nodes', 'graph_edges', 'sessions', 'context_stashes', 'threshold_history',\n 'shift_decisions', 'pending_notifications', 'project_metadata', '_migrations',\n 'tool_registry', 'tool_usage_events', 'research_buffer',\n]);\n\nfunction tableCount(db: BetterSqlite3.Database, table: string, where?: string, params?: unknown[]): number {\n if (!ALLOWED_TABLES.has(table)) return 0;\n try {\n const sql = where\n ? `SELECT COUNT(*) AS cnt FROM ${table} WHERE ${where}`\n : `SELECT COUNT(*) AS cnt FROM ${table}`;\n const row = db.prepare(sql).get(...(params || [])) as { cnt: number } | undefined;\n return row?.cnt ?? 0;\n } catch {\n return 0;\n }\n}\n\nexport const adminRoutes = new Hono<AppEnv>();\n\n/**\n * GET /api/admin/stats\n *\n * Returns row counts per table group, optionally scoped to a project.\n */\nadminRoutes.get('/stats', (c) => {\n const db = getDb(c);\n const project = c.req.query('project') || getProjectHash(c);\n\n const projectWhere = project ? 'project_hash = ?' : undefined;\n const projectIdWhere = project ? 'project_id = ?' : undefined;\n const projectParams = project ? [project] : undefined;\n\n const observations = tableCount(db, 'observations', projectWhere, projectParams);\n const observationsFts = tableCount(db, 'observations_fts');\n const observationEmbeddings = tableCount(db, 'observation_embeddings');\n const stalenessFlags = tableCount(db, 'staleness_flags');\n const graphNodes = tableCount(db, 'graph_nodes', projectWhere, projectParams);\n const graphEdges = tableCount(db, 'graph_edges', projectWhere, projectParams);\n const sessions = tableCount(db, 'sessions', projectWhere, projectParams);\n const contextStashes = tableCount(db, 'context_stashes', projectIdWhere, projectParams);\n const thresholdHistory = tableCount(db, 'threshold_history', projectIdWhere, projectParams);\n const shiftDecisions = tableCount(db, 'shift_decisions', projectIdWhere, projectParams);\n const pendingNotifications = tableCount(db, 'pending_notifications', projectIdWhere, projectParams);\n const projects = tableCount(db, 'project_metadata');\n\n return c.json({\n observations,\n observationsFts,\n observationEmbeddings,\n stalenessFlags,\n graphNodes,\n graphEdges,\n sessions,\n contextStashes,\n thresholdHistory,\n shiftDecisions,\n pendingNotifications,\n projects,\n scopedToProject: project || null,\n });\n});\n\n/**\n * GET /api/admin/system\n *\n * Returns server-scoped system info (not project-scoped).\n */\nadminRoutes.get('/system', (c) => {\n const db = getDb(c);\n const mem = process.memoryUsage();\n\n let dbSizeBytes = 0;\n let pageCount = 0;\n let pageSize = 4096;\n try {\n const pc = db.pragma('page_count', { simple: true }) as number;\n const ps = db.pragma('page_size', { simple: true }) as number;\n pageCount = pc;\n pageSize = ps;\n dbSizeBytes = pc * ps;\n } catch { /* ignore */ }\n\n let walSizeBytes = 0;\n try {\n const dbPath = db.name;\n if (dbPath) {\n const walPath = dbPath + '-wal';\n if (existsSync(walPath)) {\n walSizeBytes = statSync(walPath).size;\n }\n }\n } catch { /* ignore */ }\n\n return c.json({\n laminarkVersion: LAMINARK_VERSION,\n nodeVersion: process.version,\n platform: process.platform,\n arch: process.arch,\n uptimeSeconds: Math.floor(process.uptime()),\n memory: {\n rssBytes: mem.rss,\n heapUsedBytes: mem.heapUsed,\n heapTotalBytes: mem.heapTotal,\n },\n database: {\n sizeBytes: dbSizeBytes,\n walSizeBytes,\n pageCount,\n pageSize,\n },\n });\n});\n\n/**\n * POST /api/admin/reset\n *\n * Hard-deletes data by group inside a transaction.\n * Body: { type: 'observations'|'graph'|'sessions'|'all', scope: 'current'|'all', projectHash?: string }\n */\nadminRoutes.post('/reset', async (c) => {\n const db = getDb(c);\n const body = await c.req.json<{ type: string; scope: string; projectHash?: string }>();\n const { type, scope } = body;\n const project = body.projectHash || getProjectHash(c);\n\n const validTypes = ['observations', 'graph', 'sessions', 'all'];\n if (!validTypes.includes(type)) {\n return c.json({ error: `Invalid type. Must be one of: ${validTypes.join(', ')}` }, 400);\n }\n\n const scoped = scope === 'current' && project;\n const deleted: string[] = [];\n\n const exec = (sql: string) => {\n try { db.exec(sql); } catch { /* table/trigger may not exist */ }\n };\n\n const run = (sql: string, params?: unknown[]) => {\n try {\n db.prepare(sql).run(...(params || []));\n } catch {\n // Table may not exist — skip silently\n }\n };\n\n db.transaction(() => {\n if (type === 'observations' || type === 'all') {\n // Drop FTS sync triggers FIRST — they fire on every DELETE and will\n // abort the delete if the FTS index is out of sync with observations.\n exec('DROP TRIGGER IF EXISTS observations_ai');\n exec('DROP TRIGGER IF EXISTS observations_au');\n exec('DROP TRIGGER IF EXISTS observations_ad');\n\n if (scoped) {\n run('DELETE FROM observation_embeddings WHERE observation_id IN (SELECT id FROM observations WHERE project_hash = ?)', [project]);\n run('DELETE FROM staleness_flags WHERE observation_id IN (SELECT id FROM observations WHERE project_hash = ?)', [project]);\n run('DELETE FROM observations WHERE project_hash = ?', [project]);\n } else {\n run('DELETE FROM observation_embeddings');\n run('DELETE FROM staleness_flags');\n run('DELETE FROM observations');\n }\n\n // Rebuild FTS (will be empty or contain only remaining rows)\n exec(\"INSERT INTO observations_fts(observations_fts) VALUES('rebuild')\");\n\n // Recreate FTS sync triggers\n exec(`\n CREATE TRIGGER observations_ai AFTER INSERT ON observations BEGIN\n INSERT INTO observations_fts(rowid, title, content)\n VALUES (new.rowid, new.title, new.content);\n END\n `);\n exec(`\n CREATE TRIGGER observations_au AFTER UPDATE ON observations BEGIN\n INSERT INTO observations_fts(observations_fts, rowid, title, content)\n VALUES('delete', old.rowid, old.title, old.content);\n INSERT INTO observations_fts(rowid, title, content)\n VALUES (new.rowid, new.title, new.content);\n END\n `);\n exec(`\n CREATE TRIGGER observations_ad AFTER DELETE ON observations BEGIN\n INSERT INTO observations_fts(observations_fts, rowid, title, content)\n VALUES('delete', old.rowid, old.title, old.content);\n END\n `);\n\n deleted.push('observations', 'observations_fts', 'observation_embeddings', 'staleness_flags');\n }\n\n if (type === 'graph' || type === 'all') {\n if (scoped) {\n run('DELETE FROM graph_edges WHERE project_hash = ?', [project]);\n run('DELETE FROM graph_nodes WHERE project_hash = ?', [project]);\n } else {\n run('DELETE FROM graph_edges');\n run('DELETE FROM graph_nodes');\n }\n deleted.push('graph_nodes', 'graph_edges');\n }\n\n if (type === 'sessions' || type === 'all') {\n if (scoped) {\n run('DELETE FROM shift_decisions WHERE project_id = ?', [project]);\n run('DELETE FROM threshold_history WHERE project_id = ?', [project]);\n run('DELETE FROM context_stashes WHERE project_id = ?', [project]);\n run('DELETE FROM pending_notifications WHERE project_id = ?', [project]);\n run('DELETE FROM sessions WHERE project_hash = ?', [project]);\n } else {\n run('DELETE FROM shift_decisions');\n run('DELETE FROM threshold_history');\n run('DELETE FROM context_stashes');\n run('DELETE FROM pending_notifications');\n run('DELETE FROM sessions');\n }\n deleted.push('sessions', 'context_stashes', 'threshold_history', 'shift_decisions', 'pending_notifications');\n }\n\n if (type === 'all' && !scoped) {\n run('DELETE FROM project_metadata');\n run('DELETE FROM _migrations');\n deleted.push('project_metadata', '_migrations');\n }\n })();\n\n return c.json({ ok: true, deleted, scope: scoped ? 'project' : 'all' });\n});\n\n// =========================================================================\n// Hygiene analysis\n// =========================================================================\n\nadminRoutes.get('/hygiene', (c) => {\n const db = getDb(c);\n const project = getProjectHash(c);\n if (!project) return c.json({ error: 'No project context available' }, 400);\n\n const tier = (c.req.query('tier') || 'high') as 'high' | 'medium' | 'all';\n const sessionId = c.req.query('session_id');\n const limit = parseInt(c.req.query('limit') || '50', 10);\n const config = loadHygieneConfig();\n\n const minTier = tier === 'all' ? 'low' as const : tier;\n const report = analyzeObservations(db, project, { sessionId, limit, minTier, config });\n\n return c.json(report);\n});\n\nadminRoutes.post('/hygiene/purge', async (c) => {\n const db = getDb(c);\n const project = getProjectHash(c);\n if (!project) return c.json({ error: 'No project context available' }, 400);\n\n const body = await c.req.json<{ tier?: string }>();\n const tier = (body.tier || 'high') as 'high' | 'medium' | 'all';\n const config = loadHygieneConfig();\n\n const minTier = tier === 'all' ? 'low' as const : tier;\n const report = analyzeObservations(db, project, { minTier, limit: 500, config });\n const result = executePurge(db, project, report, tier);\n\n return c.json({\n ok: true,\n observationsPurged: result.observationsPurged,\n orphanNodesRemoved: result.orphanNodesRemoved,\n tier,\n });\n});\n\nadminRoutes.get('/hygiene/find', (c) => {\n const db = getDb(c);\n const project = getProjectHash(c);\n if (!project) return c.json({ error: 'No project context available' }, 400);\n\n const config = loadHygieneConfig();\n const report = findAnalysis(db, project, config);\n return c.json(report);\n});\n\n// =========================================================================\n// Configuration endpoints\n// =========================================================================\n\nadminRoutes.get('/config/hygiene', (c) => {\n return c.json(loadHygieneConfig());\n});\n\nadminRoutes.put('/config/hygiene', async (c) => {\n const body = await c.req.json();\n const configPath = join(getConfigDir(), 'hygiene.json');\n\n if (body && body.__reset === true) {\n try { if (existsSync(configPath)) unlinkSync(configPath); } catch { /* ignore */ }\n return c.json(resetHygieneConfig());\n }\n\n if (typeof body !== 'object' || body === null || Array.isArray(body)) {\n return c.json({ error: 'Request body must be a JSON object' }, 400);\n }\n\n const { __reset: _, ...data } = body;\n writeFileSync(configPath, JSON.stringify(data, null, 2), 'utf-8');\n const validated = loadHygieneConfig();\n // Re-write validated config to ensure clean file\n saveHygieneConfig(validated);\n return c.json(validated);\n});\n\nadminRoutes.get('/config/topic-detection', (c) => {\n return c.json(loadTopicDetectionConfig());\n});\n\nadminRoutes.put('/config/topic-detection', async (c) => {\n const body = await c.req.json();\n const configPath = join(getConfigDir(), 'topic-detection.json');\n\n if (body && body.__reset === true) {\n try { if (existsSync(configPath)) unlinkSync(configPath); } catch { /* ignore */ }\n return c.json(loadTopicDetectionConfig());\n }\n\n if (typeof body !== 'object' || body === null || Array.isArray(body)) {\n return c.json({ error: 'Request body must be a JSON object' }, 400);\n }\n\n const { __reset: _, ...data } = body;\n // Write raw input, then re-load (validates all fields), then overwrite with validated config\n writeFileSync(configPath, JSON.stringify(data, null, 2), 'utf-8');\n const validated = loadTopicDetectionConfig();\n writeFileSync(configPath, JSON.stringify(validated, null, 2), 'utf-8');\n return c.json(validated);\n});\n\nadminRoutes.get('/config/graph-extraction', (c) => {\n return c.json(loadGraphExtractionConfig());\n});\n\nadminRoutes.put('/config/graph-extraction', async (c) => {\n const body = await c.req.json();\n const configPath = join(getConfigDir(), 'graph-extraction.json');\n\n if (body && body.__reset === true) {\n try { if (existsSync(configPath)) unlinkSync(configPath); } catch { /* ignore */ }\n return c.json(loadGraphExtractionConfig());\n }\n\n if (typeof body !== 'object' || body === null || Array.isArray(body)) {\n return c.json({ error: 'Request body must be a JSON object' }, 400);\n }\n\n const { __reset: _, ...data } = body;\n // Write raw input, then re-load (validates all fields), then overwrite with validated config\n writeFileSync(configPath, JSON.stringify(data, null, 2), 'utf-8');\n const validated = loadGraphExtractionConfig();\n writeFileSync(configPath, JSON.stringify(validated, null, 2), 'utf-8');\n return c.json(validated);\n});\n\n// =========================================================================\n// Cross-Project Access Config\n// =========================================================================\n\nadminRoutes.get('/config/cross-access', (c) => {\n const project = c.req.query('project');\n if (!project) return c.json({ error: 'project query parameter is required' }, 400);\n return c.json(loadCrossAccessConfig(project));\n});\n\nadminRoutes.put('/config/cross-access', async (c) => {\n const project = c.req.query('project');\n if (!project) return c.json({ error: 'project query parameter is required' }, 400);\n\n const body = await c.req.json();\n\n if (body && body.__reset === true) {\n resetCrossAccessConfig(project);\n return c.json(loadCrossAccessConfig(project));\n }\n\n if (typeof body !== 'object' || body === null || Array.isArray(body)) {\n return c.json({ error: 'Request body must be a JSON object' }, 400);\n }\n\n saveCrossAccessConfig(project, { readableProjects: body.readableProjects || [] });\n return c.json(loadCrossAccessConfig(project));\n});\n\n// =========================================================================\n// Tool Response Verbosity Config\n// =========================================================================\n\nadminRoutes.get('/config/tool-verbosity', (c) => {\n return c.json(loadToolVerbosityConfig());\n});\n\nadminRoutes.put('/config/tool-verbosity', async (c) => {\n const body = await c.req.json();\n\n if (body && body.__reset === true) {\n const config = resetToolVerbosityConfig();\n saveToolVerbosityConfig(config);\n return c.json(config);\n }\n\n if (typeof body !== 'object' || body === null || Array.isArray(body)) {\n return c.json({ error: 'Request body must be a JSON object' }, 400);\n }\n\n const level = body.level;\n if (level !== 1 && level !== 2 && level !== 3) {\n return c.json({ error: 'level must be 1, 2, or 3' }, 400);\n }\n\n saveToolVerbosityConfig({ level });\n return c.json(loadToolVerbosityConfig());\n});\n\n// =========================================================================\n// Project Deletion (GUI-only)\n// =========================================================================\n\ninterface ProjectRow {\n project_hash: string;\n project_path: string;\n display_name: string | null;\n last_seen_at: string;\n}\n\n/**\n * DELETE /api/admin/projects/:hash?confirm=true\n *\n * Permanently deletes all data for the given project.\n * Requires `?confirm=true` query param as a safety guard.\n * Cannot delete the currently active (most-recently-seen) project.\n */\nadminRoutes.delete('/projects/:hash', (c) => {\n const db = getDb(c);\n const projectHash = c.req.param('hash');\n const confirm = c.req.query('confirm');\n\n if (confirm !== 'true') {\n return c.json({ error: 'Safety guard: append ?confirm=true to proceed with deletion' }, 400);\n }\n\n // Determine the active project (most recently seen)\n const activeProject = db.prepare(\n 'SELECT project_hash FROM project_metadata ORDER BY last_seen_at DESC LIMIT 1',\n ).get() as { project_hash: string } | undefined;\n\n if (activeProject && projectHash === activeProject.project_hash) {\n return c.json({ error: 'Cannot delete the currently active project. Switch to a different project first.' }, 400);\n }\n\n // Validate the project exists\n const project = db.prepare(\n 'SELECT project_hash, project_path FROM project_metadata WHERE project_hash = ?',\n ).get(projectHash) as ProjectRow | undefined;\n\n if (!project) {\n return c.json({ error: `No project found with hash '${projectHash}'` }, 404);\n }\n\n // Safe helpers that ignore missing tables\n const run = (sql: string, params: unknown[]) => {\n try { db.prepare(sql).run(...params); } catch { /* table may not exist */ }\n };\n const exec = (sql: string) => {\n try { db.exec(sql); } catch { /* trigger/table may not exist */ }\n };\n\n let obsCount = 0;\n try {\n const row = db.prepare('SELECT COUNT(*) as cnt FROM observations WHERE project_hash = ?').get(projectHash) as { cnt: number };\n obsCount = row.cnt;\n } catch { /* table may not exist */ }\n\n db.transaction(() => {\n // 1. Drop FTS triggers to avoid issues during bulk delete\n exec('DROP TRIGGER IF EXISTS observations_ai');\n exec('DROP TRIGGER IF EXISTS observations_au');\n exec('DROP TRIGGER IF EXISTS observations_ad');\n\n // 2. Embeddings (no FK cascade from observations)\n run('DELETE FROM observation_embeddings WHERE observation_id IN (SELECT id FROM observations WHERE project_hash = ?)', [projectHash]);\n\n // 3. Staleness flags\n run('DELETE FROM staleness_flags WHERE observation_id IN (SELECT id FROM observations WHERE project_hash = ?)', [projectHash]);\n\n // 4. Observations\n run('DELETE FROM observations WHERE project_hash = ?', [projectHash]);\n\n // 5. Rebuild FTS\n exec(\"INSERT INTO observations_fts(observations_fts) VALUES('rebuild')\");\n\n // 6. Recreate FTS sync triggers\n exec(`\n CREATE TRIGGER observations_ai AFTER INSERT ON observations BEGIN\n INSERT INTO observations_fts(rowid, title, content)\n VALUES (new.rowid, new.title, new.content);\n END\n `);\n exec(`\n CREATE TRIGGER observations_au AFTER UPDATE ON observations BEGIN\n INSERT INTO observations_fts(observations_fts, rowid, title, content)\n VALUES('delete', old.rowid, old.title, old.content);\n INSERT INTO observations_fts(rowid, title, content)\n VALUES (new.rowid, new.title, new.content);\n END\n `);\n exec(`\n CREATE TRIGGER observations_ad AFTER DELETE ON observations BEGIN\n INSERT INTO observations_fts(observations_fts, rowid, title, content)\n VALUES('delete', old.rowid, old.title, old.content);\n END\n `);\n\n // 7. Graph (edges have FK cascade from nodes, but delete both explicitly)\n run('DELETE FROM graph_edges WHERE project_hash = ?', [projectHash]);\n run('DELETE FROM graph_nodes WHERE project_hash = ?', [projectHash]);\n\n // 8. Sessions and legacy project_id tables\n run('DELETE FROM sessions WHERE project_hash = ?', [projectHash]);\n run('DELETE FROM shift_decisions WHERE project_id = ?', [projectHash]);\n run('DELETE FROM threshold_history WHERE project_id = ?', [projectHash]);\n run('DELETE FROM context_stashes WHERE project_id = ?', [projectHash]);\n run('DELETE FROM pending_notifications WHERE project_id = ?', [projectHash]);\n\n // 9. Research buffer\n run('DELETE FROM research_buffer WHERE project_hash = ?', [projectHash]);\n\n // 10. Tool registry (project-scoped entries only)\n run('DELETE FROM tool_registry WHERE project_hash = ?', [projectHash]);\n run('DELETE FROM tool_usage_events WHERE project_hash = ?', [projectHash]);\n\n // 11. Debug paths (path_waypoints cascade via FK)\n run('DELETE FROM debug_paths WHERE project_hash = ?', [projectHash]);\n\n // 12. Thought branches (branch_observations cascade via FK)\n run('DELETE FROM thought_branches WHERE project_hash = ?', [projectHash]);\n\n // 13. Routing state\n run('DELETE FROM routing_patterns WHERE project_hash = ?', [projectHash]);\n run('DELETE FROM routing_state WHERE project_hash = ?', [projectHash]);\n\n // 14. Project metadata last\n run('DELETE FROM project_metadata WHERE project_hash = ?', [projectHash]);\n })();\n\n return c.json({\n ok: true,\n deleted: {\n projectPath: project.project_path,\n projectHash,\n observationsRemoved: obsCount,\n },\n });\n});\n","/**\n * Hono web server for the Laminark visualization UI.\n *\n * Serves static assets from the ui/ directory and registers REST API\n * and SSE route groups. Configured with CORS for localhost development\n * and a health check endpoint.\n *\n * @module web/server\n */\n\nimport path from 'path';\nimport fs from 'fs';\nimport { Hono } from 'hono';\nimport { cors } from 'hono/cors';\nimport { serve } from '@hono/node-server';\nimport { serveStatic } from '@hono/node-server/serve-static';\nimport type BetterSqlite3 from 'better-sqlite3';\n\ntype AppEnv = {\n Variables: {\n db: BetterSqlite3.Database;\n defaultProject: string;\n };\n};\n\nimport { debug } from '../shared/debug.js';\nimport { apiRoutes } from './routes/api.js';\nimport { sseRoutes } from './routes/sse.js';\nimport { adminRoutes } from './routes/admin.js';\n\n/**\n * Creates a configured Hono app with middleware, static serving,\n * and route registration.\n *\n * @param db - better-sqlite3 Database instance for API queries\n * @param uiRoot - Absolute path to the ui/ directory for static file serving\n * @returns Configured Hono app\n */\nexport function createWebServer(db: BetterSqlite3.Database, uiRoot: string, defaultProjectHash?: string): Hono<AppEnv> {\n const app = new Hono<AppEnv>();\n\n // CORS middleware for localhost development\n app.use(\n '*',\n cors({\n origin: (origin) => {\n if (!origin) return '*';\n if (origin.startsWith('http://localhost:') || origin.startsWith('http://127.0.0.1:')) {\n return origin;\n }\n return null as unknown as string;\n },\n }),\n );\n\n // Make db and defaultProject available to all route handlers via Hono context\n app.use('*', async (c, next) => {\n c.set('db', db);\n if (defaultProjectHash) {\n c.set('defaultProject', defaultProjectHash);\n }\n await next();\n });\n\n // Health check endpoint\n app.get('/api/health', (c) => {\n return c.json({ status: 'ok', timestamp: Date.now() });\n });\n\n // Mount API and SSE routes\n app.route('/api', apiRoutes);\n app.route('/api', sseRoutes);\n app.route('/api/admin', adminRoutes);\n\n // Serve static files from ui/ directory (absolute path so CWD doesn't matter)\n app.use('/*', async (c, next) => {\n const reqPath = c.req.path === '/' ? '/index.html' : c.req.path;\n const filePath = path.join(uiRoot, reqPath);\n try {\n const data = fs.readFileSync(filePath);\n const ext = path.extname(filePath).toLowerCase();\n const mimeTypes: Record<string, string> = {\n '.html': 'text/html',\n '.js': 'application/javascript',\n '.css': 'text/css',\n '.json': 'application/json',\n '.png': 'image/png',\n '.svg': 'image/svg+xml',\n '.ico': 'image/x-icon',\n };\n return c.body(data, 200, {\n 'Content-Type': mimeTypes[ext] || 'application/octet-stream',\n });\n } catch {\n await next();\n }\n });\n\n // Fallback: serve index.html for SPA routing\n app.get('*', async (c) => {\n const indexPath = path.join(uiRoot, 'index.html');\n try {\n const data = fs.readFileSync(indexPath, 'utf-8');\n return c.html(data);\n } catch {\n return c.text('UI not found', 404);\n }\n });\n\n return app;\n}\n\n/**\n * Starts the Hono web server on the specified port.\n *\n * If the port is already in use (EADDRINUSE), skips starting — the first\n * MCP instance owns the web server and all instances share the same SQLite\n * database via WAL mode, so a single web server suffices.\n *\n * @param app - Configured Hono app from createWebServer()\n * @param port - Port number (default: 37820)\n * @returns The Node.js HTTP server instance, or null if port is already taken\n */\nexport function startWebServer(\n app: Hono<AppEnv>,\n port: number = 37820,\n): ReturnType<typeof serve> | null {\n debug('db', `Starting web server on port ${port}`);\n\n const server = serve({\n fetch: app.fetch,\n port,\n });\n\n server.on('error', (err: NodeJS.ErrnoException) => {\n if (err.code === 'EADDRINUSE') {\n server.close();\n debug('db', `Web server already running on port ${port}, skipping`);\n } else {\n debug('db', `Web server error: ${err.message}`);\n }\n });\n\n server.on('listening', () => {\n const addr = server.address();\n const actualPort = typeof addr === 'object' && addr ? addr.port : port;\n debug('db', `Web server listening on http://localhost:${actualPort}`);\n });\n\n return server;\n}\n","#!/usr/bin/env node\n\n// Laminark MCP server entry point\n// Re-export storage API for library consumers\nexport * from './storage/index.js';\n\nimport path from 'path';\nimport { fileURLToPath } from 'url';\n\nimport { openDatabase } from './storage/database.js';\nimport { getDatabaseConfig, getProjectHash } from './shared/config.js';\nimport { debug } from './shared/debug.js';\nimport type { ProjectHashRef } from './shared/types.js';\nimport { createServer, startServer } from './mcp/server.js';\nimport { registerRecall } from './mcp/tools/recall.js';\nimport { registerSaveMemory } from './mcp/tools/save-memory.js';\nimport { registerIngestKnowledge } from './mcp/tools/ingest-knowledge.js';\nimport { registerTopicContext } from './mcp/tools/topic-context.js';\nimport { registerQueryGraph } from './mcp/tools/query-graph.js';\nimport { registerGraphStats } from './mcp/tools/graph-stats.js';\nimport { registerHygiene } from './mcp/tools/hygiene.js';\nimport { registerStatus } from './mcp/tools/status.js';\nimport { StatusCache } from './mcp/status-cache.js';\nimport { registerDiscoverTools } from './mcp/tools/discover-tools.js';\nimport { registerReportTools } from './mcp/tools/report-tools.js';\nimport { registerDebugPathTools } from './mcp/tools/debug-paths.js';\nimport { registerThoughtBranchTools } from './mcp/tools/thought-branches.js';\nimport { BranchRepository } from './branches/branch-repository.js';\nimport { BranchTracker } from './branches/branch-tracker.js';\nimport { AnalysisWorker } from './analysis/worker-bridge.js';\nimport { EmbeddingStore } from './storage/embeddings.js';\nimport { ObservationRepository } from './storage/observations.js';\nimport { ResearchBufferRepository } from './storage/research-buffer.js';\nimport { TopicShiftHandler } from './hooks/topic-shift-handler.js';\nimport { TopicShiftDetector } from './intelligence/topic-detector.js';\nimport { AdaptiveThresholdManager } from './intelligence/adaptive-threshold.js';\nimport { TopicShiftDecisionLogger } from './intelligence/decision-logger.js';\nimport { loadTopicDetectionConfig, applyConfig } from './config/topic-detection-config.js';\nimport { loadGraphExtractionConfig } from './config/graph-extraction-config.js';\nimport { StashManager } from './storage/stash-manager.js';\nimport { ThresholdStore } from './storage/threshold-store.js';\nimport { NotificationStore } from './storage/notifications.js';\nimport { initGraphSchema } from './graph/schema.js';\nimport { CurationAgent } from './graph/curation-agent.js';\nimport { HaikuProcessor } from './intelligence/haiku-processor.js';\nimport { initPathSchema } from './paths/schema.js';\nimport { PathRepository } from './paths/path-repository.js';\nimport { PathTracker } from './paths/path-tracker.js';\nimport { broadcast } from './web/routes/sse.js';\nimport { createWebServer, startWebServer } from './web/server.js';\nimport { ToolRegistryRepository } from './storage/tool-registry.js';\n\nconst noGui = process.argv.includes('--no_gui');\n\nconst db = openDatabase(getDatabaseConfig());\ninitGraphSchema(db.db);\ninitPathSchema(db.db);\n\n// ---------------------------------------------------------------------------\n// Live project hash ref — auto-refreshes from project_metadata on access.\n//\n// The MCP server's process.cwd() is the plugin install path, NOT the user's\n// project directory. The correct hash is written to project_metadata by the\n// SessionStart hook (which receives input.cwd from Claude Code). This ref\n// re-queries the database at most once per CHECK_INTERVAL_MS so tool handlers\n// always use the correct, current project hash.\n// ---------------------------------------------------------------------------\n\nclass LiveProjectHashRef implements ProjectHashRef {\n private _current: string;\n private _lastChecked = 0;\n private _db: import('better-sqlite3').Database;\n private static readonly CHECK_INTERVAL_MS = 2000;\n\n constructor(sqliteDb: import('better-sqlite3').Database) {\n this._db = sqliteDb;\n // Best-effort initial value (may be stale from a previous session).\n // First tool-call access will re-query after CHECK_INTERVAL_MS (0ms elapsed\n // since _lastChecked starts at 0, so the very first .current triggers a refresh).\n this._current = this.resolve();\n }\n\n get current(): string {\n const now = Date.now();\n if (now - this._lastChecked >= LiveProjectHashRef.CHECK_INTERVAL_MS) {\n this._lastChecked = now;\n const fresh = this.resolve();\n if (fresh !== this._current) {\n debug('mcp', 'Project hash refreshed from database', { old: this._current, new: fresh });\n this._current = fresh;\n }\n }\n return this._current;\n }\n\n private resolve(): string {\n try {\n const row = this._db.prepare(\n 'SELECT project_hash FROM project_metadata ORDER BY last_seen_at DESC LIMIT 1',\n ).get() as { project_hash: string } | undefined;\n if (row?.project_hash) return row.project_hash;\n } catch {\n // Table may not exist yet on first run\n }\n return getProjectHash(process.cwd());\n }\n}\n\nconst projectHashRef: ProjectHashRef = new LiveProjectHashRef(db.db);\n\n// Tool registry (cross-project, scope-aware)\nlet toolRegistry: ToolRegistryRepository | null = null;\ntry {\n toolRegistry = new ToolRegistryRepository(db.db);\n} catch {\n debug('mcp', 'Tool registry not available (pre-migration-16)');\n}\n\n// ---------------------------------------------------------------------------\n// Worker thread and embedding store (graceful degradation)\n// ---------------------------------------------------------------------------\n\nconst embeddingStore = db.hasVectorSupport\n ? new EmbeddingStore(db.db, projectHashRef.current)\n : null;\n\nconst worker = new AnalysisWorker();\n\n// Start worker in background -- do NOT await during server startup (DQ-04)\nconst workerReady = worker.start().catch(() => {\n debug('mcp', 'Worker failed to start, keyword-only mode');\n});\n\n// Suppress unhandled rejection from workerReady (already handled above)\nvoid workerReady;\n\n// ---------------------------------------------------------------------------\n// Topic shift detection (runs in background embedding loop)\n// ---------------------------------------------------------------------------\n\nconst topicConfig = loadTopicDetectionConfig();\nconst graphConfig = loadGraphExtractionConfig();\nconst detector = new TopicShiftDetector();\nconst adaptiveManager = new AdaptiveThresholdManager({\n sensitivityMultiplier: topicConfig.sensitivityMultiplier,\n alpha: topicConfig.ewmaAlpha,\n});\napplyConfig(topicConfig, detector, adaptiveManager);\n\n// Seed adaptive threshold from history (cold start handling)\nconst thresholdStore = new ThresholdStore(db.db);\nconst historicalSeed = thresholdStore.loadHistoricalSeed(projectHashRef.current);\nif (historicalSeed) {\n adaptiveManager.seedFromHistory(historicalSeed.averageDistance, historicalSeed.averageVariance);\n applyConfig(topicConfig, detector, adaptiveManager);\n}\n\nconst stashManager = new StashManager(db.db);\nconst decisionLogger = new TopicShiftDecisionLogger(db.db);\nconst notificationStore = new NotificationStore(db.db);\nconst obsRepoForTopicDetection = new ObservationRepository(db.db, projectHashRef.current);\n\nconst topicShiftHandler = new TopicShiftHandler({\n detector,\n stashManager,\n observationStore: obsRepoForTopicDetection,\n config: topicConfig,\n decisionLogger,\n adaptiveManager,\n});\n\n// ---------------------------------------------------------------------------\n// Background embedding loop\n// ---------------------------------------------------------------------------\n\n// Sources that reflect user-directed work (for topic shift detection).\n// Exploration tools (Read/Glob/Grep/Task) are excluded to avoid false shifts.\nconst TOPIC_SHIFT_SOURCES = new Set(['hook:Write', 'hook:Edit', 'hook:Bash', 'manual']);\n\nasync function processUnembedded(): Promise<void> {\n if (!embeddingStore || !worker.isReady()) return;\n\n const ids = embeddingStore.findUnembedded(10);\n if (ids.length === 0) return;\n\n const currentHash = projectHashRef.current;\n const obsRepo = new ObservationRepository(db.db, currentHash);\n\n // At most one topic shift notification per processing cycle\n let shiftDetectedThisCycle = false;\n\n for (const id of ids) {\n const obs = obsRepo.getById(id);\n if (!obs) continue;\n\n const text = obs.title ? `${obs.title}\\n${obs.content}` : obs.content;\n const embedding = await worker.embed(text);\n\n if (embedding) {\n embeddingStore.store(id, embedding);\n obsRepo.update(id, {\n embeddingModel: worker.getEngineName(),\n embeddingVersion: '1',\n });\n\n // Broadcast new observation to SSE clients (minimal payload)\n const truncatedText = obs.content.length > 120\n ? obs.content.substring(0, 120) + '...'\n : obs.content;\n broadcast('new_observation', {\n id,\n text: truncatedText,\n sessionId: obs.sessionId ?? null,\n createdAt: obs.createdAt,\n projectHash: currentHash,\n });\n\n // Topic shift detection -- only evaluate user-directed observations\n // (Write/Edit/Bash reflect user intent; Read/Glob/Grep are exploration noise)\n // Only one shift notification per processing cycle to avoid spam.\n if (topicConfig.enabled && !shiftDetectedThisCycle && TOPIC_SHIFT_SOURCES.has(obs.source)) {\n try {\n // Build the observation with its newly generated embedding\n const obsWithEmbedding = { ...obs, embedding };\n const result = await topicShiftHandler.handleObservation(\n obsWithEmbedding,\n obs.sessionId ?? 'unknown',\n currentHash,\n );\n if (result.stashed && result.notification) {\n shiftDetectedThisCycle = true;\n notificationStore.add(currentHash, result.notification);\n debug('embed', 'Topic shift detected, notification queued', { id });\n\n // Broadcast topic shift to SSE clients\n broadcast('topic_shift', {\n id: result.notification.substring(0, 32),\n fromTopic: null,\n toTopic: null,\n timestamp: new Date().toISOString(),\n confidence: null,\n projectHash: currentHash,\n });\n }\n } catch (topicErr) {\n const msg = topicErr instanceof Error ? topicErr.message : String(topicErr);\n debug('embed', 'Topic shift detection error (non-fatal)', { error: msg });\n }\n }\n\n // Knowledge graph extraction is now handled by HaikuProcessor in the background.\n // The embedding loop only handles: embeddings, SSE broadcast, topic shift detection.\n }\n }\n}\n\n// Research buffer instance for periodic flush\nlet researchBufferForFlush: ResearchBufferRepository | null = null;\ntry {\n researchBufferForFlush = new ResearchBufferRepository(db.db, projectHashRef.current);\n} catch {\n // Table may not exist yet\n}\n\n// Background tool description embedding (enables semantic tool search)\nasync function processUnembeddedTools(): Promise<void> {\n if (!toolRegistry || !worker.isReady() || !db.hasVectorSupport) return;\n\n try {\n const unembedded = toolRegistry.findUnembeddedTools(5);\n for (const tool of unembedded) {\n const text = `${tool.name} ${tool.description}`;\n const embedding = await worker.embed(text);\n if (embedding) {\n toolRegistry.storeEmbedding(tool.id, embedding);\n }\n }\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n debug('embed', 'Tool embedding error (non-fatal)', { error: msg });\n }\n}\n\n// Process unembedded observations every 5 seconds\nconst embedTimer = setInterval(() => {\n processUnembedded().catch((err) => {\n const message = err instanceof Error ? err.message : String(err);\n debug('embed', 'Background embedding error', { error: message });\n });\n processUnembeddedTools().catch((err) => {\n const message = err instanceof Error ? err.message : String(err);\n debug('embed', 'Tool embedding background error', { error: message });\n });\n // Flush old research buffer entries (older than 30 minutes)\n try {\n researchBufferForFlush?.flush(30);\n } catch {\n // Non-fatal\n }\n // Refresh status cache if data changed since last build\n statusCache.refreshIfDirty();\n}, 5000);\n\n// ---------------------------------------------------------------------------\n// MCP server setup\n// ---------------------------------------------------------------------------\n\nconst statusCache = new StatusCache(\n db.db, projectHashRef, process.cwd(), db.hasVectorSupport, () => worker.isReady(),\n);\n\nconst server = createServer();\nregisterSaveMemory(server, db.db, projectHashRef, notificationStore, worker, embeddingStore, statusCache);\nregisterIngestKnowledge(server, db.db, projectHashRef, notificationStore, statusCache);\nregisterRecall(server, db.db, projectHashRef, worker, embeddingStore, notificationStore, statusCache);\nregisterTopicContext(server, db.db, projectHashRef, notificationStore);\nregisterQueryGraph(server, db.db, projectHashRef, notificationStore);\nregisterGraphStats(server, db.db, projectHashRef, notificationStore);\nregisterHygiene(server, db.db, projectHashRef, notificationStore);\nregisterStatus(server, statusCache, projectHashRef, notificationStore);\nif (toolRegistry) {\n registerDiscoverTools(server, toolRegistry, worker, db.hasVectorSupport, notificationStore, projectHashRef);\n registerReportTools(server, toolRegistry, projectHashRef);\n}\n\n// ---------------------------------------------------------------------------\n// Background Haiku processor (classification + entity extraction + relationships)\n// ---------------------------------------------------------------------------\n\nconst pathRepo = new PathRepository(db.db, projectHashRef.current);\nconst pathTracker = new PathTracker(pathRepo);\nregisterDebugPathTools(server, pathRepo, pathTracker, notificationStore, projectHashRef);\n\n// Branch tracking (thought branch auto-detection)\nlet branchRepo: BranchRepository | null = null;\nlet branchTracker: BranchTracker | undefined;\ntry {\n branchRepo = new BranchRepository(db.db, projectHashRef.current);\n branchTracker = new BranchTracker(branchRepo, db.db, projectHashRef.current);\n const obsRepoForBranches = new ObservationRepository(db.db, projectHashRef.current);\n registerThoughtBranchTools(server, branchRepo, obsRepoForBranches, notificationStore, projectHashRef);\n} catch {\n debug('mcp', 'Branch tracking not available (pre-migration-21)');\n}\n\nconst haikuProcessor = new HaikuProcessor(db.db, projectHashRef.current, {\n intervalMs: 30_000,\n batchSize: 10,\n concurrency: 3,\n pathTracker,\n branchTracker,\n});\n\nstartServer(server).then(() => {\n haikuProcessor.start();\n}).catch((err) => {\n debug('mcp', 'Fatal: failed to start server', { error: err.message });\n clearInterval(embedTimer);\n db.close();\n process.exit(1);\n});\n\n// ---------------------------------------------------------------------------\n// Web visualization server (runs alongside MCP server)\n// ---------------------------------------------------------------------------\n\nif (!noGui) {\n const webPort = parseInt(process.env.LAMINARK_WEB_PORT || '37820', 10);\n const __filename = fileURLToPath(import.meta.url);\n const __dirname = path.dirname(__filename);\n const uiRoot = path.resolve(__dirname, '..', 'ui');\n const webApp = createWebServer(db.db, uiRoot, projectHashRef.current);\n startWebServer(webApp, webPort);\n} else {\n debug('mcp', 'Web UI disabled (--no_gui)');\n}\n\n// ---------------------------------------------------------------------------\n// Background curation agent (graph maintenance)\n// ---------------------------------------------------------------------------\n\nconst curationAgent = new CurationAgent(db.db, {\n intervalMs: 5 * 60 * 1000, // 5 minutes\n graphConfig,\n onComplete: (report) => {\n debug('db', 'Curation complete', {\n merged: report.observationsMerged,\n deduped: report.entitiesDeduplicated,\n stale: report.stalenessFlagsAdded,\n pruned: report.lowValuePruned,\n decayed: report.temporalDecayUpdated,\n decayDeleted: report.temporalDecayDeleted,\n });\n statusCache.markDirty();\n },\n});\ncurationAgent.start();\n\n// ---------------------------------------------------------------------------\n// Shutdown handlers\n// ---------------------------------------------------------------------------\n\nfunction shutdown(code: number): void {\n clearInterval(embedTimer);\n haikuProcessor.stop();\n curationAgent.stop();\n worker.shutdown().catch(() => {});\n db.close();\n process.exit(code);\n}\n\nprocess.on('SIGINT', () => shutdown(0));\nprocess.on('SIGTERM', () => shutdown(0));\nprocess.on('uncaughtException', (err) => {\n debug('mcp', 'Uncaught exception', { error: err.message });\n shutdown(1);\n});\n"],"mappings":"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"}
1
+ {"version":3,"file":"index.js","names":["DEFAULTS","DEFAULTS","prependNotifications","textResponse","errorResponse","prependNotifications","textResponse","prependNotifications","textResponse","errorResponse","prependNotifications","textResponse","prependNotifications","textResponse","prependNotifications","textResponse","textResponse","errorResponse","prependNotifications","textResponse","prependNotifications","textResponse","errorResponse","fileURLToPath","DEFAULTS","DEFAULTS","jaccardSimilarity","SYSTEM_PROMPT","SYSTEM_PROMPT","SYSTEM_PROMPT","getDb","getProjectHash","fileURLToPath","getProjectHash"],"sources":["../../src/storage/embeddings.ts","../../src/storage/stash-manager.ts","../../src/storage/threshold-store.ts","../../src/mcp/server.ts","../../src/config/cross-access.ts","../../src/mcp/token-budget.ts","../../src/config/tool-verbosity-config.ts","../../src/mcp/tools/recall.ts","../../src/mcp/tools/save-memory.ts","../../src/ingestion/markdown-parser.ts","../../src/ingestion/knowledge-ingester.ts","../../src/mcp/tools/ingest-knowledge.ts","../../src/commands/resume.ts","../../src/mcp/tools/topic-context.ts","../../src/graph/types.ts","../../src/mcp/tools/query-graph.ts","../../src/mcp/tools/graph-stats.ts","../../src/mcp/tools/hygiene.ts","../../src/mcp/tools/status.ts","../../src/mcp/status-cache.ts","../../src/mcp/tools/discover-tools.ts","../../src/mcp/tools/report-tools.ts","../../src/mcp/tools/debug-paths.ts","../../src/mcp/tools/thought-branches.ts","../../src/branches/arc-detector.ts","../../src/config/haiku-config.ts","../../src/intelligence/haiku-client.ts","../../src/branches/branch-classifier-agent.ts","../../src/branches/branch-tracker.ts","../../src/analysis/worker-bridge.ts","../../src/hooks/topic-shift-handler.ts","../../src/intelligence/topic-detector.ts","../../src/intelligence/adaptive-threshold.ts","../../src/intelligence/decision-logger.ts","../../src/config/topic-detection-config.ts","../../src/config/graph-extraction-config.ts","../../src/graph/observation-merger.ts","../../src/graph/fuzzy-dedup.ts","../../src/graph/constraints.ts","../../src/graph/temporal-decay.ts","../../src/graph/curation-agent.ts","../../src/intelligence/haiku-classifier-agent.ts","../../src/intelligence/haiku-entity-agent.ts","../../src/intelligence/haiku-relationship-agent.ts","../../src/graph/write-quality-gate.ts","../../src/web/routes/sse.ts","../../src/intelligence/haiku-processor.ts","../../src/paths/kiss-summary-agent.ts","../../src/paths/path-tracker.ts","../../src/web/routes/api.ts","../../src/web/routes/admin.ts","../../src/web/server.ts","../../src/index.ts"],"sourcesContent":["/**\n * EmbeddingStore for sqlite-vec vec0 table operations.\n *\n * Provides store/search/delete/has/findUnembedded methods against the\n * cosine-distance vec0 table (observation_embeddings). All operations\n * are project-scoped via subquery join on the observations table.\n *\n * Float32Array passes directly to better-sqlite3 for vec0 operations\n * (per research Finding 7 -- no Buffer conversion needed).\n */\n\nimport type BetterSqlite3 from 'better-sqlite3';\n\nimport { debug } from '../shared/debug.js';\n\n/** A single search result with observation ID and cosine distance. */\nexport interface EmbeddingSearchResult {\n observationId: string;\n distance: number;\n}\n\n/**\n * Data layer for vector insert/query against the cosine-distance vec0 table.\n *\n * All methods catch errors internally and return empty/default values for\n * graceful degradation (DQ-03). Uses debug('embed', ...) logging.\n */\nexport class EmbeddingStore {\n private stmtInsert: BetterSqlite3.Statement;\n private stmtSearch: BetterSqlite3.Statement;\n private stmtDelete: BetterSqlite3.Statement;\n private stmtExists: BetterSqlite3.Statement;\n private stmtFindUnembedded: BetterSqlite3.Statement;\n\n constructor(\n private db: BetterSqlite3.Database,\n private projectHash: string,\n ) {\n this.stmtInsert = db.prepare(\n 'INSERT OR REPLACE INTO observation_embeddings(observation_id, embedding) VALUES (?, ?)',\n );\n\n this.stmtSearch = db.prepare(`\n SELECT observation_id, distance\n FROM observation_embeddings\n WHERE embedding MATCH ?\n AND observation_id IN (\n SELECT id FROM observations WHERE project_hash = ? AND deleted_at IS NULL\n )\n ORDER BY distance\n LIMIT ?\n `);\n\n this.stmtDelete = db.prepare(\n 'DELETE FROM observation_embeddings WHERE observation_id = ?',\n );\n\n this.stmtExists = db.prepare(\n 'SELECT 1 FROM observation_embeddings WHERE observation_id = ?',\n );\n\n this.stmtFindUnembedded = db.prepare(`\n SELECT id FROM observations\n WHERE project_hash = ?\n AND deleted_at IS NULL\n AND id NOT IN (SELECT observation_id FROM observation_embeddings)\n LIMIT ?\n `);\n }\n\n /**\n * Stores an embedding for an observation.\n *\n * Uses INSERT OR REPLACE so re-embedding an observation overwrites\n * the old vector.\n */\n store(observationId: string, embedding: Float32Array): void {\n try {\n this.stmtInsert.run(observationId, embedding);\n debug('embed', 'Stored embedding', { observationId, dimensions: embedding.length });\n } catch (err) {\n debug('embed', 'Failed to store embedding', {\n observationId,\n error: String(err),\n });\n }\n }\n\n /**\n * Project-scoped KNN search using cosine distance.\n *\n * Returns the nearest observations ordered by distance (ascending).\n * Only returns observations belonging to this store's project that\n * have not been soft-deleted.\n */\n search(queryEmbedding: Float32Array, limit = 20): EmbeddingSearchResult[] {\n try {\n const rows = this.stmtSearch.all(\n queryEmbedding,\n this.projectHash,\n limit,\n ) as Array<{ observation_id: string; distance: number }>;\n\n debug('embed', 'Search completed', {\n results: rows.length,\n limit,\n });\n\n return rows.map((row) => ({\n observationId: row.observation_id,\n distance: row.distance,\n }));\n } catch (err) {\n debug('embed', 'Search failed', { error: String(err) });\n return [];\n }\n }\n\n /**\n * Removes the embedding for a deleted observation.\n */\n delete(observationId: string): void {\n try {\n this.stmtDelete.run(observationId);\n debug('embed', 'Deleted embedding', { observationId });\n } catch (err) {\n debug('embed', 'Failed to delete embedding', {\n observationId,\n error: String(err),\n });\n }\n }\n\n /**\n * Checks if an observation has an embedding stored.\n */\n has(observationId: string): boolean {\n try {\n const row = this.stmtExists.get(observationId);\n return row !== undefined;\n } catch (err) {\n debug('embed', 'Failed to check embedding existence', {\n observationId,\n error: String(err),\n });\n return false;\n }\n }\n\n /**\n * Finds observation IDs that need embeddings generated.\n *\n * Returns IDs of observations belonging to this project that are\n * not soft-deleted and have no entry in the embeddings table.\n */\n findUnembedded(limit = 50): string[] {\n try {\n const rows = this.stmtFindUnembedded.all(\n this.projectHash,\n limit,\n ) as Array<{ id: string }>;\n\n debug('embed', 'Found unembedded observations', { count: rows.length, limit });\n\n return rows.map((row) => row.id);\n } catch (err) {\n debug('embed', 'Failed to find unembedded observations', {\n error: String(err),\n });\n return [];\n }\n }\n}\n","import type BetterSqlite3 from 'better-sqlite3';\nimport { randomBytes } from 'node:crypto';\n\nimport { debug } from '../shared/debug.js';\nimport type {\n ContextStash,\n StashObservation,\n CreateStashInput,\n} from '../types/stash.js';\n\n/**\n * Raw context_stashes row from SQLite (snake_case column names).\n */\ninterface StashRow {\n id: string;\n project_id: string;\n session_id: string;\n topic_label: string;\n summary: string;\n observation_snapshots: string; // JSON string\n observation_ids: string; // JSON string\n status: string;\n created_at: string;\n resumed_at: string | null;\n}\n\n/**\n * Maps a snake_case StashRow to a camelCase ContextStash interface.\n * JSON-parses observation_snapshots and observation_ids from their\n * serialized TEXT column format back into arrays.\n */\nfunction rowToStash(row: StashRow): ContextStash {\n return {\n id: row.id,\n projectId: row.project_id,\n sessionId: row.session_id,\n topicLabel: row.topic_label,\n summary: row.summary,\n observationIds: JSON.parse(row.observation_ids) as string[],\n observationSnapshots: JSON.parse(\n row.observation_snapshots,\n ) as StashObservation[],\n createdAt: row.created_at,\n resumedAt: row.resumed_at,\n status: row.status as ContextStash['status'],\n };\n}\n\n/**\n * Repository for context stash CRUD operations.\n *\n * Manages the lifecycle of stashed context threads: creating snapshots\n * when topic shifts are detected, listing available stashes, retrieving\n * full stash records, resuming stashes, and deleting them.\n *\n * All SQL statements are prepared once in the constructor and reused\n * for every call (better-sqlite3 performance best practice).\n */\nexport class StashManager {\n private readonly db: BetterSqlite3.Database;\n\n // Prepared statements\n private readonly stmtInsert: BetterSqlite3.Statement;\n private readonly stmtGetById: BetterSqlite3.Statement;\n private readonly stmtResume: BetterSqlite3.Statement;\n private readonly stmtDelete: BetterSqlite3.Statement;\n\n constructor(db: BetterSqlite3.Database) {\n this.db = db;\n\n this.stmtInsert = db.prepare(`\n INSERT INTO context_stashes (id, project_id, session_id, topic_label, summary, observation_snapshots, observation_ids, status)\n VALUES (?, ?, ?, ?, ?, ?, ?, 'stashed')\n `);\n\n this.stmtGetById = db.prepare(`\n SELECT * FROM context_stashes WHERE id = ?\n `);\n\n this.stmtResume = db.prepare(`\n UPDATE context_stashes\n SET status = 'resumed', resumed_at = datetime('now')\n WHERE id = ?\n `);\n\n this.stmtDelete = db.prepare(`\n DELETE FROM context_stashes WHERE id = ?\n `);\n\n debug('db', 'StashManager initialized');\n }\n\n /**\n * Creates a new stash record from a context thread snapshot.\n * JSON-serializes observation snapshots and IDs for TEXT column storage.\n * Uses randomBytes(16) hex for ID generation (matches ObservationRepository pattern).\n */\n createStash(input: CreateStashInput): ContextStash {\n const id = randomBytes(16).toString('hex');\n const observationIds = input.observations.map((o) => o.id);\n const snapshotsJson = JSON.stringify(input.observations);\n const idsJson = JSON.stringify(observationIds);\n\n debug('db', 'Creating stash', {\n topicLabel: input.topicLabel,\n observationCount: input.observations.length,\n });\n\n this.stmtInsert.run(\n id,\n input.projectId,\n input.sessionId,\n input.topicLabel,\n input.summary,\n snapshotsJson,\n idsJson,\n );\n\n const row = this.stmtGetById.get(id) as StashRow | undefined;\n if (!row) {\n throw new Error('Failed to retrieve newly created stash');\n }\n\n debug('db', 'Stash created', { id });\n\n return rowToStash(row);\n }\n\n /**\n * Lists stashes for a project, ordered by created_at DESC.\n * Supports optional filtering by session_id and status.\n */\n listStashes(\n projectId: string,\n options?: { sessionId?: string; status?: string; limit?: number },\n ): ContextStash[] {\n const limit = options?.limit ?? 10;\n\n let sql =\n 'SELECT * FROM context_stashes WHERE project_id = ?';\n const params: unknown[] = [projectId];\n\n if (options?.sessionId) {\n sql += ' AND session_id = ?';\n params.push(options.sessionId);\n }\n\n if (options?.status) {\n sql += ' AND status = ?';\n params.push(options.status);\n }\n\n sql += ' ORDER BY created_at DESC LIMIT ?';\n params.push(limit);\n\n debug('db', 'Listing stashes', { projectId, ...options });\n\n const rows = this.db.prepare(sql).all(...params) as StashRow[];\n return rows.map(rowToStash);\n }\n\n /**\n * Retrieves a single stash by ID with full observation snapshot data.\n * Returns null for nonexistent IDs.\n */\n getStash(id: string): ContextStash | null {\n const row = this.stmtGetById.get(id) as StashRow | undefined;\n return row ? rowToStash(row) : null;\n }\n\n /**\n * Marks a stash as resumed and sets resumed_at timestamp.\n * Returns the updated record.\n * Throws if the stash does not exist.\n */\n resumeStash(id: string): ContextStash {\n const result = this.stmtResume.run(id);\n\n if (result.changes === 0) {\n throw new Error(`Stash not found: ${id}`);\n }\n\n debug('db', 'Stash resumed', { id });\n\n const row = this.stmtGetById.get(id) as StashRow | undefined;\n if (!row) {\n throw new Error(`Failed to retrieve resumed stash: ${id}`);\n }\n\n return rowToStash(row);\n }\n\n /**\n * Hard-deletes a stash record.\n */\n deleteStash(id: string): void {\n this.stmtDelete.run(id);\n debug('db', 'Stash deleted', { id });\n }\n\n /**\n * Returns stashes with status='stashed' (excludes resumed) for a project,\n * ordered by created_at DESC.\n */\n getRecentStashes(projectId: string, limit?: number): ContextStash[] {\n return this.listStashes(projectId, {\n status: 'stashed',\n limit: limit ?? 10,\n });\n }\n}\n","import type BetterSqlite3 from 'better-sqlite3';\n\nimport { debug } from '../shared/debug.js';\nimport type { ThresholdState } from '../intelligence/adaptive-threshold.js';\n\n/**\n * Result of loading historical seed data for cold start.\n */\nexport interface HistoricalSeed {\n /** Average EWMA distance across recent sessions */\n averageDistance: number;\n /** Average EWMA variance across recent sessions */\n averageVariance: number;\n}\n\n/**\n * Persists and loads EWMA threshold history for session seeding.\n *\n * At the end of each session, the final EWMA state is saved via\n * saveSessionThreshold(). When a new session starts, loadHistoricalSeed()\n * computes averages from the last 10 sessions to bootstrap the EWMA\n * without cold-start problems.\n *\n * All SQL statements are prepared once in the constructor and reused\n * for every call (better-sqlite3 performance best practice).\n */\nexport class ThresholdStore {\n private readonly db: BetterSqlite3.Database;\n\n // Prepared statements\n private readonly stmtInsert: BetterSqlite3.Statement;\n private readonly stmtLoadSeed: BetterSqlite3.Statement;\n\n constructor(db: BetterSqlite3.Database) {\n this.db = db;\n\n this.stmtInsert = db.prepare(`\n INSERT INTO threshold_history (project_id, session_id, final_ewma_distance, final_ewma_variance, observation_count)\n VALUES (?, ?, ?, ?, ?)\n `);\n\n this.stmtLoadSeed = db.prepare(`\n SELECT\n AVG(final_ewma_distance) AS avg_distance,\n AVG(final_ewma_variance) AS avg_variance\n FROM (\n SELECT final_ewma_distance, final_ewma_variance\n FROM threshold_history\n WHERE project_id = ?\n ORDER BY created_at DESC\n LIMIT 10\n )\n `);\n\n debug('db', 'ThresholdStore initialized');\n }\n\n /**\n * Persist the final EWMA state of a session for future seeding.\n */\n saveSessionThreshold(\n projectId: string,\n sessionId: string,\n state: ThresholdState,\n ): void {\n this.stmtInsert.run(\n projectId,\n sessionId,\n state.ewmaDistance,\n state.ewmaVariance,\n state.observationCount,\n );\n\n debug('db', 'Threshold saved', {\n projectId,\n sessionId,\n ewmaDistance: state.ewmaDistance,\n observations: state.observationCount,\n });\n }\n\n /**\n * Load historical seed by averaging the last 10 sessions for a project.\n *\n * Returns null if no history exists for this project.\n */\n loadHistoricalSeed(projectId: string): HistoricalSeed | null {\n const row = this.stmtLoadSeed.get(projectId) as {\n avg_distance: number | null;\n avg_variance: number | null;\n };\n\n if (row.avg_distance === null || row.avg_variance === null) {\n debug('db', 'No threshold history found', { projectId });\n return null;\n }\n\n debug('db', 'Threshold seed loaded', {\n projectId,\n avgDistance: row.avg_distance,\n avgVariance: row.avg_variance,\n });\n\n return {\n averageDistance: row.avg_distance,\n averageVariance: row.avg_variance,\n };\n }\n}\n","import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';\n\nimport { debug } from '../shared/debug.js';\n\nexport function createServer(): McpServer {\n return new McpServer(\n { name: 'laminark', version: '0.1.0' },\n { capabilities: { tools: {} } },\n );\n}\n\nexport async function startServer(server: McpServer): Promise<void> {\n const transport = new StdioServerTransport();\n await server.connect(transport);\n debug('mcp', 'MCP server started on stdio transport');\n}\n","/**\n * Cross-Project Access Configuration\n *\n * Per-project config that controls which other projects' memories\n * the current project can read from. Read-only access — no writes\n * cross projects.\n *\n * Config stored at: {configDir}/cross-access-{projectHash}.json\n */\n\nimport { existsSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs';\nimport { join } from 'node:path';\n\nimport { getConfigDir } from '../shared/config.js';\n\nexport interface CrossAccessConfig {\n readableProjects: string[];\n}\n\nconst DEFAULTS: CrossAccessConfig = {\n readableProjects: [],\n};\n\nfunction getConfigPath(projectHash: string): string {\n return join(getConfigDir(), `cross-access-${projectHash}.json`);\n}\n\nexport function loadCrossAccessConfig(projectHash: string): CrossAccessConfig {\n const configPath = getConfigPath(projectHash);\n try {\n if (!existsSync(configPath)) return { ...DEFAULTS };\n const raw = readFileSync(configPath, 'utf-8');\n const parsed = JSON.parse(raw) as Partial<CrossAccessConfig>;\n return {\n readableProjects: Array.isArray(parsed.readableProjects)\n ? parsed.readableProjects.filter((h): h is string => typeof h === 'string')\n : [],\n };\n } catch {\n return { ...DEFAULTS };\n }\n}\n\nexport function saveCrossAccessConfig(projectHash: string, config: CrossAccessConfig): void {\n const configPath = getConfigPath(projectHash);\n const validated: CrossAccessConfig = {\n readableProjects: Array.isArray(config.readableProjects)\n ? config.readableProjects.filter((h): h is string => typeof h === 'string' && h !== projectHash)\n : [],\n };\n writeFileSync(configPath, JSON.stringify(validated, null, 2), 'utf-8');\n}\n\nexport function resetCrossAccessConfig(projectHash: string): void {\n const configPath = getConfigPath(projectHash);\n try {\n if (existsSync(configPath)) unlinkSync(configPath);\n } catch { /* ignore */ }\n}\n","export const TOKEN_BUDGET = 2000;\nexport const FULL_VIEW_BUDGET = 4000;\n\nexport function estimateTokens(text: string): number {\n return Math.ceil(text.length / 4);\n}\n\nexport function enforceTokenBudget<T>(\n results: T[],\n formatResult: (item: T) => string,\n budget: number = TOKEN_BUDGET,\n): { items: T[]; truncated: boolean; tokenEstimate: number } {\n const METADATA_RESERVE = 100;\n const effectiveBudget = budget - METADATA_RESERVE;\n let totalTokens = 0;\n const items: T[] = [];\n\n for (const result of results) {\n const formatted = formatResult(result);\n const tokens = estimateTokens(formatted);\n if (totalTokens + tokens > effectiveBudget && items.length > 0) {\n return { items, truncated: true, tokenEstimate: totalTokens };\n }\n items.push(result);\n totalTokens += tokens;\n }\n\n return { items, truncated: false, tokenEstimate: totalTokens };\n}\n","/**\n * Tool Response Verbosity Configuration\n *\n * Controls how much detail MCP tool responses include.\n * Three levels:\n * 1 (minimal): Just confirms the tool ran\n * 2 (standard): Shows title/key info (default)\n * 3 (verbose): Full formatted text with all details\n *\n * Configuration is loaded from .laminark/tool-verbosity.json with\n * a 5-second cache to avoid repeated disk reads.\n */\n\nimport { readFileSync, writeFileSync } from 'node:fs';\nimport { join } from 'node:path';\n\nimport { debug } from '../shared/debug.js';\nimport { getConfigDir } from '../shared/config.js';\n\nexport type VerbosityLevel = 1 | 2 | 3;\n\nexport interface ToolVerbosityConfig {\n level: VerbosityLevel;\n}\n\nconst DEFAULTS: ToolVerbosityConfig = { level: 2 };\nconst CACHE_TTL_MS = 5000;\n\nlet cachedConfig: ToolVerbosityConfig | null = null;\nlet cachedAt = 0;\n\n/**\n * Loads tool verbosity configuration from disk with a 5-second cache.\n */\nexport function loadToolVerbosityConfig(): ToolVerbosityConfig {\n const now = Date.now();\n if (cachedConfig && now - cachedAt < CACHE_TTL_MS) {\n return cachedConfig;\n }\n\n const configPath = join(getConfigDir(), 'tool-verbosity.json');\n try {\n const content = readFileSync(configPath, 'utf-8');\n const raw = JSON.parse(content) as Record<string, unknown>;\n const level = raw.level;\n if (level === 1 || level === 2 || level === 3) {\n cachedConfig = { level };\n } else {\n cachedConfig = { ...DEFAULTS };\n }\n debug('config', 'Loaded tool verbosity config', { level: cachedConfig.level });\n } catch {\n cachedConfig = { ...DEFAULTS };\n }\n\n cachedAt = now;\n return cachedConfig;\n}\n\n/**\n * Saves tool verbosity configuration to disk and invalidates cache.\n */\nexport function saveToolVerbosityConfig(config: ToolVerbosityConfig): void {\n const configPath = join(getConfigDir(), 'tool-verbosity.json');\n writeFileSync(configPath, JSON.stringify(config, null, 2), 'utf-8');\n cachedConfig = config;\n cachedAt = Date.now();\n}\n\n/**\n * Resets tool verbosity to defaults by invalidating cache.\n */\nexport function resetToolVerbosityConfig(): ToolVerbosityConfig {\n cachedConfig = null;\n cachedAt = 0;\n return { ...DEFAULTS };\n}\n\n/**\n * Selects the appropriate response text based on the current verbosity level.\n *\n * Each tool passes three pre-built strings:\n * - minimal: Level 1 — just confirms the tool ran\n * - standard: Level 2 — shows title/key info\n * - verbose: Level 3 — full formatted text\n */\nexport function formatResponse(\n level: VerbosityLevel,\n minimal: string,\n standard: string,\n verbose: string,\n): string {\n switch (level) {\n case 1: return minimal;\n case 2: return standard;\n case 3: return verbose;\n }\n}\n\n/**\n * Convenience: loads config and selects the response in one call.\n */\nexport function verboseResponse(\n minimal: string,\n standard: string,\n verbose: string,\n): string {\n const { level } = loadToolVerbosityConfig();\n return formatResponse(level, minimal, standard, verbose);\n}\n","import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type BetterSqlite3 from 'better-sqlite3';\nimport { z } from 'zod';\n\nimport { debug } from '../../shared/debug.js';\nimport type { Observation, SearchResult } from '../../shared/types.js';\nimport { ObservationRepository } from '../../storage/observations.js';\nimport { SearchEngine } from '../../storage/search.js';\nimport type { EmbeddingStore } from '../../storage/embeddings.js';\nimport type { AnalysisWorker } from '../../analysis/worker-bridge.js';\nimport type { NotificationStore } from '../../storage/notifications.js';\nimport { hybridSearch } from '../../search/hybrid.js';\nimport type { ProjectHashRef } from '../../shared/types.js';\nimport { loadCrossAccessConfig } from '../../config/cross-access.js';\nimport {\n enforceTokenBudget,\n estimateTokens,\n FULL_VIEW_BUDGET,\n TOKEN_BUDGET,\n} from '../token-budget.js';\nimport type { StatusCache } from '../status-cache.js';\nimport { loadToolVerbosityConfig } from '../../config/tool-verbosity-config.js';\n\n// ---------------------------------------------------------------------------\n// Formatting helpers\n// ---------------------------------------------------------------------------\n\nfunction shortId(id: string): string {\n return id.slice(0, 8);\n}\n\nfunction dateStr(iso: string): string {\n return iso.slice(0, 10); // YYYY-MM-DD\n}\n\nfunction timeStr(iso: string): string {\n return iso.slice(11, 16); // HH:MM\n}\n\nfunction snippetText(content: string, maxLen: number): string {\n return content.replace(/\\n/g, ' ').slice(0, maxLen);\n}\n\n// ---------------------------------------------------------------------------\n// Detail-level formatters\n// ---------------------------------------------------------------------------\n\nfunction formatCompactItem(\n obs: Observation,\n index: number,\n score?: number,\n): string {\n const idShort = shortId(obs.id);\n const title = obs.title ?? 'untitled';\n const scoreStr = score !== undefined ? score.toFixed(2) : '-';\n const snippet = snippetText(obs.content, 100);\n const date = dateStr(obs.createdAt);\n return `[${index}] ${idShort} | ${title} | ${scoreStr} | ${snippet} | ${date}`;\n}\n\nfunction formatTimelineGroup(\n date: string,\n items: { obs: Observation; score?: number }[],\n): string {\n const lines = [`## ${date}`];\n for (const { obs } of items) {\n const time = timeStr(obs.createdAt);\n const title = obs.title ?? 'untitled';\n const source = obs.source;\n const snippet = snippetText(obs.content, 150);\n lines.push(`${time} | ${title} | ${source} | ${snippet}`);\n }\n return lines.join('\\n');\n}\n\nfunction formatFullItem(obs: Observation): string {\n const idShort = shortId(obs.id);\n const title = obs.title ?? 'untitled';\n return `--- ${idShort} | ${title} | ${obs.createdAt} ---\\n${obs.content}`;\n}\n\n// ---------------------------------------------------------------------------\n// Response helpers\n// ---------------------------------------------------------------------------\n\nfunction prependNotifications(\n notificationStore: NotificationStore | null,\n projectHash: string,\n responseText: string,\n): string {\n if (!notificationStore) return responseText;\n const pending = notificationStore.consumePending(projectHash);\n if (pending.length === 0) return responseText;\n const banner = pending.map(n => `[Laminark] ${n.message}`).join('\\n');\n return banner + '\\n\\n' + responseText;\n}\n\nfunction textResponse(text: string) {\n return { content: [{ type: 'text' as const, text }] };\n}\n\nfunction errorResponse(text: string) {\n return { content: [{ type: 'text' as const, text }], isError: true };\n}\n\n// ---------------------------------------------------------------------------\n// Cross-project helpers\n// ---------------------------------------------------------------------------\n\ninterface ProjectNameRow {\n project_hash: string;\n display_name: string | null;\n}\n\nfunction getProjectNameMap(db: BetterSqlite3.Database): Map<string, string> {\n const map = new Map<string, string>();\n try {\n const rows = db.prepare(\n 'SELECT project_hash, display_name FROM project_metadata'\n ).all() as ProjectNameRow[];\n for (const row of rows) {\n map.set(row.project_hash, row.display_name ?? row.project_hash.slice(0, 8));\n }\n } catch { /* table may not exist yet */ }\n return map;\n}\n\n// ---------------------------------------------------------------------------\n// registerRecall\n// ---------------------------------------------------------------------------\n\nexport function registerRecall(\n server: McpServer,\n db: BetterSqlite3.Database,\n projectHashRef: ProjectHashRef,\n worker: AnalysisWorker | null = null,\n embeddingStore: EmbeddingStore | null = null,\n notificationStore: NotificationStore | null = null,\n statusCache: StatusCache | null = null,\n): void {\n server.registerTool(\n 'recall',\n {\n title: 'Recall Memories',\n description:\n 'Search, view, purge, or restore memories. Search first to find matches, then act on specific results by ID.',\n inputSchema: {\n query: z\n .string()\n .optional()\n .describe('FTS5 keyword search query'),\n id: z.string().optional().describe('Direct lookup by observation ID'),\n title: z\n .string()\n .optional()\n .describe('Search by title (partial match)'),\n action: z\n .enum(['view', 'purge', 'restore'])\n .default('view')\n .describe(\n 'Action to take on results: view (show details), purge (soft-delete), restore (un-delete)',\n ),\n ids: z\n .array(z.string())\n .optional()\n .describe(\n 'Specific observation IDs to act on (from a previous search result)',\n ),\n detail: z\n .enum(['compact', 'timeline', 'full'])\n .default('compact')\n .describe(\n 'View detail level: compact (index ~80 tokens/result), timeline (date-grouped), full (complete text)',\n ),\n kind: z\n .enum(['change', 'reference', 'finding', 'decision', 'verification'])\n .optional()\n .describe(\n 'Filter results by observation kind: change, reference, finding, decision, verification',\n ),\n limit: z\n .number()\n .int()\n .min(1)\n .max(50)\n .default(10)\n .describe('Maximum results to return'),\n include_purged: z\n .boolean()\n .default(false)\n .describe(\n 'Include soft-deleted items in results (needed for restore)',\n ),\n },\n },\n async (args) => {\n const projectHash = projectHashRef.current;\n // Helper to wrap textResponse with pending notifications\n const withNotifications = (text: string) =>\n textResponse(prependNotifications(notificationStore, projectHash, text));\n\n try {\n const repo = new ObservationRepository(db, projectHash);\n const searchEngine = new SearchEngine(db, projectHash);\n\n // -------------------------------------------------------------------\n // PHASE A: Input Validation\n // -------------------------------------------------------------------\n const hasSearch = args.query !== undefined || args.id !== undefined || args.title !== undefined;\n if (args.ids && hasSearch) {\n return errorResponse(\n 'Provide either a search query or IDs to act on, not both.',\n );\n }\n\n if (\n (args.action === 'purge' || args.action === 'restore') &&\n !args.ids &&\n !args.id\n ) {\n return errorResponse(\n `Provide ids array or id to specify which memories to ${args.action}.`,\n );\n }\n\n // -------------------------------------------------------------------\n // PHASE B: Resolve Observations\n // -------------------------------------------------------------------\n let observations: Observation[] = [];\n let searchResults: SearchResult[] | null = null;\n\n if (args.ids) {\n // Fetch each ID, track not-found\n const notFound: string[] = [];\n for (const itemId of args.ids) {\n const obs = repo.getByIdIncludingDeleted(itemId);\n if (obs) {\n observations.push(obs);\n } else {\n notFound.push(itemId);\n }\n }\n if (notFound.length > 0 && observations.length === 0) {\n return withNotifications(\n `No memories found matching '${notFound.join(', ')}'. Try broader search terms or check the ID.`,\n );\n }\n } else if (args.id) {\n const obs = args.include_purged\n ? repo.getByIdIncludingDeleted(args.id)\n : repo.getById(args.id);\n if (!obs) {\n return withNotifications(\n `No memories found matching '${args.id}'. Try broader search terms or check the ID.`,\n );\n }\n observations = [obs];\n } else if (args.query) {\n if (embeddingStore) {\n searchResults = await hybridSearch({\n searchEngine,\n embeddingStore,\n worker,\n query: args.query,\n db,\n projectHash,\n options: { limit: args.limit },\n });\n } else {\n searchResults = searchEngine.searchKeyword(args.query, {\n limit: args.limit,\n });\n }\n observations = searchResults.map((r) => r.observation);\n\n // Cross-project search: also search readable projects\n const crossConfig = loadCrossAccessConfig(projectHash);\n if (crossConfig.readableProjects.length > 0) {\n const nameMap = getProjectNameMap(db);\n for (const otherHash of crossConfig.readableProjects) {\n const otherEngine = new SearchEngine(db, otherHash);\n let otherResults: SearchResult[];\n if (embeddingStore) {\n otherResults = await hybridSearch({\n searchEngine: otherEngine,\n embeddingStore,\n worker,\n query: args.query,\n db,\n projectHash: otherHash,\n options: { limit: args.limit },\n });\n } else {\n otherResults = otherEngine.searchKeyword(args.query, {\n limit: args.limit,\n });\n }\n if (otherResults.length > 0) {\n const projName = nameMap.get(otherHash) ?? otherHash.slice(0, 8);\n for (const r of otherResults) {\n r.observation.title = `[${projName}] ${r.observation.title ?? 'untitled'}`;\n }\n searchResults.push(...otherResults);\n observations.push(...otherResults.map((r) => r.observation));\n }\n }\n }\n } else if (args.title) {\n observations = repo.getByTitle(args.title, {\n limit: args.limit,\n includePurged: args.include_purged,\n });\n } else {\n // No query, id, title, or ids -- list recent\n observations = args.include_purged\n ? repo.listIncludingDeleted({ limit: args.limit })\n : repo.list({ limit: args.limit, kind: args.kind });\n }\n\n // Apply kind filter to resolved observations (post-search filtering)\n if (args.kind && observations.length > 0) {\n observations = observations.filter((obs) => obs.kind === args.kind);\n }\n\n if (observations.length === 0) {\n const searchTerm = args.query ?? args.title ?? args.id ?? '';\n return withNotifications(\n `No memories found matching '${searchTerm}'. Try broader search terms or check the ID.`,\n );\n }\n\n // -------------------------------------------------------------------\n // PHASE C: Execute Action\n // -------------------------------------------------------------------\n\n // --- VIEW ---\n if (args.action === 'view') {\n const verbosity = loadToolVerbosityConfig().level;\n\n if (verbosity === 1) {\n // Minimal: just count\n const searchTerm = args.query ?? args.title ?? 'query';\n return textResponse(\n prependNotifications(notificationStore, projectHash,\n `Found ${observations.length} memories matching \"${searchTerm}\"`),\n );\n }\n\n if (verbosity === 2) {\n // Standard: title-only numbered list\n const lines = observations.map((obs, i) => {\n const title = obs.title ?? 'untitled';\n return `${i + 1}. ${title}`;\n });\n const footer = `\\n---\\n${observations.length} result(s)`;\n return textResponse(\n prependNotifications(notificationStore, projectHash, lines.join('\\n') + footer),\n );\n }\n\n // Verbose (level 3): full output (current behavior)\n const viewResponse = formatViewResponse(\n observations,\n searchResults,\n args.detail,\n args.id !== undefined,\n );\n const originalText = viewResponse.content[0].text;\n return textResponse(prependNotifications(notificationStore, projectHash, originalText));\n }\n\n // --- PURGE ---\n if (args.action === 'purge') {\n const targetIds = args.ids ?? (args.id ? [args.id] : []);\n let success = 0;\n const failures: string[] = [];\n for (const targetId of targetIds) {\n if (repo.softDelete(targetId)) {\n success++;\n } else {\n failures.push(targetId);\n }\n }\n debug('mcp', 'recall: purge', { success, total: targetIds.length });\n if (success > 0) statusCache?.markDirty();\n let msg = `Purged ${success}/${targetIds.length} memories.`;\n if (failures.length > 0) {\n msg += ` Not found or already purged: ${failures.join(', ')}`;\n }\n return withNotifications(msg);\n }\n\n // --- RESTORE ---\n if (args.action === 'restore') {\n const targetIds = args.ids ?? (args.id ? [args.id] : []);\n let success = 0;\n const failures: string[] = [];\n for (const targetId of targetIds) {\n if (repo.restore(targetId)) {\n success++;\n } else {\n failures.push(targetId);\n }\n }\n debug('mcp', 'recall: restore', {\n success,\n total: targetIds.length,\n });\n if (success > 0) statusCache?.markDirty();\n let msg = `Restored ${success}/${targetIds.length} memories.`;\n if (failures.length > 0) {\n msg += ` Not found: ${failures.join(', ')}`;\n }\n return withNotifications(msg);\n }\n\n // Should not reach here, but TypeScript exhaustiveness\n return errorResponse(`Unknown action: ${args.action as string}`);\n } catch (err) {\n const message =\n err instanceof Error ? err.message : 'Unknown error';\n debug('mcp', 'recall: error', { error: message });\n return errorResponse(`Recall error: ${message}`);\n }\n },\n );\n}\n\n// ---------------------------------------------------------------------------\n// View formatting with token budget\n// ---------------------------------------------------------------------------\n\nfunction formatViewResponse(\n observations: Observation[],\n searchResults: SearchResult[] | null,\n detail: 'compact' | 'timeline' | 'full',\n isSingleIdLookup: boolean,\n): { content: { type: 'text'; text: string }[] } {\n let body: string;\n let truncated: boolean;\n let tokenEstimate: number;\n\n if (detail === 'compact') {\n const scoreMap = buildScoreMap(searchResults);\n const result = enforceTokenBudget(\n observations,\n (obs) => formatCompactItem(obs, observations.indexOf(obs) + 1, scoreMap.get(obs.id)),\n TOKEN_BUDGET,\n );\n body = result.items\n .map((obs, i) => formatCompactItem(obs, i + 1, scoreMap.get(obs.id)))\n .join('\\n');\n truncated = result.truncated;\n tokenEstimate = result.tokenEstimate;\n } else if (detail === 'timeline') {\n // Group by date\n const groups = new Map<string, { obs: Observation; score?: number }[]>();\n const scoreMap = buildScoreMap(searchResults);\n for (const obs of observations) {\n const date = dateStr(obs.createdAt);\n if (!groups.has(date)) {\n groups.set(date, []);\n }\n groups.get(date)!.push({ obs, score: scoreMap.get(obs.id) });\n }\n\n const result = enforceTokenBudget(\n observations,\n (obs) => {\n const time = timeStr(obs.createdAt);\n const title = obs.title ?? 'untitled';\n return `${time} | ${title} | ${obs.source} | ${snippetText(obs.content, 150)}`;\n },\n TOKEN_BUDGET,\n );\n\n // Re-group the budget-enforced items\n const includedIds = new Set(result.items.map((o) => o.id));\n const filteredGroups = new Map<string, { obs: Observation; score?: number }[]>();\n for (const [date, items] of groups) {\n const filtered = items.filter((item) => includedIds.has(item.obs.id));\n if (filtered.length > 0) {\n filteredGroups.set(date, filtered);\n }\n }\n\n body = Array.from(filteredGroups.entries())\n .map(([date, items]) => formatTimelineGroup(date, items))\n .join('\\n\\n');\n truncated = result.truncated;\n tokenEstimate = result.tokenEstimate;\n } else {\n // detail === 'full'\n const budget = isSingleIdLookup ? FULL_VIEW_BUDGET : TOKEN_BUDGET;\n\n if (observations.length === 1) {\n const formatted = formatFullItem(observations[0]);\n tokenEstimate = estimateTokens(formatted);\n if (tokenEstimate > budget) {\n // Truncate single item to fit budget\n const maxChars = budget * 4; // ~4 chars per token\n body =\n formatted.slice(0, maxChars) +\n `\\n[...truncated at ~${budget} tokens]`;\n truncated = true;\n tokenEstimate = budget;\n } else {\n body = formatted;\n truncated = false;\n }\n } else {\n const result = enforceTokenBudget(\n observations,\n formatFullItem,\n budget,\n );\n body = result.items.map(formatFullItem).join('\\n\\n');\n truncated = result.truncated;\n tokenEstimate = result.tokenEstimate;\n }\n }\n\n // Metadata footer\n let footer = `---\\n${observations.length} result(s) | ~${tokenEstimate} tokens | detail: ${detail}`;\n if (truncated) {\n footer += ' | truncated (use id for full view)';\n }\n\n return textResponse(`${body}\\n${footer}`);\n}\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\nfunction buildScoreMap(\n searchResults: SearchResult[] | null,\n): Map<string, number> {\n const map = new Map<string, number>();\n if (searchResults) {\n for (const r of searchResults) {\n map.set(r.observation.id, r.score);\n }\n }\n return map;\n}\n","import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type BetterSqlite3 from 'better-sqlite3';\nimport { z } from 'zod';\n\nimport { debug } from '../../shared/debug.js';\nimport type { ProjectHashRef } from '../../shared/types.js';\nimport { ObservationRepository } from '../../storage/observations.js';\nimport type { NotificationStore } from '../../storage/notifications.js';\nimport type { AnalysisWorker } from '../../analysis/worker-bridge.js';\nimport type { EmbeddingStore } from '../../storage/embeddings.js';\nimport { SaveGuard } from '../../hooks/save-guard.js';\nimport type { StatusCache } from '../status-cache.js';\nimport { verboseResponse } from '../../config/tool-verbosity-config.js';\n\n/**\n * Generates a title from observation content.\n * Extracts the first sentence (up to 100 chars) or first 80 chars with ellipsis.\n */\nexport function generateTitle(content: string): string {\n const firstSentence = content.match(/^[^.!?\\n]+[.!?]?/);\n if (firstSentence && firstSentence[0].length <= 100) {\n return firstSentence[0].trim();\n }\n if (content.length <= 80) return content.trim();\n return content.slice(0, 80).trim() + '...';\n}\n\n/**\n * Registers the save_memory tool on the MCP server.\n *\n * save_memory persists user-provided text as a new observation with an optional title.\n * If title is omitted, one is auto-generated from the text content.\n */\nexport function registerSaveMemory(\n server: McpServer,\n db: BetterSqlite3.Database,\n projectHashRef: ProjectHashRef,\n notificationStore: NotificationStore | null = null,\n worker: AnalysisWorker | null = null,\n embeddingStore: EmbeddingStore | null = null,\n statusCache: StatusCache | null = null,\n): void {\n server.registerTool(\n 'save_memory',\n {\n title: 'Save Memory',\n description:\n 'Save a new memory observation. Provide text content and an optional title. If title is omitted, one is auto-generated from the text.',\n inputSchema: {\n text: z\n .string()\n .min(1)\n .max(10000)\n .describe('The text content to save as a memory'),\n title: z\n .string()\n .max(200)\n .optional()\n .describe(\n 'Optional title for the memory. Auto-generated from text if omitted.',\n ),\n source: z\n .string()\n .default('manual')\n .describe(\"Source identifier (e.g., manual, hook:PostToolUse)\"),\n kind: z\n .enum(['change', 'reference', 'finding', 'decision', 'verification'])\n .default('finding')\n .describe('Observation kind: change, reference, finding, decision, or verification'),\n },\n },\n async (args) => {\n const projectHash = projectHashRef.current;\n try {\n const repo = new ObservationRepository(db, projectHash);\n\n // Pre-save gate: duplicate detection + relevance scoring\n const guard = new SaveGuard(repo, { worker, embeddingStore });\n const decision = await guard.evaluate(args.text, args.source);\n if (!decision.save) {\n debug('mcp', 'save_memory: rejected by save guard', {\n reason: decision.reason,\n duplicateOf: decision.duplicateOf,\n });\n return {\n content: [{\n type: 'text' as const,\n text: `Memory not saved: ${decision.reason}` +\n (decision.duplicateOf ? ` (similar to existing observation ${decision.duplicateOf})` : ''),\n }],\n };\n }\n\n const resolvedTitle = args.title ?? generateTitle(args.text);\n // Create as unclassified so HaikuProcessor picks it up for\n // entity extraction and graph population in its next cycle.\n const obs = repo.create({\n content: args.text,\n title: resolvedTitle,\n source: args.source,\n kind: args.kind,\n });\n\n debug('mcp', 'save_memory: saved', {\n id: obs.id,\n title: resolvedTitle,\n });\n\n statusCache?.markDirty();\n\n // Prepend any pending notifications to the response\n let responseText = verboseResponse(\n 'Memory saved.',\n `Saved \"${resolvedTitle}\"`,\n `Saved memory \"${resolvedTitle}\" (id: ${obs.id})`,\n );\n if (notificationStore) {\n const pending = notificationStore.consumePending(projectHash);\n if (pending.length > 0) {\n const banner = pending.map(n => `[Laminark] ${n.message}`).join('\\n');\n responseText = banner + '\\n\\n' + responseText;\n }\n }\n\n return {\n content: [\n {\n type: 'text' as const,\n text: responseText,\n },\n ],\n };\n } catch (err) {\n const message =\n err instanceof Error ? err.message : 'Unknown error';\n return {\n content: [\n { type: 'text' as const, text: `Failed to save: ${message}` },\n ],\n isError: true,\n };\n }\n },\n );\n}\n","/**\n * Markdown section parser for knowledge ingestion.\n *\n * Splits structured markdown documents (GSD map-codebase output) into\n * discrete sections by ## headings. Each section becomes one observation\n * in the knowledge store.\n */\n\nexport interface ParsedSection {\n title: string; // Full title: \"Technology Stack > Languages\" (docTitle > heading)\n heading: string; // Just the heading text: \"Languages\"\n content: string; // Everything under heading until next ## or EOF\n sourceFile: string; // Filename: \"STACK.md\"\n sectionIndex: number; // 0-based index within file\n}\n\n/**\n * Parse a markdown file into discrete sections split on ## headings.\n *\n * - The # (level 1) heading is the doc title, used as prefix: \"DocTitle > SectionHeading\"\n * - ### subsections stay within their parent ## section (not split separately)\n * - Sections with empty content after trimming are skipped\n * - Content before the first ## heading is skipped\n * - ## inside fenced code blocks are not treated as headings\n */\nexport function parseMarkdownSections(\n fileContent: string,\n sourceFile: string,\n): ParsedSection[] {\n const lines = fileContent.split('\\n');\n const sections: ParsedSection[] = [];\n\n let docTitle = '';\n let currentHeading = '';\n let currentLines: string[] = [];\n let sectionIndex = 0;\n let inCodeBlock = false;\n\n for (const line of lines) {\n // Track fenced code blocks to avoid splitting on ## inside them\n if (line.trimStart().startsWith('```')) {\n inCodeBlock = !inCodeBlock;\n }\n\n if (inCodeBlock) {\n if (currentHeading) {\n currentLines.push(line);\n }\n continue;\n }\n\n // Document title (# heading) -- only match single # not ##\n if (/^# (?!#)/.test(line)) {\n docTitle = line.slice(2).trim();\n continue;\n }\n\n // Section heading (## heading) -- only match exactly ## not ###\n if (/^## (?!#)/.test(line)) {\n // Save previous section if any\n if (currentHeading) {\n const content = currentLines.join('\\n').trim();\n if (content.length > 0) {\n sections.push({\n title: docTitle ? `${docTitle} > ${currentHeading}` : currentHeading,\n heading: currentHeading,\n content,\n sourceFile,\n sectionIndex,\n });\n sectionIndex++;\n }\n }\n currentHeading = line.slice(3).trim();\n currentLines = [];\n continue;\n }\n\n if (currentHeading) {\n currentLines.push(line);\n }\n }\n\n // Don't forget the last section\n if (currentHeading) {\n const content = currentLines.join('\\n').trim();\n if (content.length > 0) {\n sections.push({\n title: docTitle ? `${docTitle} > ${currentHeading}` : currentHeading,\n heading: currentHeading,\n content,\n sourceFile,\n sectionIndex,\n });\n }\n }\n\n return sections;\n}\n","/**\n * Knowledge ingester for markdown documents.\n *\n * Transforms structured markdown files (from .planning/codebase/ or .laminark/codebase/)\n * into discrete, queryable reference observations. Implements idempotent re-ingestion\n * via soft-delete + recreate strategy.\n */\n\nimport type BetterSqlite3 from 'better-sqlite3';\nimport { existsSync, statSync } from 'node:fs';\nimport { readdir, readFile } from 'node:fs/promises';\nimport { basename, join } from 'node:path';\nimport { parseMarkdownSections } from './markdown-parser.js';\nimport { ObservationRepository } from '../storage/observations.js';\n\n/**\n * Statistics from an ingestion operation.\n */\nexport interface IngestionStats {\n filesProcessed: number;\n sectionsCreated: number;\n sectionsRemoved: number;\n}\n\n/**\n * Ingests markdown files into the knowledge store.\n *\n * Creates one observation per ## section, with idempotent re-ingestion\n * that cleans up stale sections without duplication.\n */\nexport class KnowledgeIngester {\n private readonly db: BetterSqlite3.Database;\n private readonly projectHash: string;\n\n constructor(db: BetterSqlite3.Database, projectHash: string) {\n this.db = db;\n this.projectHash = projectHash;\n }\n\n /**\n * Detects the knowledge directory for a project.\n * Checks in order:\n * 1. {projectRoot}/.planning/codebase/ (GSD output)\n * 2. {projectRoot}/.laminark/codebase/\n * Returns the first existing directory, or null if none exist.\n */\n static detectKnowledgeDir(projectRoot: string): string | null {\n const candidates = [\n join(projectRoot, '.planning', 'codebase'),\n join(projectRoot, '.laminark', 'codebase'),\n ];\n\n for (const candidate of candidates) {\n try {\n if (existsSync(candidate) && statSync(candidate).isDirectory()) {\n return candidate;\n }\n } catch {\n // Directory doesn't exist, try next\n }\n }\n\n return null;\n }\n\n /**\n * Ingests all markdown files from a directory.\n * Reads all files async first, then runs DB operations in a single transaction.\n */\n async ingestDirectory(dirPath: string): Promise<IngestionStats> {\n let files: string[];\n try {\n files = await readdir(dirPath);\n } catch {\n // Directory doesn't exist or can't be read\n return { filesProcessed: 0, sectionsCreated: 0, sectionsRemoved: 0 };\n }\n\n // Filter to .md files only\n const mdFiles = files.filter((f) => f.endsWith('.md'));\n\n // Read all file contents async first\n const fileContents = new Map<string, string>();\n for (const file of mdFiles) {\n const filePath = join(dirPath, file);\n try {\n const content = await readFile(filePath, 'utf-8');\n fileContents.set(file, content);\n } catch {\n // Skip files that can't be read\n }\n }\n\n // Aggregate stats\n let totalCreated = 0;\n let totalRemoved = 0;\n\n // Process each file in a transaction\n for (const [filename, content] of fileContents) {\n const stats = this.ingestFileSync(filename, content);\n totalCreated += stats.sectionsCreated;\n totalRemoved += stats.sectionsRemoved;\n }\n\n return {\n filesProcessed: fileContents.size,\n sectionsCreated: totalCreated,\n sectionsRemoved: totalRemoved,\n };\n }\n\n /**\n * Ingests a single markdown file.\n * Wraps async file reading with sync ingestion.\n */\n async ingestFile(filePath: string): Promise<IngestionStats> {\n try {\n const content = await readFile(filePath, 'utf-8');\n const filename = basename(filePath);\n return this.ingestFileSync(filename, content);\n } catch {\n // File can't be read\n return { filesProcessed: 0, sectionsCreated: 0, sectionsRemoved: 0 };\n }\n }\n\n /**\n * Internal sync ingestion method (runs within transaction).\n * Implements idempotent upsert via soft-delete + recreate.\n */\n private ingestFileSync(filename: string, fileContent: string): IngestionStats {\n const sourceTag = `ingest:${filename}`;\n\n // Parse sections from file\n const sections = parseMarkdownSections(fileContent, filename);\n\n // Run DB operations in a transaction\n return this.db.transaction(() => {\n const repo = new ObservationRepository(this.db, this.projectHash);\n\n // Soft-delete ALL existing observations with matching source and project\n const deleteResult = this.db\n .prepare(\n `UPDATE observations\n SET deleted_at = datetime('now'), updated_at = datetime('now')\n WHERE project_hash = ? AND source = ? AND deleted_at IS NULL`,\n )\n .run(this.projectHash, sourceTag);\n\n const sectionsRemoved = deleteResult.changes;\n\n // Create new observations for each parsed section\n let sectionsCreated = 0;\n for (const section of sections) {\n repo.createClassified(\n {\n content: section.content,\n title: section.title,\n source: sourceTag,\n kind: 'reference',\n sessionId: null,\n },\n 'discovery', // Bypass noise filter, make immediately searchable\n );\n sectionsCreated++;\n }\n\n return {\n filesProcessed: 1,\n sectionsCreated,\n sectionsRemoved,\n };\n })();\n }\n}\n","import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type BetterSqlite3 from 'better-sqlite3';\nimport { z } from 'zod';\n\nimport { debug } from '../../shared/debug.js';\nimport type { ProjectHashRef } from '../../shared/types.js';\nimport type { NotificationStore } from '../../storage/notifications.js';\nimport { KnowledgeIngester } from '../../ingestion/knowledge-ingester.js';\nimport type { StatusCache } from '../status-cache.js';\nimport { verboseResponse } from '../../config/tool-verbosity-config.js';\n\n/**\n * Registers the ingest_knowledge tool on the MCP server.\n *\n * ingest_knowledge transforms structured markdown documents into per-project\n * reference observations. Supports optional directory path; auto-detects\n * .planning/codebase/ or .laminark/codebase/ from project metadata when\n * directory is omitted.\n */\nexport function registerIngestKnowledge(\n server: McpServer,\n db: BetterSqlite3.Database,\n projectHashRef: ProjectHashRef,\n notificationStore: NotificationStore | null = null,\n statusCache: StatusCache | null = null,\n): void {\n server.registerTool(\n 'ingest_knowledge',\n {\n title: 'Ingest Knowledge',\n description:\n 'Ingest structured markdown documents from a directory into queryable per-project memories. Reads .md files, splits by ## headings, and stores each section as a reference observation. Supports .planning/codebase/ (GSD output) and .laminark/codebase/.',\n inputSchema: {\n directory: z\n .string()\n .optional()\n .describe(\n 'Directory containing .md files to ingest. If omitted, auto-detects .planning/codebase/ or .laminark/codebase/ using the project path from project_metadata.',\n ),\n },\n },\n async (args) => {\n const projectHash = projectHashRef.current;\n try {\n let resolvedDir = args.directory;\n\n // If directory not provided, resolve from project_metadata\n if (!resolvedDir) {\n const row = db\n .prepare(\n 'SELECT project_path FROM project_metadata WHERE project_hash = ? ORDER BY last_seen_at DESC LIMIT 1',\n )\n .get(projectHash) as { project_path: string } | undefined;\n\n if (!row) {\n return {\n content: [\n {\n type: 'text' as const,\n text: 'Could not determine project path. Please provide the directory parameter explicitly.',\n },\n ],\n isError: true,\n };\n }\n\n const detected = KnowledgeIngester.detectKnowledgeDir(row.project_path);\n if (!detected) {\n return {\n content: [\n {\n type: 'text' as const,\n text: 'No knowledge directory found. Expected .planning/codebase/ or .laminark/codebase/ in the project root. Run /gsd:map-codebase first or provide a directory path.',\n },\n ],\n isError: true,\n };\n }\n\n resolvedDir = detected;\n }\n\n const ingester = new KnowledgeIngester(db, projectHash);\n const stats = await ingester.ingestDirectory(resolvedDir!);\n\n debug('mcp', 'ingest_knowledge: completed', {\n directory: resolvedDir,\n stats,\n });\n\n statusCache?.markDirty();\n\n const responseText = verboseResponse(\n `Ingested ${stats.filesProcessed} files: ${stats.sectionsCreated} sections created, ${stats.sectionsRemoved} stale sections removed.`,\n `Ingested ${stats.filesProcessed} file(s): ${stats.sectionsCreated} sections created, ${stats.sectionsRemoved} removed.`,\n `Ingested ${stats.filesProcessed} file(s) from ${resolvedDir}: ${stats.sectionsCreated} sections created, ${stats.sectionsRemoved} stale sections removed.`,\n );\n\n // Prepend any pending notifications to the response\n let finalResponse = responseText;\n if (notificationStore) {\n const pending = notificationStore.consumePending(projectHash);\n if (pending.length > 0) {\n const banner = pending.map(n => `[Laminark] ${n.message}`).join('\\n');\n finalResponse = banner + '\\n\\n' + finalResponse;\n }\n }\n\n return {\n content: [\n {\n type: 'text' as const,\n text: finalResponse,\n },\n ],\n };\n } catch (err) {\n const message =\n err instanceof Error ? err.message : 'Unknown error';\n return {\n content: [\n {\n type: 'text' as const,\n text: `Failed to ingest knowledge: ${message}`,\n },\n ],\n isError: true,\n };\n }\n },\n );\n}\n","import type { StashManager } from '../storage/stash-manager.js';\nimport type { StashObservation } from '../types/stash.js';\n\n/**\n * Result of the /laminark:resume command.\n */\nexport interface ResumeResult {\n success: boolean;\n message: string;\n context?: StashObservation[];\n}\n\n/**\n * Dependencies injected into the resume command handler.\n */\nexport interface ResumeDeps {\n stashManager: StashManager;\n}\n\n/**\n * Returns a human-readable relative time string from an ISO date.\n * Examples: \"just now\", \"2 minutes ago\", \"3 hours ago\", \"yesterday\", \"5 days ago\"\n */\nexport function timeAgo(dateString: string, now?: Date): string {\n const date = new Date(dateString);\n const ref = now ?? new Date();\n const diffMs = ref.getTime() - date.getTime();\n\n if (diffMs < 0) return 'just now';\n\n const seconds = Math.floor(diffMs / 1000);\n if (seconds < 60) return 'just now';\n\n const minutes = Math.floor(seconds / 60);\n if (minutes === 1) return '1 minute ago';\n if (minutes < 60) return `${minutes} minutes ago`;\n\n const hours = Math.floor(minutes / 60);\n if (hours === 1) return '1 hour ago';\n if (hours < 24) return `${hours} hours ago`;\n\n const days = Math.floor(hours / 24);\n if (days === 1) return 'yesterday';\n if (days < 30) return `${days} days ago`;\n\n const months = Math.floor(days / 30);\n if (months === 1) return '1 month ago';\n return `${months} months ago`;\n}\n\n/**\n * Truncates a string to maxLen characters, appending \"...\" if truncated.\n */\nfunction truncate(text: string, maxLen: number): string {\n if (text.length <= maxLen) return text;\n return text.slice(0, maxLen) + '...';\n}\n\n/**\n * Handles the /laminark:resume slash command.\n *\n * Two modes:\n * 1. List mode (no stashId): shows stashed context threads\n * 2. Resume mode (stashId provided): restores context from a specific stash\n */\nexport async function handleResumeCommand(\n args: { projectId: string; stashId?: string },\n deps: ResumeDeps,\n): Promise<ResumeResult> {\n const { stashManager } = deps;\n\n // Resume mode: restore a specific stash\n if (args.stashId) {\n const stash = stashManager.getStash(args.stashId);\n if (!stash) {\n return { success: false, message: `Stash not found: ${args.stashId}` };\n }\n\n stashManager.resumeStash(args.stashId);\n\n const count = stash.observationSnapshots.length;\n return {\n success: true,\n message: `Resumed: \"${stash.topicLabel}\"\\n\\nContext restored with ${count} observations.`,\n context: stash.observationSnapshots,\n };\n }\n\n // List mode: show available stashed threads\n const stashes = stashManager.listStashes(args.projectId, {\n status: 'stashed',\n limit: 5,\n });\n\n if (stashes.length === 0) {\n return { success: true, message: 'No stashed context threads found.' };\n }\n\n const lines = ['Stashed context threads:'];\n for (let i = 0; i < stashes.length; i++) {\n const s = stashes[i];\n const ago = timeAgo(s.createdAt);\n const summary = truncate(s.summary, 80);\n lines.push(`${i + 1}. ${s.topicLabel} (${ago}) - ${summary}`);\n }\n lines.push('');\n lines.push('Use /laminark:resume {id} to restore a thread.');\n\n return { success: true, message: lines.join('\\n') };\n}\n","import type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type BetterSqlite3 from 'better-sqlite3';\nimport { z } from 'zod';\n\nimport { debug } from '../../shared/debug.js';\nimport type { ProjectHashRef } from '../../shared/types.js';\nimport { StashManager } from '../../storage/stash-manager.js';\nimport type { NotificationStore } from '../../storage/notifications.js';\nimport type { ContextStash } from '../../types/stash.js';\nimport { timeAgo } from '../../commands/resume.js';\nimport { loadToolVerbosityConfig } from '../../config/tool-verbosity-config.js';\n\n// ---------------------------------------------------------------------------\n// Formatting helpers (progressive disclosure)\n// ---------------------------------------------------------------------------\n\nfunction truncate(text: string, maxLen: number): string {\n if (text.length <= maxLen) return text;\n return text.slice(0, maxLen) + '...';\n}\n\n/**\n * Compact format: numbered list of topic labels with relative time.\n */\nfunction formatCompact(stashes: ContextStash[]): string {\n return stashes\n .map(\n (s, i) =>\n `${i + 1}. ${s.topicLabel} (${timeAgo(s.createdAt)})`,\n )\n .join('\\n');\n}\n\n/**\n * Detail format: topic labels with summaries.\n */\nfunction formatDetail(stashes: ContextStash[]): string {\n return stashes\n .map(\n (s, i) =>\n `${i + 1}. **${s.topicLabel}** (${timeAgo(s.createdAt)})\\n ${truncate(s.summary, 120)}`,\n )\n .join('\\n\\n');\n}\n\n/**\n * Full format: topic labels, summaries, observation count, and first few observation snippets.\n */\nfunction formatFull(stashes: ContextStash[]): string {\n return stashes\n .map((s, i) => {\n const lines = [\n `${i + 1}. **${s.topicLabel}** (${timeAgo(s.createdAt)})`,\n ` ${s.summary}`,\n ` Observations: ${s.observationSnapshots.length}`,\n ];\n\n // Show first 3 observation snippets\n const previews = s.observationSnapshots.slice(0, 3);\n for (const obs of previews) {\n lines.push(` - ${truncate(obs.content.replace(/\\n/g, ' '), 80)}`);\n }\n if (s.observationSnapshots.length > 3) {\n lines.push(\n ` ... and ${s.observationSnapshots.length - 3} more`,\n );\n }\n\n return lines.join('\\n');\n })\n .join('\\n\\n');\n}\n\n/**\n * Formats stashes using progressive disclosure based on count.\n * - 1-3 stashes: full detail\n * - 4-8 stashes: detail (summaries)\n * - 9+: compact (labels only)\n */\nexport function formatStashes(stashes: ContextStash[]): string {\n if (stashes.length <= 3) return formatFull(stashes);\n if (stashes.length <= 8) return formatDetail(stashes);\n return formatCompact(stashes);\n}\n\n// ---------------------------------------------------------------------------\n// Response helpers\n// ---------------------------------------------------------------------------\n\nfunction prependNotifications(\n notificationStore: NotificationStore | null,\n projectHash: string,\n responseText: string,\n): string {\n if (!notificationStore) return responseText;\n const pending = notificationStore.consumePending(projectHash);\n if (pending.length === 0) return responseText;\n const banner = pending.map(n => `[Laminark] ${n.message}`).join('\\n');\n return banner + '\\n\\n' + responseText;\n}\n\nfunction textResponse(text: string) {\n return { content: [{ type: 'text' as const, text }] };\n}\n\n// ---------------------------------------------------------------------------\n// registerTopicContext\n// ---------------------------------------------------------------------------\n\n/**\n * Registers the topic_context MCP tool.\n *\n * Shows recently stashed context threads. Used when the user asks\n * \"where was I?\" or wants to see abandoned conversation threads.\n */\nexport function registerTopicContext(\n server: McpServer,\n db: BetterSqlite3.Database,\n projectHashRef: ProjectHashRef,\n notificationStore: NotificationStore | null = null,\n): void {\n const stashManager = new StashManager(db);\n\n server.registerTool(\n 'topic_context',\n {\n title: 'Topic Context',\n description:\n \"Shows recently stashed context threads. Use when the user asks 'where was I?' or wants to see abandoned conversation threads.\",\n inputSchema: {\n query: z\n .string()\n .optional()\n .describe('Optional search query to filter threads by topic label or summary'),\n limit: z\n .number()\n .int()\n .min(1)\n .max(20)\n .default(5)\n .describe('Max threads to return'),\n },\n },\n async (args) => {\n const projectHash = projectHashRef.current;\n // Helper to wrap textResponse with pending notifications\n const withNotifications = (text: string) =>\n textResponse(prependNotifications(notificationStore, projectHash, text));\n\n try {\n debug('mcp', 'topic_context: request', { query: args.query, limit: args.limit });\n\n let stashes = stashManager.getRecentStashes(projectHash, args.limit);\n\n // Filter by query if provided (case-insensitive match on topicLabel or summary)\n if (args.query) {\n const q = args.query.toLowerCase();\n stashes = stashes.filter(\n (s) =>\n s.topicLabel.toLowerCase().includes(q) ||\n s.summary.toLowerCase().includes(q),\n );\n }\n\n if (stashes.length === 0) {\n return withNotifications(\n 'No stashed context threads found. You\\'re working in a single thread.',\n );\n }\n\n const verbosity = loadToolVerbosityConfig().level;\n\n if (verbosity === 1) {\n return withNotifications(`${stashes.length} stashed thread(s)`);\n }\n\n if (verbosity === 2) {\n // Standard: topic labels with relative time only\n const lines = stashes.map(\n (s, i) => `${i + 1}. ${s.topicLabel} (${timeAgo(s.createdAt)})`,\n );\n return withNotifications(lines.join('\\n'));\n }\n\n // Verbose: full output\n const formatted = formatStashes(stashes);\n const footer = `\\n---\\n${stashes.length} stashed thread(s) | Use /laminark:resume {id} to restore`;\n\n debug('mcp', 'topic_context: returning', { count: stashes.length });\n\n return withNotifications(formatted + footer);\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Unknown error';\n debug('mcp', 'topic_context: error', { error: message });\n return textResponse(`Error retrieving context threads: ${message}`);\n }\n },\n );\n}\n","/**\n * Type definitions for the knowledge graph.\n *\n * Defines a fixed entity/relationship taxonomy using const arrays and\n * derived union types (NOT enums) for better type inference and runtime\n * validation. Every Phase 7 module imports from this file.\n */\n\n// =============================================================================\n// Entity Type Taxonomy (FIXED -- no other types allowed)\n// =============================================================================\n\nexport const ENTITY_TYPES = [\n 'Project',\n 'File',\n 'Decision',\n 'Problem',\n 'Solution',\n 'Reference',\n] as const;\n\nexport type EntityType = (typeof ENTITY_TYPES)[number];\n\n// =============================================================================\n// Relationship Type Taxonomy (FIXED -- no other types allowed)\n// =============================================================================\n\nexport const RELATIONSHIP_TYPES = [\n 'related_to',\n 'solved_by',\n 'caused_by',\n 'modifies',\n 'informed_by',\n 'references',\n 'verified_by',\n 'preceded_by',\n] as const;\n\nexport type RelationshipType = (typeof RELATIONSHIP_TYPES)[number];\n\n// =============================================================================\n// Graph Node Interface\n// =============================================================================\n\n/**\n * A node in the knowledge graph representing a named entity.\n *\n * - id: UUID (hex-encoded randomBytes)\n * - type: one of the 6 entity types\n * - name: canonical name (e.g., \"src/auth/login.ts\" for File, \"Use JWT\" for Decision)\n * - metadata: flexible JSON for type-specific data\n * - observation_ids: source observations this entity was extracted from\n * - created_at / updated_at: ISO 8601 timestamps\n */\nexport interface GraphNode {\n id: string;\n type: EntityType;\n name: string;\n metadata: Record<string, unknown>;\n observation_ids: string[];\n created_at: string;\n updated_at: string;\n}\n\n// =============================================================================\n// Graph Edge Interface\n// =============================================================================\n\n/**\n * A directed edge in the knowledge graph connecting two nodes.\n *\n * - id: UUID (hex-encoded randomBytes)\n * - source_id / target_id: references to GraphNode.id\n * - type: one of the 8 relationship types\n * - weight: confidence/strength score between 0.0 and 1.0\n * - metadata: flexible JSON for relationship-specific data\n * - created_at: ISO 8601 timestamp\n */\nexport interface GraphEdge {\n id: string;\n source_id: string;\n target_id: string;\n type: RelationshipType;\n weight: number;\n metadata: Record<string, unknown>;\n created_at: string;\n}\n\n// =============================================================================\n// Type Guards\n// =============================================================================\n\n/**\n * Runtime type guard for EntityType.\n * Uses the ENTITY_TYPES const array for O(n) lookup (n=6, negligible).\n */\nexport function isEntityType(s: string): s is EntityType {\n return (ENTITY_TYPES as readonly string[]).includes(s);\n}\n\n/**\n * Runtime type guard for RelationshipType.\n * Uses the RELATIONSHIP_TYPES const array for O(n) lookup (n=8, negligible).\n */\nexport function isRelationshipType(s: string): s is RelationshipType {\n return (RELATIONSHIP_TYPES as readonly string[]).includes(s);\n}\n\n// =============================================================================\n// Constants\n// =============================================================================\n\n/**\n * Maximum number of edges a single node can have.\n * Used by constraint enforcement in Plan 05 to prevent\n * hub nodes from dominating the graph.\n */\nexport const MAX_NODE_DEGREE = 50;\n","/**\n * MCP tool handler for knowledge graph queries.\n *\n * Allows Claude to search the knowledge graph for entities by name or type,\n * traverse relationships to a configurable depth, and see linked observation\n * excerpts. Results use progressive disclosure: entity list with connection\n * counts, then relationships, then observation excerpts.\n */\n\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type BetterSqlite3 from 'better-sqlite3';\nimport { z } from 'zod';\n\nimport { debug } from '../../shared/debug.js';\nimport type { ProjectHashRef } from '../../shared/types.js';\nimport type { NotificationStore } from '../../storage/notifications.js';\nimport {\n type EntityType,\n type RelationshipType,\n ENTITY_TYPES,\n RELATIONSHIP_TYPES,\n isEntityType,\n isRelationshipType,\n} from '../../graph/types.js';\nimport type { GraphNode, GraphEdge } from '../../graph/types.js';\nimport {\n initGraphSchema,\n traverseFrom,\n getNodeByNameAndType,\n getEdgesForNode,\n type TraversalResult,\n} from '../../graph/schema.js';\nimport { loadToolVerbosityConfig } from '../../config/tool-verbosity-config.js';\n\n// =============================================================================\n// Types\n// =============================================================================\n\nexport interface QueryGraphInput {\n query: string;\n entity_type?: EntityType;\n depth?: number;\n relationship_types?: RelationshipType[];\n limit?: number;\n}\n\nexport interface QueryGraphOutput {\n entities: Array<{\n node: GraphNode;\n connectionCount: number;\n relationships: Array<{\n direction: 'outgoing' | 'incoming';\n type: RelationshipType;\n targetName: string;\n targetType: EntityType;\n }>;\n }>;\n observations: Array<{\n text: string;\n createdAt: string;\n }>;\n totalFound: number;\n}\n\n// =============================================================================\n// Internal Row Types\n// =============================================================================\n\ninterface NodeRow {\n id: string;\n type: string;\n name: string;\n metadata: string;\n observation_ids: string;\n created_at: string;\n updated_at: string;\n}\n\ninterface ObsSnippetRow {\n content: string;\n created_at: string;\n}\n\n// =============================================================================\n// Formatting Helpers\n// =============================================================================\n\nfunction truncateText(text: string, maxLen: number): string {\n if (text.length <= maxLen) return text;\n return text.slice(0, maxLen).trimEnd() + '...';\n}\n\nfunction formatEntityType(type: EntityType): string {\n return `[${type}]`;\n}\n\n/**\n * Formats query results as readable text for Claude consumption.\n * Uses progressive disclosure: entity list -> relationships -> observations.\n */\nfunction formatResults(\n rootNodes: GraphNode[],\n traversalsByNode: Map<string, TraversalResult[]>,\n observations: Array<{ text: string; createdAt: string }>,\n query: string,\n): string {\n const lines: string[] = [];\n\n // Section: Entities Found\n lines.push('## Entities Found');\n lines.push('');\n\n for (const node of rootNodes) {\n const traversals = traversalsByNode.get(node.id) ?? [];\n const connectionCount = traversals.length;\n\n lines.push(\n `- ${formatEntityType(node.type)} ${node.name} (${connectionCount} connection${connectionCount !== 1 ? 's' : ''})`,\n );\n\n // Show relationships\n for (const t of traversals) {\n if (!t.edge) continue;\n const direction = t.edge.source_id === node.id ? '->' : '<-';\n lines.push(\n ` ${direction} ${t.edge.type} ${formatEntityType(t.node.type)} ${t.node.name}`,\n );\n }\n\n lines.push('');\n }\n\n // Section: Related Observations\n if (observations.length > 0) {\n lines.push('## Related Observations');\n lines.push('');\n\n for (const obs of observations) {\n const age = formatAge(obs.createdAt);\n const snippet = truncateText(obs.text.replace(/\\n/g, ' '), 200);\n lines.push(`- \"${snippet}\" (${age})`);\n }\n }\n\n return lines.join('\\n').trim();\n}\n\n/**\n * Simple relative time formatting.\n */\nfunction formatAge(isoDate: string): string {\n const now = Date.now();\n const then = new Date(isoDate).getTime();\n const diffMs = now - then;\n\n const hours = Math.floor(diffMs / (1000 * 60 * 60));\n if (hours < 1) return 'just now';\n if (hours < 24) return `${hours} hour${hours !== 1 ? 's' : ''} ago`;\n\n const days = Math.floor(hours / 24);\n if (days < 30) return `${days} day${days !== 1 ? 's' : ''} ago`;\n\n const months = Math.floor(days / 30);\n return `${months} month${months !== 1 ? 's' : ''} ago`;\n}\n\n// =============================================================================\n// Response Helpers\n// =============================================================================\n\nfunction prependNotifications(\n notificationStore: NotificationStore | null,\n projectHash: string,\n responseText: string,\n): string {\n if (!notificationStore) return responseText;\n const pending = notificationStore.consumePending(projectHash);\n if (pending.length === 0) return responseText;\n const banner = pending.map((n) => `[Laminark] ${n.message}`).join('\\n');\n return banner + '\\n\\n' + responseText;\n}\n\nfunction textResponse(text: string) {\n return { content: [{ type: 'text' as const, text }] };\n}\n\nfunction errorResponse(text: string) {\n return { content: [{ type: 'text' as const, text }], isError: true };\n}\n\n// =============================================================================\n// Tool Registration\n// =============================================================================\n\n/**\n * Registers the query_graph MCP tool on the server.\n *\n * Allows Claude to search entities by name (exact or fuzzy), filter by type,\n * traverse relationships to configurable depth, and see linked observations.\n */\nexport function registerQueryGraph(\n server: McpServer,\n db: BetterSqlite3.Database,\n projectHashRef: ProjectHashRef,\n notificationStore: NotificationStore | null = null,\n): void {\n // Ensure graph schema is initialized\n initGraphSchema(db);\n\n server.registerTool(\n 'query_graph',\n {\n title: 'Query Knowledge Graph',\n description:\n \"Query the knowledge graph to find entities and their relationships. Use to answer questions like 'what files does this decision affect?' or 'what references informed this change?'\",\n inputSchema: {\n query: z\n .string()\n .min(1)\n .describe('Entity name or search text to look for'),\n entity_type: z\n .string()\n .optional()\n .describe(\n `Filter to entity type: ${ENTITY_TYPES.join(', ')}`,\n ),\n depth: z\n .number()\n .int()\n .min(1)\n .max(4)\n .default(2)\n .describe('Traversal depth (default: 2, max: 4)'),\n relationship_types: z\n .array(z.string())\n .optional()\n .describe(\n `Filter to relationship types: ${RELATIONSHIP_TYPES.join(', ')}`,\n ),\n limit: z\n .number()\n .int()\n .min(1)\n .max(50)\n .default(20)\n .describe('Max root entities to return (default: 20, max: 50)'),\n },\n },\n async (args) => {\n const projectHash = projectHashRef.current;\n const withNotifications = (text: string) =>\n textResponse(\n prependNotifications(notificationStore, projectHash, text),\n );\n\n try {\n debug('mcp', 'query_graph: request', {\n query: args.query,\n entity_type: args.entity_type,\n depth: args.depth,\n });\n\n // Validate entity_type if provided\n if (args.entity_type !== undefined && !isEntityType(args.entity_type)) {\n return errorResponse(\n `Invalid entity_type \"${args.entity_type}\". Valid types: ${ENTITY_TYPES.join(', ')}`,\n );\n }\n const entityType = args.entity_type as EntityType | undefined;\n\n // Validate relationship_types if provided\n if (args.relationship_types) {\n for (const rt of args.relationship_types) {\n if (!isRelationshipType(rt)) {\n return errorResponse(\n `Invalid relationship_type \"${rt}\". Valid types: ${RELATIONSHIP_TYPES.join(', ')}`,\n );\n }\n }\n }\n const relationshipTypes = args.relationship_types as\n | RelationshipType[]\n | undefined;\n\n // Search strategy: exact match first, then fuzzy LIKE search\n const rootNodes: GraphNode[] = [];\n\n // 1. Try exact name match (optionally filtered by type)\n if (entityType) {\n const exact = getNodeByNameAndType(db, args.query, entityType, projectHash);\n if (exact) rootNodes.push(exact);\n } else {\n // Try exact match across all types\n for (const t of ENTITY_TYPES) {\n const exact = getNodeByNameAndType(db, args.query, t, projectHash);\n if (exact) {\n rootNodes.push(exact);\n break; // Take first exact match\n }\n }\n }\n\n // 2. If no exact match, try case-insensitive LIKE search\n if (rootNodes.length === 0) {\n const likePattern = `%${args.query}%`;\n let sql: string;\n const params: unknown[] = [likePattern, projectHash];\n\n if (entityType) {\n sql =\n 'SELECT * FROM graph_nodes WHERE name LIKE ? COLLATE NOCASE AND project_hash = ? AND type = ? LIMIT ?';\n params.push(entityType, args.limit);\n } else {\n sql =\n 'SELECT * FROM graph_nodes WHERE name LIKE ? COLLATE NOCASE AND project_hash = ? LIMIT ?';\n params.push(args.limit);\n }\n\n const rows = db.prepare(sql).all(...params) as NodeRow[];\n for (const row of rows) {\n rootNodes.push({\n id: row.id,\n type: row.type as EntityType,\n name: row.name,\n metadata: JSON.parse(row.metadata) as Record<string, unknown>,\n observation_ids: JSON.parse(row.observation_ids) as string[],\n created_at: row.created_at,\n updated_at: row.updated_at,\n });\n }\n }\n\n // No results found\n if (rootNodes.length === 0) {\n const suggestions = entityType\n ? `Try searching without the entity_type filter, or try a different name.`\n : `Try: entity types ${ENTITY_TYPES.join(', ')}`;\n return withNotifications(\n `No entities matching \"${args.query}\" found. ${suggestions}`,\n );\n }\n\n // 3. Traverse from each root node\n const traversalsByNode = new Map<string, TraversalResult[]>();\n\n for (const node of rootNodes) {\n const results = traverseFrom(db, node.id, {\n depth: args.depth,\n edgeTypes: relationshipTypes,\n direction: 'both',\n projectHash,\n });\n traversalsByNode.set(node.id, results);\n }\n\n // 4. Collect observation snippets from root nodes\n const allObsIds = new Set<string>();\n for (const node of rootNodes) {\n for (const obsId of node.observation_ids) {\n allObsIds.add(obsId);\n }\n }\n\n const observations: Array<{ text: string; createdAt: string }> = [];\n if (allObsIds.size > 0) {\n const obsIdList = [...allObsIds];\n const placeholders = obsIdList.map(() => '?').join(', ');\n const obsRows = db\n .prepare(\n `SELECT content, created_at FROM observations WHERE id IN (${placeholders}) AND deleted_at IS NULL ORDER BY created_at DESC LIMIT 10`,\n )\n .all(...obsIdList) as ObsSnippetRow[];\n\n for (const row of obsRows) {\n observations.push({\n text: row.content,\n createdAt: row.created_at,\n });\n }\n }\n\n // 5. Format and return\n const verbosity = loadToolVerbosityConfig().level;\n\n if (verbosity === 1) {\n const totalTraversals = [...traversalsByNode.values()].reduce(\n (sum, arr) => sum + arr.length, 0);\n return withNotifications(\n `${rootNodes.length} entities, ${totalTraversals} connections found`,\n );\n }\n\n if (verbosity === 2) {\n // Standard: entity names and types, no observations\n const lines: string[] = [];\n lines.push('## Entities Found');\n lines.push('');\n for (const node of rootNodes) {\n const traversals = traversalsByNode.get(node.id) ?? [];\n lines.push(\n `- ${formatEntityType(node.type)} ${node.name} (${traversals.length} connections)`,\n );\n }\n return withNotifications(lines.join('\\n'));\n }\n\n // Verbose: full output\n const formatted = formatResults(\n rootNodes,\n traversalsByNode,\n observations,\n args.query,\n );\n\n debug('mcp', 'query_graph: returning', {\n rootNodes: rootNodes.length,\n totalTraversals: [...traversalsByNode.values()].reduce(\n (sum, arr) => sum + arr.length,\n 0,\n ),\n observations: observations.length,\n });\n\n return withNotifications(formatted);\n } catch (err) {\n const message =\n err instanceof Error ? err.message : 'Unknown error';\n debug('mcp', 'query_graph: error', { error: message });\n return errorResponse(`Graph query error: ${message}`);\n }\n },\n );\n}\n","/**\n * MCP tool handler for graph statistics and health metrics.\n *\n * Provides a dashboard view of the knowledge graph: node/edge counts,\n * entity type distribution, relationship type distribution, degree stats,\n * hotspots (nodes near edge limit), duplicate candidates, and staleness flags.\n *\n * No input parameters -- this is a read-only dashboard.\n */\n\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type BetterSqlite3 from 'better-sqlite3';\n\nimport { debug } from '../../shared/debug.js';\nimport type { ProjectHashRef } from '../../shared/types.js';\nimport type { NotificationStore } from '../../storage/notifications.js';\nimport {\n type EntityType,\n type RelationshipType,\n ENTITY_TYPES,\n RELATIONSHIP_TYPES,\n MAX_NODE_DEGREE,\n} from '../../graph/types.js';\nimport { initGraphSchema } from '../../graph/schema.js';\nimport { initStalenessSchema } from '../../graph/staleness.js';\n\n// =============================================================================\n// Types\n// =============================================================================\n\nexport interface GraphStatsOutput {\n total_nodes: number;\n total_edges: number;\n by_entity_type: Record<string, number>;\n by_relationship_type: Record<string, number>;\n avg_degree: number;\n max_degree: { node_name: string; node_type: EntityType; degree: number } | null;\n hotspots: Array<{ name: string; type: EntityType; degree: number }>;\n duplicate_candidates: number;\n staleness_flags: number;\n}\n\n// =============================================================================\n// Internal Row Types\n// =============================================================================\n\ninterface CountRow {\n type: string;\n cnt: number;\n}\n\ninterface DegreeRow {\n node_id: string;\n node_name: string;\n node_type: string;\n degree: number;\n}\n\n// =============================================================================\n// Stats Collection\n// =============================================================================\n\n/**\n * Collects comprehensive graph statistics directly from the database.\n * Does not depend on constraints module (which may not be built yet).\n */\nfunction collectGraphStats(db: BetterSqlite3.Database, projectHash: string): GraphStatsOutput {\n // Total counts (project-scoped)\n const totalNodes =\n (db.prepare('SELECT COUNT(*) as cnt FROM graph_nodes WHERE project_hash = ?').get(projectHash) as { cnt: number })\n .cnt;\n const totalEdges =\n (db.prepare('SELECT COUNT(*) as cnt FROM graph_edges WHERE project_hash = ?').get(projectHash) as { cnt: number })\n .cnt;\n\n // By entity type\n const entityCounts = db\n .prepare('SELECT type, COUNT(*) as cnt FROM graph_nodes WHERE project_hash = ? GROUP BY type')\n .all(projectHash) as CountRow[];\n const byEntityType: Record<string, number> = {};\n for (const t of ENTITY_TYPES) {\n byEntityType[t] = 0;\n }\n for (const row of entityCounts) {\n byEntityType[row.type] = row.cnt;\n }\n\n // By relationship type\n const relCounts = db\n .prepare('SELECT type, COUNT(*) as cnt FROM graph_edges WHERE project_hash = ? GROUP BY type')\n .all(projectHash) as CountRow[];\n const byRelType: Record<string, number> = {};\n for (const t of RELATIONSHIP_TYPES) {\n byRelType[t] = 0;\n }\n for (const row of relCounts) {\n byRelType[row.type] = row.cnt;\n }\n\n // Degree statistics\n const avgDegree =\n totalNodes > 0 ? (totalEdges * 2) / totalNodes : 0;\n\n // Max degree node -- count edges in both directions per node (project-scoped)\n const degreeRows = db\n .prepare(\n `SELECT n.id as node_id, n.name as node_name, n.type as node_type,\n (SELECT COUNT(*) FROM graph_edges WHERE (source_id = n.id OR target_id = n.id) AND project_hash = ?) as degree\n FROM graph_nodes n\n WHERE n.project_hash = ?\n ORDER BY degree DESC\n LIMIT 10`,\n )\n .all(projectHash, projectHash) as DegreeRow[];\n\n let maxDegreeEntry: { node_name: string; node_type: EntityType; degree: number } | null =\n null;\n const hotspots: Array<{ name: string; type: EntityType; degree: number }> = [];\n const hotspotThreshold = Math.floor(MAX_NODE_DEGREE * 0.8); // 80% of limit\n\n for (const row of degreeRows) {\n if (!maxDegreeEntry || row.degree > maxDegreeEntry.degree) {\n maxDegreeEntry = {\n node_name: row.node_name,\n node_type: row.node_type as EntityType,\n degree: row.degree,\n };\n }\n if (row.degree >= hotspotThreshold) {\n hotspots.push({\n name: row.node_name,\n type: row.node_type as EntityType,\n degree: row.degree,\n });\n }\n }\n\n // Duplicate candidates: nodes with same name but different type (project-scoped)\n const dupCount =\n (\n db\n .prepare(\n `SELECT COUNT(*) as cnt FROM (\n SELECT name FROM graph_nodes WHERE project_hash = ? GROUP BY name HAVING COUNT(DISTINCT type) > 1\n )`,\n )\n .get(projectHash) as { cnt: number }\n ).cnt;\n\n // Staleness flags count\n let stalenessCount = 0;\n try {\n initStalenessSchema(db);\n stalenessCount =\n (\n db\n .prepare(\n 'SELECT COUNT(*) as cnt FROM staleness_flags WHERE resolved = 0',\n )\n .get() as { cnt: number }\n ).cnt;\n } catch {\n // staleness_flags table may not exist yet\n stalenessCount = 0;\n }\n\n return {\n total_nodes: totalNodes,\n total_edges: totalEdges,\n by_entity_type: byEntityType,\n by_relationship_type: byRelType,\n avg_degree: Math.round(avgDegree * 10) / 10,\n max_degree: maxDegreeEntry,\n hotspots,\n duplicate_candidates: dupCount,\n staleness_flags: stalenessCount,\n };\n}\n\n// =============================================================================\n// Formatting\n// =============================================================================\n\n/**\n * Formats graph stats as a readable dashboard for Claude.\n */\nfunction formatStats(stats: GraphStatsOutput): string {\n const lines: string[] = [];\n\n // Header\n lines.push('## Knowledge Graph Stats');\n lines.push(\n `Nodes: ${stats.total_nodes} | Edges: ${stats.total_edges} | Avg degree: ${stats.avg_degree}`,\n );\n lines.push('');\n\n // Entity Distribution\n lines.push('### Entity Distribution');\n const entityParts: string[] = [];\n for (const t of ENTITY_TYPES) {\n const count = stats.by_entity_type[t] ?? 0;\n if (count > 0) {\n entityParts.push(`${t}: ${count}`);\n }\n }\n lines.push(entityParts.length > 0 ? entityParts.join(' | ') : 'No entities yet');\n lines.push('');\n\n // Relationship Distribution\n lines.push('### Relationship Distribution');\n const relParts: string[] = [];\n for (const t of RELATIONSHIP_TYPES) {\n const count = stats.by_relationship_type[t] ?? 0;\n if (count > 0) {\n relParts.push(`${t}: ${count}`);\n }\n }\n lines.push(relParts.length > 0 ? relParts.join(' | ') : 'No relationships yet');\n lines.push('');\n\n // Health\n lines.push('### Health');\n if (stats.hotspots.length > 0) {\n const hotspotStr = stats.hotspots\n .map((h) => `${h.name} (${h.degree} edges)`)\n .join(', ');\n lines.push(`Hotspots (near ${MAX_NODE_DEGREE}-edge limit): ${hotspotStr}`);\n } else {\n lines.push('Hotspots: none (all nodes well within edge limits)');\n }\n lines.push(`Duplicate candidates: ${stats.duplicate_candidates} name${stats.duplicate_candidates !== 1 ? 's' : ''}`);\n lines.push(`Stale observations: ${stats.staleness_flags}`);\n\n if (stats.max_degree) {\n lines.push('');\n lines.push(\n `Most connected: ${stats.max_degree.node_name} (${stats.max_degree.node_type}, ${stats.max_degree.degree} edges)`,\n );\n }\n\n return lines.join('\\n');\n}\n\n// =============================================================================\n// Response Helpers\n// =============================================================================\n\nfunction prependNotifications(\n notificationStore: NotificationStore | null,\n projectHash: string,\n responseText: string,\n): string {\n if (!notificationStore) return responseText;\n const pending = notificationStore.consumePending(projectHash);\n if (pending.length === 0) return responseText;\n const banner = pending.map((n) => `[Laminark] ${n.message}`).join('\\n');\n return banner + '\\n\\n' + responseText;\n}\n\nfunction textResponse(text: string) {\n return { content: [{ type: 'text' as const, text }] };\n}\n\n// =============================================================================\n// Tool Registration\n// =============================================================================\n\n/**\n * Registers the graph_stats MCP tool on the server.\n *\n * Returns comprehensive knowledge graph health metrics: entity/relationship\n * type distribution, degree statistics, hotspot nodes, duplicate candidates,\n * and staleness flags. No input parameters -- dashboard view.\n */\nexport function registerGraphStats(\n server: McpServer,\n db: BetterSqlite3.Database,\n projectHashRef: ProjectHashRef,\n notificationStore: NotificationStore | null = null,\n): void {\n // Ensure graph schema is initialized\n initGraphSchema(db);\n\n server.registerTool(\n 'graph_stats',\n {\n title: 'Graph Statistics',\n description:\n 'Get knowledge graph statistics: entity counts, relationship distribution, health metrics. Use to understand the state of accumulated knowledge.',\n inputSchema: {},\n },\n async () => {\n const projectHash = projectHashRef.current;\n try {\n debug('mcp', 'graph_stats: request');\n\n const stats = collectGraphStats(db, projectHash);\n const formatted = formatStats(stats);\n\n debug('mcp', 'graph_stats: returning', {\n nodes: stats.total_nodes,\n edges: stats.total_edges,\n });\n\n return textResponse(\n prependNotifications(notificationStore, projectHash, formatted),\n );\n } catch (err) {\n const message =\n err instanceof Error ? err.message : 'Unknown error';\n debug('mcp', 'graph_stats: error', { error: message });\n return textResponse(`Graph stats error: ${message}`);\n }\n },\n );\n}\n","/**\n * MCP tool handler for database hygiene analysis and cleanup.\n *\n * Analyzes observations for deletion signals, scores candidates, and\n * optionally purges them. Default mode is simulate (dry-run).\n */\n\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport type BetterSqlite3 from 'better-sqlite3';\nimport { z } from 'zod';\n\nimport { debug } from '../../shared/debug.js';\nimport type { ProjectHashRef } from '../../shared/types.js';\nimport type { NotificationStore } from '../../storage/notifications.js';\nimport {\n analyzeObservations,\n executePurge,\n type HygieneCandidate,\n type HygieneReport,\n} from '../../graph/hygiene-analyzer.js';\n\n// =============================================================================\n// Formatting\n// =============================================================================\n\nfunction formatReport(report: HygieneReport, mode: string, tier: string): string {\n const lines: string[] = [];\n\n lines.push('## Database Hygiene Report');\n lines.push(`Analyzed ${report.totalObservations.toLocaleString()} observations`);\n lines.push('');\n\n // Summary table\n lines.push('### Summary');\n lines.push('| Tier | Count | Action |');\n lines.push('|------|-------|--------|');\n lines.push(`| High (>= 0.7) | ${report.summary.high} | Safe to purge |`);\n lines.push(`| Medium (0.5-0.69) | ${report.summary.medium} | Review recommended |`);\n if (report.summary.low > 0) {\n lines.push(`| Low (< 0.5) | ${report.summary.low} | Kept |`);\n }\n lines.push(`| Orphan graph nodes | ${report.summary.orphanNodeCount} | Dead references |`);\n lines.push('');\n\n if (report.candidates.length === 0) {\n lines.push('No candidates found matching the selected tier.');\n return lines.join('\\n');\n }\n\n // Group candidates by session\n const bySession = new Map<string, HygieneCandidate[]>();\n for (const c of report.candidates) {\n const key = c.sessionId ?? '(no session)';\n const list = bySession.get(key) ?? [];\n list.push(c);\n bySession.set(key, list);\n }\n\n const tierLabel = tier === 'all' ? 'All' : tier === 'medium' ? 'Medium+' : 'High';\n lines.push(`### ${tierLabel} Confidence Candidates (showing ${report.candidates.length})`);\n lines.push('');\n\n for (const [sessionId, candidates] of bySession) {\n const sessionDate = candidates[0]?.createdAt?.substring(0, 10) ?? '';\n lines.push(`#### Session: ${sessionId.substring(0, 8)} (${sessionDate}, ${candidates.length} obs)`);\n lines.push('| ID | Kind | Source | Confidence | Signals | Preview |');\n lines.push('|----|------|--------|------------|---------|---------|');\n\n for (const c of candidates) {\n const signals: string[] = [];\n if (c.signals.orphaned) signals.push('orphaned');\n if (c.signals.islandNode) signals.push('island');\n if (c.signals.noiseClassified) signals.push('noise');\n if (c.signals.shortContent) signals.push('short');\n if (c.signals.autoCaptured) signals.push('auto');\n if (c.signals.stale) signals.push('stale');\n\n const preview = c.contentPreview\n .replace(/\\|/g, '\\\\|')\n .replace(/\\n/g, ' ');\n\n lines.push(\n `| ${c.shortId} | ${c.kind} | ${c.source} | ${c.confidence.toFixed(2)} | ${signals.join(',') || '-'} | ${preview} |`,\n );\n }\n lines.push('');\n }\n\n if (mode === 'simulate') {\n lines.push(`_Dry run — no data modified. Use \\`hygiene(mode=\"purge\", tier=\"${tier}\")\\` to execute._`);\n }\n\n return lines.join('\\n');\n}\n\nfunction formatPurgeResult(\n observationsPurged: number,\n orphanNodesRemoved: number,\n tier: string,\n): string {\n const lines: string[] = [];\n lines.push('## Hygiene Purge Complete');\n lines.push(`- Tier: ${tier}`);\n lines.push(`- Observations soft-deleted: ${observationsPurged}`);\n lines.push(`- Orphan graph nodes removed: ${orphanNodesRemoved}`);\n return lines.join('\\n');\n}\n\n// =============================================================================\n// Response Helpers\n// =============================================================================\n\nfunction prependNotifications(\n notificationStore: NotificationStore | null,\n projectHash: string,\n responseText: string,\n): string {\n if (!notificationStore) return responseText;\n const pending = notificationStore.consumePending(projectHash);\n if (pending.length === 0) return responseText;\n const banner = pending.map((n) => `[Laminark] ${n.message}`).join('\\n');\n return banner + '\\n\\n' + responseText;\n}\n\nfunction textResponse(text: string) {\n return { content: [{ type: 'text' as const, text }] };\n}\n\n// =============================================================================\n// Tool Registration\n// =============================================================================\n\nexport function registerHygiene(\n server: McpServer,\n db: BetterSqlite3.Database,\n projectHashRef: ProjectHashRef,\n notificationStore: NotificationStore | null = null,\n): void {\n server.registerTool(\n 'hygiene',\n {\n title: 'Database Hygiene',\n description:\n 'Analyze observations for deletion candidates with confidence scoring. ' +\n 'Simulate mode (default) produces a dry-run report. Purge mode soft-deletes candidates and removes dead orphan graph nodes.',\n inputSchema: {\n mode: z.enum(['simulate', 'purge']).default('simulate')\n .describe('simulate = dry-run report, purge = execute deletions'),\n tier: z.enum(['high', 'medium', 'all']).default('high')\n .describe('Which confidence tier to act on'),\n session_id: z.string().optional()\n .describe('Optional: scope analysis to a single session'),\n limit: z.number().int().min(1).max(200).default(50)\n .describe('Max results to return'),\n },\n },\n async (args) => {\n const projectHash = projectHashRef.current;\n try {\n const mode = args.mode ?? 'simulate';\n const tier = args.tier ?? 'high';\n const sessionId = args.session_id;\n const limit = args.limit ?? 50;\n\n debug('hygiene', 'Request', { mode, tier, sessionId, limit });\n\n // Determine minimum tier for analysis\n const minTier = tier === 'all' ? 'low' as const : tier;\n\n const report = analyzeObservations(db, projectHash, {\n sessionId,\n limit,\n minTier,\n });\n\n if (mode === 'purge') {\n const result = executePurge(db, projectHash, report, tier);\n const formatted = formatPurgeResult(\n result.observationsPurged,\n result.orphanNodesRemoved,\n tier,\n );\n return textResponse(\n prependNotifications(notificationStore, projectHash, formatted),\n );\n }\n\n const formatted = formatReport(report, mode, tier);\n return textResponse(\n prependNotifications(notificationStore, projectHash, formatted),\n );\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Unknown error';\n debug('hygiene', 'Error', { error: message });\n return textResponse(`Hygiene analysis error: ${message}`);\n }\n },\n );\n}\n","/**\n * MCP tool handler for Laminark system status.\n *\n * Returns a compact dashboard: connection info, memory counts,\n * token estimates, and capability flags. No input parameters.\n *\n * The heavy lifting (SQL queries, formatting) lives in StatusCache.\n * This handler just returns the pre-built string.\n */\n\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\n\nimport { debug } from '../../shared/debug.js';\nimport type { ProjectHashRef } from '../../shared/types.js';\nimport type { NotificationStore } from '../../storage/notifications.js';\nimport type { StatusCache } from '../status-cache.js';\nimport { loadToolVerbosityConfig } from '../../config/tool-verbosity-config.js';\n\n// =============================================================================\n// Response Helpers\n// =============================================================================\n\nfunction prependNotifications(\n notificationStore: NotificationStore | null,\n projectHash: string,\n responseText: string,\n): string {\n if (!notificationStore) return responseText;\n const pending = notificationStore.consumePending(projectHash);\n if (pending.length === 0) return responseText;\n const banner = pending.map((n) => `[Laminark] ${n.message}`).join('\\n');\n return banner + '\\n\\n' + responseText;\n}\n\nfunction textResponse(text: string) {\n return { content: [{ type: 'text' as const, text }] };\n}\n\n// =============================================================================\n// Tool Registration\n// =============================================================================\n\nexport function registerStatus(\n server: McpServer,\n cache: StatusCache,\n projectHashRef: ProjectHashRef,\n notificationStore: NotificationStore | null = null,\n): void {\n server.registerTool(\n 'status',\n {\n title: 'Laminark Status',\n description:\n 'Show Laminark system status: connection info, memory count, token estimates, and capabilities.',\n inputSchema: {},\n },\n async () => {\n const projectHash = projectHashRef.current;\n try {\n debug('mcp', 'status: request (cached)');\n\n const verbosity = loadToolVerbosityConfig().level;\n\n if (verbosity === 1) {\n return textResponse(\n prependNotifications(notificationStore, projectHash, 'Laminark: connected'),\n );\n }\n\n const formatted = cache.getFormatted();\n\n if (verbosity === 2) {\n // Standard: first few lines only (connection + counts)\n const lines = formatted.split('\\n').slice(0, 8);\n return textResponse(\n prependNotifications(notificationStore, projectHash, lines.join('\\n')),\n );\n }\n\n // Verbose: full output\n return textResponse(\n prependNotifications(notificationStore, projectHash, formatted),\n );\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Unknown error';\n debug('mcp', 'status: error', { error: message });\n return textResponse(`Status error: ${message}`);\n }\n },\n );\n}\n","/**\n * Pre-built status cache for the Laminark MCP status tool.\n *\n * Queries the database once at construction, stores the formatted markdown\n * string, and only re-queries when explicitly marked dirty (after writes).\n * The tool handler returns the cached string with zero SQL overhead.\n */\n\nimport type BetterSqlite3 from 'better-sqlite3';\n\nimport { debug } from '../shared/debug.js';\nimport type { ProjectHashRef } from '../shared/types.js';\nimport { getDbPath } from '../shared/config.js';\nimport { estimateTokens } from './token-budget.js';\n\n// =============================================================================\n// Uptime formatting\n// =============================================================================\n\nfunction formatUptime(seconds: number): string {\n const h = Math.floor(seconds / 3600);\n const m = Math.floor((seconds % 3600) / 60);\n const s = seconds % 60;\n if (h > 0) return `${h}h ${m}m`;\n if (m > 0) return `${m}m ${s}s`;\n return `${s}s`;\n}\n\n// =============================================================================\n// StatusCache\n// =============================================================================\n\nexport class StatusCache {\n private db: BetterSqlite3.Database;\n private projectHashRef: ProjectHashRef;\n private projectPath: string;\n private hasVectorSupport: boolean;\n private isWorkerReady: () => boolean;\n\n /** Pre-built markdown string (everything except the uptime line). */\n private cachedBody = '';\n /** Uptime snapshot at the time cachedBody was built. */\n private builtAtUptime = 0;\n private dirty = false;\n\n constructor(\n db: BetterSqlite3.Database,\n projectHashRef: ProjectHashRef,\n projectPath: string,\n hasVectorSupport: boolean,\n isWorkerReady: () => boolean,\n ) {\n this.db = db;\n this.projectHashRef = projectHashRef;\n this.projectPath = projectPath;\n this.hasVectorSupport = hasVectorSupport;\n this.isWorkerReady = isWorkerReady;\n\n this.rebuild();\n }\n\n /** Flag that underlying data has changed (cheap -- no queries). */\n markDirty(): void {\n this.dirty = true;\n }\n\n /** Re-query and rebuild if dirty. Call from a background timer. */\n refreshIfDirty(): void {\n if (!this.dirty) return;\n this.dirty = false;\n this.rebuild();\n }\n\n /**\n * Return the formatted status string instantly.\n * Patches the uptime line inline so it's always current.\n */\n getFormatted(): string {\n const currentUptime = formatUptime(Math.floor(process.uptime()));\n const workerReady = this.isWorkerReady();\n return this.cachedBody\n .replace(\n `Uptime: ${formatUptime(this.builtAtUptime)}`,\n `Uptime: ${currentUptime}`,\n )\n .replace(\n /Embedding worker: (?:ready|degraded)/,\n `Embedding worker: ${workerReady ? 'ready' : 'degraded'}`,\n );\n }\n\n // ---------------------------------------------------------------------------\n // Internal: query + format\n // ---------------------------------------------------------------------------\n\n private rebuild(): void {\n try {\n const ph = this.projectHashRef.current;\n\n const totalObs = (\n this.db.prepare(\n 'SELECT COUNT(*) as cnt FROM observations WHERE project_hash = ? AND deleted_at IS NULL',\n ).get(ph) as { cnt: number }\n ).cnt;\n\n const embeddedObs = (\n this.db.prepare(\n 'SELECT COUNT(*) as cnt FROM observations WHERE project_hash = ? AND deleted_at IS NULL AND embedding_model IS NOT NULL',\n ).get(ph) as { cnt: number }\n ).cnt;\n\n const deletedObs = (\n this.db.prepare(\n 'SELECT COUNT(*) as cnt FROM observations WHERE project_hash = ? AND deleted_at IS NOT NULL',\n ).get(ph) as { cnt: number }\n ).cnt;\n\n const sessions = (\n this.db.prepare(\n 'SELECT COUNT(DISTINCT session_id) as cnt FROM observations WHERE project_hash = ? AND session_id IS NOT NULL AND deleted_at IS NULL',\n ).get(ph) as { cnt: number }\n ).cnt;\n\n let stashes = 0;\n try {\n stashes = (\n this.db.prepare(\n \"SELECT COUNT(*) as cnt FROM context_stashes WHERE project_hash = ? AND status = 'stashed'\",\n ).get(ph) as { cnt: number }\n ).cnt;\n } catch {\n // Table may not exist\n }\n\n const totalChars = (\n this.db.prepare(\n 'SELECT COALESCE(SUM(LENGTH(content)), 0) as chars FROM observations WHERE project_hash = ? AND deleted_at IS NULL',\n ).get(ph) as { chars: number }\n ).chars;\n\n let graphNodes = 0;\n let graphEdges = 0;\n try {\n graphNodes = (\n this.db.prepare('SELECT COUNT(*) as cnt FROM graph_nodes').get() as { cnt: number }\n ).cnt;\n graphEdges = (\n this.db.prepare('SELECT COUNT(*) as cnt FROM graph_edges').get() as { cnt: number }\n ).cnt;\n } catch {\n // Graph tables may not exist\n }\n\n const uptimeNow = Math.floor(process.uptime());\n const tokenEstimate = estimateTokens(String('x').repeat(totalChars));\n const workerReady = this.isWorkerReady();\n\n // Build markdown\n const lines: string[] = [];\n lines.push('## Laminark Status');\n lines.push('');\n lines.push('### Connection');\n lines.push(`Project: ${this.projectPath}`);\n lines.push(`Project hash: ${ph}`);\n lines.push(`Database: ${getDbPath()}`);\n lines.push(`Uptime: ${formatUptime(uptimeNow)}`);\n lines.push('');\n lines.push('### Capabilities');\n lines.push(`Vector search: ${this.hasVectorSupport ? 'active' : 'unavailable (keyword-only)'}`);\n lines.push(`Embedding worker: ${workerReady ? 'ready' : 'degraded'}`);\n lines.push('');\n lines.push('### Memories');\n lines.push(`Observations: ${totalObs} (${embeddedObs} embedded, ${deletedObs} deleted)`);\n lines.push(`Sessions: ${sessions}`);\n lines.push(`Stashed threads: ${stashes}`);\n lines.push('');\n lines.push('### Tokens');\n lines.push(`Estimated total: ~${tokenEstimate.toLocaleString()} tokens across all memories`);\n lines.push('');\n lines.push('### Knowledge Graph');\n lines.push(`Nodes: ${graphNodes} | Edges: ${graphEdges}`);\n\n this.cachedBody = lines.join('\\n');\n this.builtAtUptime = uptimeNow;\n\n debug('mcp', 'status-cache: rebuilt', { memories: totalObs, tokens: tokenEstimate });\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n debug('mcp', 'status-cache: rebuild error', { error: msg });\n // Keep previous cached body on failure\n }\n }\n}\n","/**\n * MCP tool handler for discovering available tools by keyword or semantic search.\n *\n * Allows Claude to search the tool registry for MCP servers, slash commands,\n * skills, and plugins. Supports hybrid search (FTS5 keyword + vec0 vector)\n * with scope filtering and deduplication of server-level vs individual tool entries.\n */\n\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { z } from 'zod';\n\nimport { debug } from '../../shared/debug.js';\nimport type { ProjectHashRef } from '../../shared/types.js';\nimport type { ToolSearchResult } from '../../shared/tool-types.js';\nimport type { ToolRegistryRepository } from '../../storage/tool-registry.js';\nimport type { AnalysisWorker } from '../../analysis/worker-bridge.js';\nimport type { NotificationStore } from '../../storage/notifications.js';\nimport { enforceTokenBudget, TOKEN_BUDGET } from '../token-budget.js';\n\n// ---------------------------------------------------------------------------\n// Response helpers\n// ---------------------------------------------------------------------------\n\nfunction textResponse(text: string) {\n return { content: [{ type: 'text' as const, text }] };\n}\n\nfunction errorResponse(text: string) {\n return { content: [{ type: 'text' as const, text }], isError: true };\n}\n\nfunction prependNotifications(\n notificationStore: NotificationStore | null,\n projectHash: string,\n responseText: string,\n): string {\n if (!notificationStore) return responseText;\n const pending = notificationStore.consumePending(projectHash);\n if (pending.length === 0) return responseText;\n const banner = pending.map(n => `[Laminark] ${n.message}`).join('\\n');\n return banner + '\\n\\n' + responseText;\n}\n\n// ---------------------------------------------------------------------------\n// Formatting helpers\n// ---------------------------------------------------------------------------\n\nfunction formatToolResult(result: ToolSearchResult, index: number): string {\n const { tool, score } = result;\n const description = tool.description ? ` -- ${tool.description}` : '';\n const statusTag = tool.status !== 'active' ? ` [${tool.status}]` : '';\n const usageStr = tool.usage_count > 0 ? `${tool.usage_count} uses` : 'never used';\n const lastUsedStr = tool.last_used_at ? `last: ${tool.last_used_at.slice(0, 10)}` : 'never';\n return `${index}. ${tool.name}${statusTag}${description}\\n [${tool.scope}] | ${usageStr} | ${lastUsedStr} | score: ${score.toFixed(2)}`;\n}\n\n// ---------------------------------------------------------------------------\n// Tool registration\n// ---------------------------------------------------------------------------\n\n/**\n * Registers the discover_tools MCP tool on the server.\n *\n * Allows Claude to search the tool registry by keyword or semantic description,\n * with optional scope filtering. Returns ranked results with scope, usage count,\n * and last used timestamp metadata.\n */\nexport function registerDiscoverTools(\n server: McpServer,\n toolRegistry: ToolRegistryRepository,\n worker: AnalysisWorker | null,\n hasVectorSupport: boolean,\n notificationStore: NotificationStore | null,\n projectHashRef: ProjectHashRef,\n): void {\n server.registerTool(\n 'discover_tools',\n {\n title: 'Discover Tools',\n description:\n 'Search the tool registry to find available tools by keyword or description. Supports semantic search -- \"file manipulation\" finds tools described as \"read and write files\". Returns scope, usage count, and last used timestamp for each result.',\n inputSchema: {\n query: z\n .string()\n .min(1)\n .describe('Search query: keywords or natural language description'),\n scope: z\n .enum(['global', 'project', 'plugin'])\n .optional()\n .describe('Optional scope filter. Omit to search all scopes.'),\n limit: z\n .number()\n .int()\n .min(1)\n .max(50)\n .default(20)\n .describe('Maximum results to return (default: 20)'),\n },\n },\n async (args) => {\n const projectHash = projectHashRef.current;\n const withNotifications = (text: string) =>\n textResponse(prependNotifications(notificationStore, projectHash, text));\n\n try {\n debug('mcp', 'discover_tools: request', {\n query: args.query,\n scope: args.scope,\n limit: args.limit,\n });\n\n // Search the tool registry (hybrid FTS5 + vector via RRF)\n const searchResults = await toolRegistry.searchTools(args.query, {\n scope: args.scope,\n limit: args.limit,\n worker,\n hasVectorSupport,\n });\n\n // Zero results\n if (searchResults.length === 0) {\n const scopeContext = args.scope ? ` in scope \"${args.scope}\"` : '';\n return withNotifications(\n `No tools found matching \"${args.query}\"${scopeContext}.`,\n );\n }\n\n // Deduplicate: prefer mcp_server entries over individual mcp_tool entries\n const seenServers = new Set<string>();\n for (const result of searchResults) {\n if (result.tool.tool_type === 'mcp_server') {\n seenServers.add(result.tool.server_name ?? result.tool.name);\n }\n }\n const deduped = searchResults.filter(result => {\n if (\n result.tool.tool_type === 'mcp_tool' &&\n result.tool.server_name &&\n seenServers.has(result.tool.server_name)\n ) {\n return false;\n }\n return true;\n });\n\n // Format results with token budget enforcement\n const budgetResult = enforceTokenBudget(\n deduped,\n (r) => formatToolResult(r, deduped.indexOf(r) + 1),\n TOKEN_BUDGET,\n );\n\n const body = budgetResult.items\n .map((r, i) => formatToolResult(r, i + 1))\n .join('\\n');\n\n // Metadata footer\n const scopeLabel = args.scope ?? 'all';\n let footer = `---\\n${deduped.length} result(s) | query: \"${args.query}\" | scope: ${scopeLabel}`;\n if (budgetResult.truncated) {\n footer += ' | truncated';\n }\n\n debug('mcp', 'discover_tools: returning', {\n total: searchResults.length,\n deduped: deduped.length,\n displayed: budgetResult.items.length,\n truncated: budgetResult.truncated,\n });\n\n return withNotifications(`${body}\\n${footer}`);\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Unknown error';\n debug('mcp', 'discover_tools: error', { error: message });\n return errorResponse(`Discover tools error: ${message}`);\n }\n },\n );\n}\n","/**\n * MCP tool handler for bulk-registering available tools reported by Claude.\n *\n * At session start, Claude is prompted to call this tool with every tool it\n * has access to (built-in + MCP). This lets Laminark dynamically discover\n * the full tool surface without hardcoding names or relying on organic\n * PostToolUse discovery.\n */\n\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { z } from 'zod';\n\nimport { debug } from '../../shared/debug.js';\nimport type { ProjectHashRef } from '../../shared/types.js';\nimport type { ToolRegistryRepository } from '../../storage/tool-registry.js';\nimport { inferToolType, inferScope, extractServerName } from '../../hooks/tool-name-parser.js';\n\n// ---------------------------------------------------------------------------\n// Response helpers\n// ---------------------------------------------------------------------------\n\nfunction textResponse(text: string) {\n return { content: [{ type: 'text' as const, text }] };\n}\n\n// ---------------------------------------------------------------------------\n// Tool registration\n// ---------------------------------------------------------------------------\n\n/**\n * Registers the report_available_tools MCP tool on the server.\n *\n * Accepts an array of tool names (with optional descriptions) and upserts\n * each into the tool registry. Tool type, scope, and server name are inferred\n * from the tool name using the same parser as PostToolUse organic discovery.\n */\nexport function registerReportTools(\n server: McpServer,\n toolRegistry: ToolRegistryRepository,\n projectHashRef: ProjectHashRef,\n): void {\n server.registerTool(\n 'report_available_tools',\n {\n title: 'Report Available Tools',\n description:\n 'Register all tools available in this session with Laminark. Call this once at session start with every tool name you have access to (built-in and MCP). This populates the tool registry for discovery and routing.',\n inputSchema: {\n tools: z\n .array(\n z.object({\n name: z.string().min(1).describe('Tool name exactly as it appears (e.g., \"Read\", \"mcp__playwright__browser_click\")'),\n description: z.string().optional().describe('Brief description of the tool'),\n }),\n )\n .min(1)\n .describe('Array of tools available in this session'),\n },\n },\n async (args) => {\n const projectHash = projectHashRef.current;\n try {\n let registered = 0;\n let skipped = 0;\n\n for (const tool of args.tools) {\n // Skip Laminark's own tools — they're already registered\n if (\n tool.name.startsWith('mcp__plugin_laminark_') ||\n tool.name.startsWith('mcp__laminark__')\n ) {\n skipped++;\n continue;\n }\n\n const toolType = inferToolType(tool.name);\n const scope = inferScope(tool.name);\n const serverName = extractServerName(tool.name);\n\n toolRegistry.upsert({\n name: tool.name,\n toolType,\n scope,\n source: 'config:session-report',\n projectHash: scope === 'global' ? null : projectHash,\n description: tool.description ?? null,\n serverName,\n triggerHints: null,\n });\n registered++;\n }\n\n debug('mcp', 'report_available_tools: completed', {\n total: args.tools.length,\n registered,\n skipped,\n });\n\n return textResponse(\n `Registered ${registered} tools in the tool registry.${skipped > 0 ? ` Skipped ${skipped} Laminark tools (already known).` : ''}`,\n );\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Unknown error';\n debug('mcp', 'report_available_tools: error', { error: message });\n return {\n content: [{ type: 'text' as const, text: `Report tools error: ${message}` }],\n isError: true,\n };\n }\n },\n );\n}\n","/**\n * MCP tool handlers for debug resolution path management.\n *\n * Provides four tools for explicit user control over debug path lifecycle:\n * - path_start: Manually start tracking a debug path (UI-01)\n * - path_resolve: Manually resolve the active debug path (UI-02)\n * - path_show: Show a debug path with waypoints and KISS summary (UI-03)\n * - path_list: List recent debug paths with optional status filter (UI-04)\n */\n\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { z } from 'zod';\n\nimport { debug } from '../../shared/debug.js';\nimport type { ProjectHashRef } from '../../shared/types.js';\nimport type { NotificationStore } from '../../storage/notifications.js';\nimport type { PathRepository } from '../../paths/path-repository.js';\nimport type { PathTracker } from '../../paths/path-tracker.js';\nimport { loadToolVerbosityConfig, verboseResponse } from '../../config/tool-verbosity-config.js';\n\n// =============================================================================\n// Response Helpers\n// =============================================================================\n\nfunction prependNotifications(\n notificationStore: NotificationStore | null,\n projectHash: string,\n responseText: string,\n): string {\n if (!notificationStore) return responseText;\n const pending = notificationStore.consumePending(projectHash);\n if (pending.length === 0) return responseText;\n const banner = pending.map((n) => `[Laminark] ${n.message}`).join('\\n');\n return banner + '\\n\\n' + responseText;\n}\n\nfunction textResponse(text: string) {\n return { content: [{ type: 'text' as const, text }] };\n}\n\nfunction errorResponse(text: string) {\n return { content: [{ type: 'text' as const, text }], isError: true };\n}\n\n// =============================================================================\n// KISS Summary Formatting\n// =============================================================================\n\ninterface KissSummary {\n kiss_summary: string;\n root_cause: string;\n what_fixed_it: string;\n dimensions: {\n logical: string;\n programmatic: string;\n development: string;\n };\n}\n\nfunction formatKissSummary(raw: string | null): string {\n if (!raw) return 'KISS summary not yet generated';\n\n try {\n const kiss = JSON.parse(raw) as KissSummary;\n const lines: string[] = [];\n lines.push(`**Next time:** ${kiss.kiss_summary}`);\n lines.push(`**Root cause:** ${kiss.root_cause}`);\n lines.push(`**What fixed it:** ${kiss.what_fixed_it}`);\n lines.push(`**Logical:** ${kiss.dimensions.logical}`);\n lines.push(`**Programmatic:** ${kiss.dimensions.programmatic}`);\n lines.push(`**Development:** ${kiss.dimensions.development}`);\n return lines.join('\\n');\n } catch {\n return 'KISS summary not yet generated';\n }\n}\n\n// =============================================================================\n// Tool Registration\n// =============================================================================\n\n/**\n * Registers four debug path MCP tools on the server.\n *\n * Tools: path_start, path_resolve, path_show, path_list\n */\nexport function registerDebugPathTools(\n server: McpServer,\n pathRepo: PathRepository,\n pathTracker: PathTracker,\n notificationStore: NotificationStore | null,\n projectHashRef: ProjectHashRef,\n): void {\n // ---------------------------------------------------------------------------\n // path_start (UI-01)\n // ---------------------------------------------------------------------------\n\n server.registerTool(\n 'path_start',\n {\n title: 'Start Debug Path',\n description:\n \"Explicitly start tracking a debug path. Use when auto-detection hasn't triggered but you're actively debugging.\",\n inputSchema: {\n trigger: z\n .string()\n .describe('Brief description of the issue being debugged'),\n },\n },\n async (args) => {\n const projectHash = projectHashRef.current;\n const withNotifications = (text: string) =>\n textResponse(\n prependNotifications(notificationStore, projectHash, text),\n );\n\n try {\n debug('mcp', 'path_start: request', { trigger: args.trigger });\n\n const existingPathId = pathTracker.getActivePathId();\n const pathId = pathTracker.startManually(args.trigger);\n\n if (!pathId) {\n return errorResponse('Failed to start debug path');\n }\n\n if (existingPathId && existingPathId === pathId) {\n return withNotifications(`Debug path already active: ${pathId}`);\n }\n\n return withNotifications(verboseResponse(\n 'Debug path started.',\n `Debug path started: ${pathId}`,\n `Debug path started: ${pathId}\\nTracking: ${args.trigger}`,\n ));\n } catch (err) {\n const message =\n err instanceof Error ? err.message : 'Unknown error';\n debug('mcp', 'path_start: error', { error: message });\n return errorResponse(`path_start error: ${message}`);\n }\n },\n );\n\n // ---------------------------------------------------------------------------\n // path_resolve (UI-02)\n // ---------------------------------------------------------------------------\n\n server.registerTool(\n 'path_resolve',\n {\n title: 'Resolve Debug Path',\n description:\n \"Explicitly resolve the active debug path with a resolution summary. Use when auto-detection hasn't detected resolution.\",\n inputSchema: {\n resolution: z\n .string()\n .describe('What fixed the issue'),\n },\n },\n async (args) => {\n const projectHash = projectHashRef.current;\n const withNotifications = (text: string) =>\n textResponse(\n prependNotifications(notificationStore, projectHash, text),\n );\n\n try {\n debug('mcp', 'path_resolve: request', { resolution: args.resolution });\n\n const pathId = pathTracker.getActivePathId();\n if (!pathId) {\n return errorResponse('No active debug path to resolve');\n }\n\n pathTracker.resolveManually(args.resolution);\n\n return withNotifications(verboseResponse(\n 'Debug path resolved.',\n `Debug path resolved: ${pathId}`,\n `Debug path resolved: ${pathId}\\nResolution: ${args.resolution}\\nKISS summary generating in background...`,\n ));\n } catch (err) {\n const message =\n err instanceof Error ? err.message : 'Unknown error';\n debug('mcp', 'path_resolve: error', { error: message });\n return errorResponse(`path_resolve error: ${message}`);\n }\n },\n );\n\n // ---------------------------------------------------------------------------\n // path_show (UI-03)\n // ---------------------------------------------------------------------------\n\n server.registerTool(\n 'path_show',\n {\n title: 'Show Debug Path',\n description:\n 'Show a debug path with its waypoints and KISS summary.',\n inputSchema: {\n path_id: z\n .string()\n .optional()\n .describe('Path ID to show. Omit for active path.'),\n },\n },\n async (args) => {\n const projectHash = projectHashRef.current;\n const withNotifications = (text: string) =>\n textResponse(\n prependNotifications(notificationStore, projectHash, text),\n );\n\n try {\n debug('mcp', 'path_show: request', { path_id: args.path_id });\n\n let pathData;\n if (args.path_id) {\n pathData = pathRepo.getPath(args.path_id);\n if (!pathData) {\n return errorResponse(`Debug path not found: ${args.path_id}`);\n }\n } else {\n pathData = pathRepo.getActivePath();\n if (!pathData) {\n return errorResponse('No active debug path');\n }\n }\n\n const verbosity = loadToolVerbosityConfig().level;\n\n if (verbosity === 1) {\n return withNotifications(`Showing debug path: ${pathData.status}`);\n }\n\n const waypoints = pathRepo.getWaypoints(pathData.id);\n\n if (verbosity === 2) {\n // Standard: key fields\n const lines: string[] = [];\n lines.push(`## Debug Path: ${pathData.id}`);\n lines.push(`**Status:** ${pathData.status} | **Trigger:** ${pathData.trigger_summary}`);\n lines.push(`Waypoints: ${waypoints.length}`);\n if (pathData.resolution_summary) lines.push(`Resolution: ${pathData.resolution_summary}`);\n return withNotifications(lines.join('\\n'));\n }\n\n // Verbose: full output\n const lines: string[] = [];\n lines.push(`## Debug Path: ${pathData.id}`);\n lines.push(`Status: ${pathData.status}`);\n lines.push(`Started: ${pathData.started_at}`);\n lines.push(`Trigger: ${pathData.trigger_summary}`);\n lines.push('');\n\n // Waypoints section\n lines.push(`### Waypoints (${waypoints.length})`);\n for (let i = 0; i < waypoints.length; i++) {\n const wp = waypoints[i];\n lines.push(\n `${i + 1}. [${wp.waypoint_type}] ${wp.summary} (${wp.created_at})`,\n );\n }\n lines.push('');\n\n // Resolution section\n lines.push('### Resolution');\n lines.push(pathData.resolution_summary ?? 'Still active');\n lines.push('');\n\n // KISS summary section\n lines.push('### KISS Summary');\n lines.push(formatKissSummary(pathData.kiss_summary));\n\n return withNotifications(lines.join('\\n'));\n } catch (err) {\n const message =\n err instanceof Error ? err.message : 'Unknown error';\n debug('mcp', 'path_show: error', { error: message });\n return errorResponse(`path_show error: ${message}`);\n }\n },\n );\n\n // ---------------------------------------------------------------------------\n // path_list (UI-04)\n // ---------------------------------------------------------------------------\n\n server.registerTool(\n 'path_list',\n {\n title: 'List Debug Paths',\n description:\n 'List recent debug paths, optionally filtered by status.',\n inputSchema: {\n status: z\n .enum(['active', 'resolved', 'abandoned'])\n .optional()\n .describe('Filter by status'),\n limit: z\n .number()\n .int()\n .min(1)\n .max(50)\n .default(10)\n .describe('Max paths to return'),\n },\n },\n async (args) => {\n const projectHash = projectHashRef.current;\n const withNotifications = (text: string) =>\n textResponse(\n prependNotifications(notificationStore, projectHash, text),\n );\n\n try {\n debug('mcp', 'path_list: request', {\n status: args.status,\n limit: args.limit,\n });\n\n let paths = pathRepo.listPaths(args.limit);\n\n // In-memory status filter\n if (args.status) {\n paths = paths.filter((p) => p.status === args.status);\n }\n\n if (paths.length === 0) {\n return withNotifications('No debug paths found');\n }\n\n const verbosity = loadToolVerbosityConfig().level;\n\n if (verbosity === 1) {\n return withNotifications(`${paths.length} debug paths found`);\n }\n\n const lines: string[] = [];\n lines.push('## Debug Paths');\n lines.push('');\n\n if (verbosity === 2) {\n // Standard: compact table\n lines.push('| Status | Trigger |');\n lines.push('|--------|---------|');\n for (const p of paths) {\n const trigger = p.trigger_summary.length > 60\n ? p.trigger_summary.slice(0, 60) + '...'\n : p.trigger_summary;\n lines.push(`| ${p.status} | ${trigger} |`);\n }\n } else {\n // Verbose: full table\n lines.push(\n '| ID (short) | Status | Trigger | Started | Resolved |',\n );\n lines.push(\n '|------------|--------|---------|---------|----------|',\n );\n for (const p of paths) {\n const shortId = p.id.slice(0, 8);\n const trigger = p.trigger_summary.length > 50\n ? p.trigger_summary.slice(0, 50) + '...'\n : p.trigger_summary;\n const resolved = p.resolved_at ?? '-';\n lines.push(\n `| ${shortId} | ${p.status} | ${trigger} | ${p.started_at} | ${resolved} |`,\n );\n }\n }\n\n return withNotifications(lines.join('\\n'));\n } catch (err) {\n const message =\n err instanceof Error ? err.message : 'Unknown error';\n debug('mcp', 'path_list: error', { error: message });\n return errorResponse(`path_list error: ${message}`);\n }\n },\n );\n}\n","/**\n * MCP tool handlers for thought branch management.\n *\n * Provides three tools for querying work history:\n * - query_branches: List/search branches by status or type\n * - show_branch: Show branch detail with observation timeline\n * - branch_summary: Summary of recent work activity\n */\n\nimport type { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';\nimport { z } from 'zod';\n\nimport { debug } from '../../shared/debug.js';\nimport type { ProjectHashRef } from '../../shared/types.js';\nimport type { NotificationStore } from '../../storage/notifications.js';\nimport type { BranchRepository } from '../../branches/branch-repository.js';\nimport type { ObservationRepository } from '../../storage/observations.js';\nimport { loadToolVerbosityConfig } from '../../config/tool-verbosity-config.js';\n\n// =============================================================================\n// Response Helpers\n// =============================================================================\n\nfunction prependNotifications(\n notificationStore: NotificationStore | null,\n projectHash: string,\n responseText: string,\n): string {\n if (!notificationStore) return responseText;\n const pending = notificationStore.consumePending(projectHash);\n if (pending.length === 0) return responseText;\n const banner = pending.map((n) => `[Laminark] ${n.message}`).join('\\n');\n return banner + '\\n\\n' + responseText;\n}\n\nfunction textResponse(text: string) {\n return { content: [{ type: 'text' as const, text }] };\n}\n\nfunction errorResponse(text: string) {\n return { content: [{ type: 'text' as const, text }], isError: true };\n}\n\n// =============================================================================\n// Tool Registration\n// =============================================================================\n\nexport function registerThoughtBranchTools(\n server: McpServer,\n branchRepo: BranchRepository,\n obsRepo: ObservationRepository,\n notificationStore: NotificationStore | null,\n projectHashRef: ProjectHashRef,\n): void {\n // ---------------------------------------------------------------------------\n // query_branches\n // ---------------------------------------------------------------------------\n\n server.registerTool(\n 'query_branches',\n {\n title: 'Query Thought Branches',\n description:\n \"Search and list thought branches - coherent units of work (investigations, bug fixes, features). Use to see work history and what was investigated, fixed, or built.\",\n inputSchema: {\n status: z\n .enum(['active', 'completed', 'abandoned', 'merged'])\n .optional()\n .describe('Filter by branch status'),\n branch_type: z\n .enum(['investigation', 'bug_fix', 'feature', 'refactor', 'research', 'unknown'])\n .optional()\n .describe('Filter by branch type'),\n limit: z\n .number()\n .int()\n .min(1)\n .max(50)\n .default(10)\n .describe('Maximum results to return'),\n },\n },\n async (args) => {\n const projectHash = projectHashRef.current;\n const withNotifications = (text: string) =>\n textResponse(prependNotifications(notificationStore, projectHash, text));\n\n try {\n debug('mcp', 'query_branches: request', {\n status: args.status,\n branch_type: args.branch_type,\n limit: args.limit,\n });\n\n let branches;\n if (args.status) {\n branches = branchRepo.listByStatus(args.status, args.limit);\n } else if (args.branch_type) {\n branches = branchRepo.listByType(args.branch_type, args.limit);\n } else {\n branches = branchRepo.listBranches(args.limit);\n }\n\n if (branches.length === 0) {\n return withNotifications('No thought branches found');\n }\n\n const verbosity = loadToolVerbosityConfig().level;\n\n if (verbosity === 1) {\n return withNotifications(`${branches.length} branches found`);\n }\n\n const lines: string[] = [];\n lines.push('## Thought Branches');\n lines.push('');\n\n if (verbosity === 2) {\n // Standard: key columns, no observation timeline\n lines.push('| Status | Type | Title |');\n lines.push('|--------|------|-------|');\n for (const b of branches) {\n const title = b.title\n ? b.title.length > 50 ? b.title.slice(0, 50) + '...' : b.title\n : '-';\n lines.push(`| ${b.status} | ${b.branch_type} | ${title} |`);\n }\n } else {\n // Verbose: full table\n lines.push(\n '| ID (short) | Status | Type | Stage | Title | Observations | Started |',\n );\n lines.push(\n '|------------|--------|------|-------|-------|-------------|---------|',\n );\n for (const b of branches) {\n const shortId = b.id.slice(0, 8);\n const title = b.title\n ? b.title.length > 40\n ? b.title.slice(0, 40) + '...'\n : b.title\n : '-';\n lines.push(\n `| ${shortId} | ${b.status} | ${b.branch_type} | ${b.arc_stage} | ${title} | ${b.observation_count} | ${b.started_at} |`,\n );\n }\n }\n\n return withNotifications(lines.join('\\n'));\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Unknown error';\n debug('mcp', 'query_branches: error', { error: message });\n return errorResponse(`query_branches error: ${message}`);\n }\n },\n );\n\n // ---------------------------------------------------------------------------\n // show_branch\n // ---------------------------------------------------------------------------\n\n server.registerTool(\n 'show_branch',\n {\n title: 'Show Thought Branch',\n description:\n 'Show detailed view of a thought branch with observation timeline and arc stage annotations. Trace the full arc of a work unit.',\n inputSchema: {\n branch_id: z\n .string()\n .optional()\n .describe('Branch ID to show. Omit for active branch.'),\n },\n },\n async (args) => {\n const projectHash = projectHashRef.current;\n const withNotifications = (text: string) =>\n textResponse(prependNotifications(notificationStore, projectHash, text));\n\n try {\n debug('mcp', 'show_branch: request', { branch_id: args.branch_id });\n\n let branch;\n if (args.branch_id) {\n branch = branchRepo.getBranch(args.branch_id);\n if (!branch) {\n return errorResponse(`Branch not found: ${args.branch_id}`);\n }\n } else {\n branch = branchRepo.getActiveBranch();\n if (!branch) {\n return errorResponse('No active thought branch');\n }\n }\n\n const verbosity = loadToolVerbosityConfig().level;\n const branchTitle = branch.title ?? branch.id.slice(0, 12);\n\n if (verbosity === 1) {\n return withNotifications(`Showing \"${branchTitle}\"`);\n }\n\n const observations = branchRepo.getObservations(branch.id);\n\n if (verbosity === 2) {\n // Standard: key fields, no observation timeline\n const lines: string[] = [];\n lines.push(`## ${branchTitle}`);\n lines.push(`**Status:** ${branch.status} | **Type:** ${branch.branch_type} | **Stage:** ${branch.arc_stage}`);\n if (branch.summary) lines.push(branch.summary);\n lines.push(`Observations: ${observations.length}`);\n return withNotifications(lines.join('\\n'));\n }\n\n // Verbose: full output\n const lines: string[] = [];\n lines.push(`## Thought Branch: ${branchTitle}`);\n lines.push(`**ID:** ${branch.id}`);\n lines.push(`**Status:** ${branch.status}`);\n lines.push(`**Type:** ${branch.branch_type}`);\n lines.push(`**Arc Stage:** ${branch.arc_stage}`);\n lines.push(`**Started:** ${branch.started_at}`);\n if (branch.ended_at) lines.push(`**Ended:** ${branch.ended_at}`);\n if (branch.trigger_source) lines.push(`**Trigger:** ${branch.trigger_source}`);\n if (branch.linked_debug_path_id) {\n lines.push(`**Linked Debug Path:** ${branch.linked_debug_path_id}`);\n }\n lines.push('');\n\n // Tool pattern\n const tools = Object.entries(branch.tool_pattern)\n .sort(([, a], [, b]) => b - a);\n if (tools.length > 0) {\n lines.push('### Tool Usage');\n for (const [tool, count] of tools) {\n lines.push(`- ${tool}: ${count}`);\n }\n lines.push('');\n }\n\n // Summary\n if (branch.summary) {\n lines.push('### Summary');\n lines.push(branch.summary);\n lines.push('');\n }\n\n // Observation timeline\n lines.push(`### Observation Timeline (${observations.length})`);\n for (const bo of observations) {\n const obs = obsRepo.getById(bo.observation_id);\n const content = obs\n ? (obs.title ?? obs.content.slice(0, 100))\n : bo.observation_id.slice(0, 8);\n const stageTag = bo.arc_stage_at_add ? `[${bo.arc_stage_at_add}]` : '';\n const toolTag = bo.tool_name ? `(${bo.tool_name})` : '';\n lines.push(\n `${bo.sequence_order}. ${stageTag} ${toolTag} ${content}`,\n );\n }\n\n return withNotifications(lines.join('\\n'));\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Unknown error';\n debug('mcp', 'show_branch: error', { error: message });\n return errorResponse(`show_branch error: ${message}`);\n }\n },\n );\n\n // ---------------------------------------------------------------------------\n // branch_summary\n // ---------------------------------------------------------------------------\n\n server.registerTool(\n 'branch_summary',\n {\n title: 'Branch Activity Summary',\n description:\n 'Summary of recent work activity grouped by time window. Shows what was investigated, fixed, built, and where work left off.',\n inputSchema: {\n hours: z\n .number()\n .int()\n .min(1)\n .max(168)\n .default(24)\n .describe('Time window in hours (default 24)'),\n },\n },\n async (args) => {\n const projectHash = projectHashRef.current;\n const withNotifications = (text: string) =>\n textResponse(prependNotifications(notificationStore, projectHash, text));\n\n try {\n debug('mcp', 'branch_summary: request', { hours: args.hours });\n\n const branches = branchRepo.listRecentBranches(args.hours);\n\n if (branches.length === 0) {\n return withNotifications(`No work branches in the last ${args.hours} hours`);\n }\n\n const verbosity = loadToolVerbosityConfig().level;\n\n if (verbosity === 1) {\n return withNotifications(`${branches.length} branches in ${args.hours}h`);\n }\n\n // Group by status\n const active = branches.filter(b => b.status === 'active');\n const completed = branches.filter(b => b.status === 'completed');\n const abandoned = branches.filter(b => b.status === 'abandoned');\n\n const lines: string[] = [];\n lines.push(`## Work Summary (last ${args.hours}h)`);\n lines.push(`**Total branches:** ${branches.length}`);\n lines.push('');\n\n if (active.length > 0) {\n lines.push('### Active');\n for (const b of active) {\n const title = b.title ?? b.id.slice(0, 8);\n lines.push(verbosity === 2\n ? `- ${title} (${b.branch_type})`\n : `- **${title}** (${b.branch_type}, ${b.arc_stage}) — ${b.observation_count} obs`);\n }\n lines.push('');\n }\n\n if (completed.length > 0) {\n lines.push('### Completed');\n for (const b of completed) {\n const title = b.title ?? b.id.slice(0, 8);\n const summary = b.summary ? `: ${b.summary.slice(0, 100)}` : '';\n lines.push(verbosity === 2\n ? `- ${title} (${b.branch_type})`\n : `- **${title}** (${b.branch_type})${summary}`);\n }\n lines.push('');\n }\n\n if (abandoned.length > 0) {\n lines.push('### Abandoned');\n for (const b of abandoned) {\n const title = b.title ?? b.id.slice(0, 8);\n lines.push(verbosity === 2\n ? `- ${title} (${b.branch_type})`\n : `- **${title}** (${b.branch_type}) — ${b.observation_count} obs`);\n }\n lines.push('');\n }\n\n // Tool distribution only at verbose level\n if (verbosity === 3) {\n const allTools: Record<string, number> = {};\n for (const b of branches) {\n for (const [tool, count] of Object.entries(b.tool_pattern)) {\n allTools[tool] = (allTools[tool] ?? 0) + count;\n }\n }\n const toolEntries = Object.entries(allTools).sort(([, a], [, b]) => b - a);\n if (toolEntries.length > 0) {\n lines.push('### Tool Distribution');\n for (const [tool, count] of toolEntries.slice(0, 10)) {\n lines.push(`- ${tool}: ${count}`);\n }\n }\n }\n\n return withNotifications(lines.join('\\n'));\n } catch (err) {\n const message = err instanceof Error ? err.message : 'Unknown error';\n debug('mcp', 'branch_summary: error', { error: message });\n return errorResponse(`branch_summary error: ${message}`);\n }\n },\n );\n}\n","/**\n * Infers arc stage from tool pattern counts within a branch.\n *\n * No LLM call -- deterministic based on tool usage ratios.\n *\n * Classification sources (in priority order):\n * 1. Built-in tool table (hardcoded, always correct)\n * 2. Registry-primed cache (from tool_registry descriptions at startup)\n * 3. Name-pattern fallback (regex heuristic for tools not yet in registry)\n *\n * The cache is re-primed whenever the tool registry changes (detected by\n * row count delta). BranchTracker calls `primeFromRegistry()` on startup\n * and during periodic maintenance.\n */\n\nimport type BetterSqlite3 from 'better-sqlite3';\nimport type { ArcStage } from './types.js';\nimport { debug } from '../shared/debug.js';\n\n// ---------------------------------------------------------------------------\n// Arc Category\n// ---------------------------------------------------------------------------\n\nexport type ArcCategory = 'investigation' | 'write' | 'verification' | 'planning' | 'uncategorized';\n\n// ---------------------------------------------------------------------------\n// Built-in Tool Categories (fast path, always correct)\n// ---------------------------------------------------------------------------\n\nconst BUILTIN_CATEGORY: Record<string, ArcCategory> = {\n // Investigation\n 'Read': 'investigation',\n 'Glob': 'investigation',\n 'Grep': 'investigation',\n 'WebSearch': 'investigation',\n 'WebFetch': 'investigation',\n 'Task': 'investigation',\n 'AskUserQuestion': 'investigation',\n\n // Write/execution\n 'Write': 'write',\n 'Edit': 'write',\n 'NotebookEdit': 'write',\n\n // Verification\n 'Bash': 'verification',\n\n // Planning\n 'EnterPlanMode': 'planning',\n 'ExitPlanMode': 'planning',\n 'TaskCreate': 'planning',\n 'TaskUpdate': 'planning',\n 'TaskList': 'planning',\n 'TaskGet': 'planning',\n 'Skill': 'uncategorized',\n};\n\n// ---------------------------------------------------------------------------\n// Description-keyword classification\n// ---------------------------------------------------------------------------\n\n/** Keywords matched against tool descriptions (case-insensitive). */\nconst DESCRIPTION_RULES: Array<{ category: ArcCategory; keywords: RegExp }> = [\n // Planning first (most specific)\n { category: 'planning', keywords: /\\b(plan|todo|task|roadmap|milestone|phase|design|architect)\\b/i },\n\n // Verification\n { category: 'verification', keywords: /\\b(run|test|build|execute|evaluate|validate|verify|check|assert|lint|compile)\\b/i },\n\n // Write/mutation\n { category: 'write', keywords: /\\b(write|edit|create|update|save|upload|modify|delete|remove|fill|type|click|select|drag|press|submit|install|deploy|push|commit|insert|drop|replace)\\b/i },\n\n // Investigation (broadest)\n { category: 'investigation', keywords: /\\b(read|search|query|find|list|get|fetch|browse|snapshot|screenshot|inspect|show|view|discover|status|stats|navigate|hover|recall|monitor|log|trace|debug|profile|measure|analyze|explore)\\b/i },\n];\n\n/**\n * Classify a tool from its description text.\n * Returns null if no confident match.\n */\nfunction classifyFromDescription(description: string): ArcCategory | null {\n for (const rule of DESCRIPTION_RULES) {\n if (rule.keywords.test(description)) {\n return rule.category;\n }\n }\n return null;\n}\n\n// ---------------------------------------------------------------------------\n// Name-pattern fallback (for tools not in registry)\n// ---------------------------------------------------------------------------\n\nconst NAME_RULES: Array<{ category: ArcCategory; pattern: RegExp }> = [\n { category: 'planning', pattern: /\\b(plan|todo|task|roadmap|phase|milestone)\\b/i },\n { category: 'verification', pattern: /\\b(run|test|build|exec|evaluate|validate|check|verify)\\b/i },\n { category: 'write', pattern: /\\b(write|edit|create|update|save|upload|fill|type|click|select|drag|press|install)\\b/i },\n { category: 'investigation', pattern: /\\b(search|query|find|list|get|read|fetch|browse|snapshot|screenshot|inspect|show|view|recall|discover|status|stats|console|network|navigate|tabs|hover)\\b/i },\n];\n\nfunction classifyFromName(toolName: string): ArcCategory {\n // For MCP tools, extract the action part after the last `__`\n const actionPart = toolName.includes('__')\n ? toolName.substring(toolName.lastIndexOf('__') + 2)\n : toolName;\n\n for (const rule of NAME_RULES) {\n if (rule.pattern.test(actionPart)) return rule.category;\n }\n\n // Laminark's own tools are investigation\n if (toolName.includes('laminark')) return 'investigation';\n\n return 'uncategorized';\n}\n\n// ---------------------------------------------------------------------------\n// Cache state\n// ---------------------------------------------------------------------------\n\nconst classificationCache = new Map<string, ArcCategory>();\nlet lastRegistryCount = -1;\n\n// ---------------------------------------------------------------------------\n// Public: prime cache from tool registry\n// ---------------------------------------------------------------------------\n\n/**\n * Re-reads the tool_registry table and classifies every tool by its\n * description. Only rescans when the registry row count has changed.\n *\n * Call on startup and periodically (e.g., during BranchTracker maintenance).\n */\nexport function primeFromRegistry(db: BetterSqlite3.Database, projectHash: string): void {\n try {\n const countRow = db.prepare('SELECT COUNT(*) AS cnt FROM tool_registry').get() as { cnt: number } | undefined;\n const currentCount = countRow?.cnt ?? 0;\n\n // Skip if registry hasn't changed\n if (currentCount === lastRegistryCount && lastRegistryCount >= 0) return;\n\n const rows = db.prepare(`\n SELECT name, description FROM tool_registry\n WHERE status = 'active'\n AND (scope = 'global' OR project_hash IS NULL OR project_hash = ?)\n `).all(projectHash) as Array<{ name: string; description: string | null }>;\n\n let primed = 0;\n for (const row of rows) {\n // Don't override built-in classifications\n if (BUILTIN_CATEGORY[row.name]) continue;\n\n let category: ArcCategory | null = null;\n\n // Try description first (best signal)\n if (row.description) {\n category = classifyFromDescription(row.description);\n }\n\n // Fall back to name patterns\n if (!category) {\n category = classifyFromName(row.name);\n }\n\n classificationCache.set(row.name, category);\n primed++;\n }\n\n lastRegistryCount = currentCount;\n debug('branches', 'Arc detector cache primed from registry', {\n registryTools: rows.length,\n primed,\n });\n } catch {\n // tool_registry may not exist yet (pre-migration-16)\n }\n}\n\n// ---------------------------------------------------------------------------\n// Public: classify a single tool\n// ---------------------------------------------------------------------------\n\n/**\n * Classify any tool name into an arc category.\n *\n * Priority: built-in table > registry-primed cache > name-pattern fallback.\n */\nexport function classifyTool(toolName: string): ArcCategory {\n // 1. Check cache (includes both built-in and registry-primed entries)\n const cached = classificationCache.get(toolName);\n if (cached) return cached;\n\n // 2. Built-in exact match\n const builtin = BUILTIN_CATEGORY[toolName];\n if (builtin) {\n classificationCache.set(toolName, builtin);\n return builtin;\n }\n\n // 3. Name-pattern fallback (tool not in registry yet)\n const fromName = classifyFromName(toolName);\n classificationCache.set(toolName, fromName);\n return fromName;\n}\n\n// ---------------------------------------------------------------------------\n// Public: infer arc stage\n// ---------------------------------------------------------------------------\n\n/**\n * Infers the current arc stage from tool usage pattern counts.\n *\n * Handles all tool types: builtins, MCP tools, plugins, skills, slash commands.\n * Uncategorized tools are excluded from ratio calculations so they don't\n * dilute the signal from known tools.\n *\n * @param toolPattern - Map of tool name to usage count within the branch\n * @param classification - Optional dominant observation classification\n * @returns The inferred arc stage\n */\nexport function inferArcStage(\n toolPattern: Record<string, number>,\n classification?: string | null,\n): ArcStage {\n let investigationCount = 0;\n let writeCount = 0;\n let verificationCount = 0;\n let planningCount = 0;\n let categorizedCount = 0;\n\n for (const [tool, count] of Object.entries(toolPattern)) {\n const category = classifyTool(tool);\n switch (category) {\n case 'investigation':\n investigationCount += count;\n categorizedCount += count;\n break;\n case 'write':\n writeCount += count;\n categorizedCount += count;\n break;\n case 'verification':\n verificationCount += count;\n categorizedCount += count;\n break;\n case 'planning':\n planningCount += count;\n categorizedCount += count;\n break;\n case 'uncategorized':\n // Excluded from ratios so unknown tools don't dilute the signal\n break;\n }\n }\n\n if (categorizedCount === 0) return 'investigation';\n\n // Check for verification: Bash/test commands after writes\n if (verificationCount > 0 && writeCount > 0) {\n const verificationRatio = verificationCount / categorizedCount;\n if (verificationRatio > 0.2) return 'verification';\n }\n\n // Check for execution: Write/Edit dominates\n const writeRatio = writeCount / categorizedCount;\n if (writeRatio > 0.4) return 'execution';\n\n // Check for planning: plan mode or task creation\n if (planningCount > 0) {\n const planRatio = planningCount / categorizedCount;\n if (planRatio > 0.1) return 'planning';\n }\n\n // Check for diagnosis: observations classified as 'problem' with mixed read/write\n if (classification === 'problem' && writeCount > 0 && investigationCount > 0) {\n return 'diagnosis';\n }\n\n // Default: investigation (mostly reads)\n return 'investigation';\n}\n","/**\n * Haiku configuration.\n *\n * With the Claude Agent SDK, authentication is handled by the user's\n * Claude Code subscription -- no API key needed.\n */\n\nexport interface HaikuConfig {\n model: string;\n maxTokensPerCall: number;\n}\n\nexport function loadHaikuConfig(): HaikuConfig {\n return {\n model: 'claude-haiku-4-5-20251001',\n maxTokensPerCall: 1024,\n };\n}\n","/**\n * Shared Haiku client using Claude Agent SDK V2 session.\n *\n * Routes Haiku calls through the user's Claude Code subscription\n * instead of requiring a separate API key. Uses a persistent session\n * to avoid 12s cold-start overhead on sequential calls.\n *\n * Provides the core infrastructure for all Haiku agent modules:\n * - callHaiku() helper for structured prompt/response calls\n * - extractJsonFromResponse() for defensive JSON parsing\n * - Session reuse across batch processing cycles\n */\n\nimport {\n unstable_v2_createSession,\n type SDKSession,\n} from '@anthropic-ai/claude-agent-sdk';\n\nimport { loadHaikuConfig } from '../config/haiku-config.js';\n\n// ---------------------------------------------------------------------------\n// Singleton state\n// ---------------------------------------------------------------------------\n\nlet _session: SDKSession | null = null;\n\n// ---------------------------------------------------------------------------\n// Internal helpers\n// ---------------------------------------------------------------------------\n\nfunction getOrCreateSession(): SDKSession {\n if (!_session) {\n const config = loadHaikuConfig();\n _session = unstable_v2_createSession({\n model: config.model,\n permissionMode: 'bypassPermissions',\n allowedTools: [], // No tools -- pure text completion only\n });\n }\n return _session;\n}\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Returns whether Haiku enrichment is available.\n * Always true with subscription auth -- no API key check needed.\n */\nexport function isHaikuEnabled(): boolean {\n return true;\n}\n\n/**\n * Calls Haiku with a system prompt and user content.\n * Returns the text content from the response.\n *\n * Uses a persistent V2 session to avoid cold-start overhead on sequential calls.\n * System prompt is embedded in the user message since session-level systemPrompt\n * is set at creation time and we need different prompts per agent.\n *\n * @param systemPrompt - Instructions for the model\n * @param userContent - The content to process\n * @param _maxTokens - Kept for signature compatibility (unused -- Agent SDK constrains output via prompts)\n * @throws Error if the Haiku call fails or session expires\n */\nexport async function callHaiku(\n systemPrompt: string,\n userContent: string,\n _maxTokens?: number,\n): Promise<string> {\n const session = getOrCreateSession();\n\n // Embed system prompt in user message since the session shares a single\n // system prompt but our three agents each need different instructions\n const fullPrompt = `<instructions>\\n${systemPrompt}\\n</instructions>\\n\\n${userContent}`;\n\n try {\n await session.send(fullPrompt);\n for await (const msg of session.stream()) {\n if (msg.type === 'result') {\n if (msg.subtype === 'success') {\n return msg.result;\n }\n const errors = 'errors' in msg ? (msg as { errors?: string[] }).errors : undefined;\n const errorMsg = errors?.join(', ') ?? msg.subtype;\n throw new Error(`Haiku call failed: ${errorMsg}`);\n }\n }\n return ''; // No result message received\n } catch (error) {\n // Session may have expired -- reset and rethrow so next call creates fresh session\n try {\n _session?.close();\n } catch {\n // Ignore close errors\n }\n _session = null;\n throw error;\n }\n}\n\n/**\n * Defensive JSON extraction from Haiku response text.\n *\n * Handles common LLM response quirks:\n * - Markdown code fences (```json ... ```)\n * - Explanatory text before/after JSON\n * - Both array and object JSON shapes\n *\n * @throws Error if no JSON structure found in text\n */\nexport function extractJsonFromResponse(text: string): unknown {\n // Strip markdown code fences\n const cleaned = text.replace(/```json\\s*/g, '').replace(/```\\s*/g, '');\n\n // Try to find JSON array\n const arrayMatch = cleaned.match(/\\[[\\s\\S]*\\]/);\n if (arrayMatch) return JSON.parse(arrayMatch[0]);\n\n // Try to find JSON object\n const objMatch = cleaned.match(/\\{[\\s\\S]*\\}/);\n if (objMatch) return JSON.parse(objMatch[0]);\n\n throw new Error('No JSON found in Haiku response');\n}\n\n/**\n * Resets the singleton session. Used for testing.\n */\nexport function resetHaikuClient(): void {\n try {\n _session?.close();\n } catch {\n // Ignore close errors\n }\n _session = null;\n}\n","/**\n * Haiku agent for classifying thought branch type and generating title/summary.\n *\n * Uses a single Haiku call to determine:\n * 1. Branch type (investigation, bug_fix, feature, refactor, research)\n * 2. A concise title for the branch\n * 3. An optional summary (for completed branches)\n *\n * Follows the same pattern as haiku-classifier-agent.ts.\n */\n\nimport { z } from 'zod';\n\nimport { callHaiku, extractJsonFromResponse } from '../intelligence/haiku-client.js';\nimport type { BranchType } from './types.js';\n\n// ---------------------------------------------------------------------------\n// Zod validation schemas\n// ---------------------------------------------------------------------------\n\nconst ClassifyBranchSchema = z.object({\n branch_type: z.enum(['investigation', 'bug_fix', 'feature', 'refactor', 'research']),\n title: z.string().max(100),\n});\n\nconst SummarizeBranchSchema = z.object({\n summary: z.string().max(500),\n});\n\n// ---------------------------------------------------------------------------\n// Exported types\n// ---------------------------------------------------------------------------\n\nexport type BranchClassification = {\n branch_type: BranchType;\n title: string;\n};\n\nexport type BranchSummaryResult = {\n summary: string;\n};\n\n// ---------------------------------------------------------------------------\n// System prompts\n// ---------------------------------------------------------------------------\n\nconst CLASSIFY_PROMPT = `You classify developer work branches for a knowledge management system.\n\nGiven a sequence of observations from a work session, determine:\n1. branch_type: What kind of work is this?\n - \"investigation\": Exploring code, reading docs, understanding behavior\n - \"bug_fix\": Fixing an error, test failure, or unexpected behavior\n - \"feature\": Building new functionality\n - \"refactor\": Restructuring existing code without changing behavior\n - \"research\": Looking up external resources, comparing approaches\n\n2. title: A concise title (3-8 words) describing the work unit. Use imperative form.\n Examples: \"Fix auth token refresh\", \"Add branch detection system\", \"Investigate memory leak\"\n\nReturn JSON: {\"branch_type\": \"...\", \"title\": \"...\"}\nNo markdown, no explanation, ONLY the JSON object.`;\n\nconst SUMMARIZE_PROMPT = `You summarize completed developer work branches for a knowledge management system.\n\nGiven a sequence of observations from a completed work branch, write a concise summary (1-3 sentences) that captures:\n- What was the goal\n- What was done\n- What was the outcome\n\nReturn JSON: {\"summary\": \"...\"}\nNo markdown, no explanation, ONLY the JSON object.`;\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Classifies a branch type and generates a title from observation content.\n */\nexport async function classifyBranchWithHaiku(\n observationTexts: string[],\n toolPattern: Record<string, number>,\n): Promise<BranchClassification> {\n const toolSummary = Object.entries(toolPattern)\n .sort(([, a], [, b]) => b - a)\n .map(([tool, count]) => `${tool}: ${count}`)\n .join(', ');\n\n const userContent = [\n `Tool usage: ${toolSummary}`,\n '',\n 'Observations:',\n ...observationTexts.slice(0, 10).map((t, i) => `${i + 1}. ${t.slice(0, 200)}`),\n ].join('\\n');\n\n const response = await callHaiku(CLASSIFY_PROMPT, userContent, 256);\n const parsed = extractJsonFromResponse(response);\n return ClassifyBranchSchema.parse(parsed);\n}\n\n/**\n * Generates a completion summary for a finished branch.\n */\nexport async function summarizeBranchWithHaiku(\n title: string,\n branchType: string,\n observationTexts: string[],\n): Promise<BranchSummaryResult> {\n const userContent = [\n `Branch: ${title} (${branchType})`,\n '',\n 'Observations:',\n ...observationTexts.slice(0, 15).map((t, i) => `${i + 1}. ${t.slice(0, 200)}`),\n ].join('\\n');\n\n const response = await callHaiku(SUMMARIZE_PROMPT, userContent, 256);\n const parsed = extractJsonFromResponse(response);\n return SummarizeBranchSchema.parse(parsed);\n}\n","/**\n * BranchTracker — state machine for automatic thought branch detection.\n *\n * Consumes observations from the HaikuProcessor pipeline and manages\n * the lifecycle of thought branches. Detects boundaries via:\n * - Topic shifts (from TopicShiftHandler)\n * - Project hash changes\n * - Session changes\n * - Time gaps (>15 min between observations)\n * - Manual starts\n *\n * Lives in the MCP server process and maintains in-memory state.\n * Persists branches and observations via BranchRepository.\n */\n\nimport type { BranchRepository } from './branch-repository.js';\nimport type { ArcStage, TriggerSource } from './types.js';\nimport { inferArcStage, primeFromRegistry } from './arc-detector.js';\nimport {\n classifyBranchWithHaiku,\n summarizeBranchWithHaiku,\n} from './branch-classifier-agent.js';\nimport { isHaikuEnabled } from '../intelligence/haiku-client.js';\nimport { ObservationRepository } from '../storage/observations.js';\nimport { debug } from '../shared/debug.js';\nimport type BetterSqlite3 from 'better-sqlite3';\n\n// ---------------------------------------------------------------------------\n// Constants\n// ---------------------------------------------------------------------------\n\nconst TIME_GAP_MS = 15 * 60 * 1000; // 15 minutes\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\ntype TrackerState = 'idle' | 'tracking';\n\nexport interface BranchObservationInput {\n id: string;\n content: string;\n source: string;\n projectHash: string;\n sessionId?: string | null;\n classification?: string | null;\n createdAt: string;\n}\n\n// ---------------------------------------------------------------------------\n// BranchTracker\n// ---------------------------------------------------------------------------\n\nexport class BranchTracker {\n private state: TrackerState = 'idle';\n private activeBranchId: string | null = null;\n private activeProjectHash: string | null = null;\n private activeSessionId: string | null = null;\n private lastObservationTime: number = 0;\n private toolPattern: Record<string, number> = {};\n\n private readonly repo: BranchRepository;\n private readonly db: BetterSqlite3.Database;\n private readonly projectHash: string;\n\n constructor(repo: BranchRepository, db: BetterSqlite3.Database, projectHash: string) {\n this.repo = repo;\n this.db = db;\n this.projectHash = projectHash;\n\n // Prime arc detector cache from tool registry descriptions\n primeFromRegistry(db, projectHash);\n\n // Recover state from DB on startup\n const activeBranch = repo.findRecentActiveBranch();\n if (activeBranch) {\n this.state = 'tracking';\n this.activeBranchId = activeBranch.id;\n this.activeProjectHash = activeBranch.project_hash;\n this.activeSessionId = activeBranch.session_id;\n this.toolPattern = activeBranch.tool_pattern;\n this.lastObservationTime = new Date(activeBranch.started_at).getTime();\n debug('branches', 'Recovered active branch from DB', { branchId: activeBranch.id });\n }\n }\n\n // ===========================================================================\n // Public API\n // ===========================================================================\n\n /**\n * Process a new observation through the boundary detection state machine.\n * Called from HaikuProcessor after classification (Step 1.6).\n */\n processObservation(obs: BranchObservationInput): void {\n const now = Date.now();\n const obsTime = new Date(obs.createdAt).getTime();\n const toolName = this.extractToolName(obs.source);\n\n // Check for boundary signals\n const boundary = this.detectBoundary(obs, obsTime);\n\n if (boundary) {\n // Complete current branch if tracking\n if (this.state === 'tracking' && this.activeBranchId) {\n this.completeBranch();\n }\n\n // Start new branch\n this.startBranch(boundary, obs);\n } else if (this.state === 'idle') {\n // First observation — start tracking\n this.startBranch('session_start', obs);\n }\n\n // Add observation to active branch\n if (this.activeBranchId) {\n const arcStage = inferArcStage(this.toolPattern, obs.classification);\n\n // Update tool pattern\n if (toolName) {\n this.toolPattern[toolName] = (this.toolPattern[toolName] ?? 0) + 1;\n this.repo.updateToolPattern(this.activeBranchId, this.toolPattern);\n }\n\n // Add observation\n this.repo.addObservation(\n this.activeBranchId,\n obs.id,\n toolName,\n arcStage,\n );\n\n // Update arc stage\n const newStage = inferArcStage(this.toolPattern, obs.classification);\n this.repo.updateArcStage(this.activeBranchId, newStage);\n }\n\n this.lastObservationTime = obsTime || now;\n this.activeProjectHash = obs.projectHash;\n this.activeSessionId = obs.sessionId ?? this.activeSessionId;\n }\n\n /**\n * Notify the tracker of a topic shift (from TopicShiftHandler).\n */\n onTopicShift(observationId: string): void {\n if (this.state === 'tracking' && this.activeBranchId) {\n this.completeBranch();\n // Start new branch with topic_shift trigger will happen on next observation\n debug('branches', 'Topic shift boundary detected', { observationId });\n }\n }\n\n /**\n * Link the active branch to a debug path (when PathTracker activates).\n */\n linkDebugPath(debugPathId: string): void {\n if (this.activeBranchId) {\n this.repo.linkDebugPath(this.activeBranchId, debugPathId);\n debug('branches', 'Linked debug path to branch', {\n branchId: this.activeBranchId,\n debugPathId,\n });\n }\n }\n\n /**\n * Get the active branch ID (for external callers).\n */\n getActiveBranchId(): string | null {\n return this.activeBranchId;\n }\n\n // ===========================================================================\n // Maintenance (called from HaikuProcessor Step 4)\n // ===========================================================================\n\n /**\n * Run periodic maintenance tasks:\n * - Classify branches with 3+ observations via Haiku\n * - Generate summaries for recently completed branches\n * - Auto-abandon stale branches (>24h)\n * - Link branches to debug paths\n */\n async runMaintenance(): Promise<void> {\n try {\n // Re-prime arc detector from registry (no-ops if registry unchanged)\n primeFromRegistry(this.db, this.projectHash);\n\n // Auto-abandon stale branches\n const stale = this.repo.findStaleBranches();\n for (const branch of stale) {\n this.repo.abandonBranch(branch.id);\n if (this.activeBranchId === branch.id) {\n this.state = 'idle';\n this.activeBranchId = null;\n this.toolPattern = {};\n }\n debug('branches', 'Auto-abandoned stale branch', { branchId: branch.id });\n }\n\n // Classify unclassified branches with Haiku\n if (isHaikuEnabled()) {\n const unclassified = this.repo.findUnclassifiedBranches(3);\n for (const branch of unclassified) {\n try {\n const observations = this.repo.getObservations(branch.id);\n const obsRepo = new ObservationRepository(this.db, branch.project_hash);\n const texts = observations\n .map(bo => {\n const obs = obsRepo.getById(bo.observation_id);\n return obs ? (obs.title ? `${obs.title}: ${obs.content}` : obs.content) : null;\n })\n .filter((t): t is string => t !== null);\n\n if (texts.length === 0) continue;\n\n const result = await classifyBranchWithHaiku(texts, branch.tool_pattern);\n this.repo.updateClassification(branch.id, result.branch_type, result.title);\n debug('branches', 'Branch classified', {\n branchId: branch.id,\n type: result.branch_type,\n title: result.title,\n });\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n debug('branches', 'Branch classification failed (non-fatal)', {\n branchId: branch.id,\n error: msg,\n });\n }\n }\n\n // Generate summaries for recently completed branches\n const unsummarized = this.repo.findRecentCompletedUnsummarized(2);\n for (const branch of unsummarized) {\n try {\n const observations = this.repo.getObservations(branch.id);\n const obsRepo = new ObservationRepository(this.db, branch.project_hash);\n const texts = observations\n .map(bo => {\n const obs = obsRepo.getById(bo.observation_id);\n return obs ? (obs.title ? `${obs.title}: ${obs.content}` : obs.content) : null;\n })\n .filter((t): t is string => t !== null);\n\n if (texts.length === 0) continue;\n\n const result = await summarizeBranchWithHaiku(\n branch.title ?? 'Untitled',\n branch.branch_type,\n texts,\n );\n this.repo.updateSummary(branch.id, result.summary);\n debug('branches', 'Branch summarized', { branchId: branch.id });\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n debug('branches', 'Branch summarization failed (non-fatal)', {\n branchId: branch.id,\n error: msg,\n });\n }\n }\n }\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n debug('branches', 'Maintenance error (non-fatal)', { error: msg });\n }\n }\n\n // ===========================================================================\n // Private Helpers\n // ===========================================================================\n\n private detectBoundary(\n obs: BranchObservationInput,\n obsTime: number,\n ): TriggerSource | null {\n // 1. Project hash change\n if (this.activeProjectHash && obs.projectHash !== this.activeProjectHash) {\n return 'project_switch';\n }\n\n // 2. Session change\n if (\n this.activeSessionId &&\n obs.sessionId &&\n obs.sessionId !== this.activeSessionId\n ) {\n return 'session_start';\n }\n\n // 3. Time gap (>15 minutes)\n if (this.lastObservationTime > 0) {\n const gap = obsTime - this.lastObservationTime;\n if (gap > TIME_GAP_MS) {\n return 'time_gap';\n }\n }\n\n return null;\n }\n\n private startBranch(\n triggerSource: TriggerSource,\n obs: BranchObservationInput,\n ): void {\n const branch = this.repo.createBranch(\n obs.sessionId ?? null,\n triggerSource,\n obs.id,\n );\n this.state = 'tracking';\n this.activeBranchId = branch.id;\n this.toolPattern = {};\n debug('branches', 'New branch started', {\n branchId: branch.id,\n trigger: triggerSource,\n });\n }\n\n private completeBranch(): void {\n if (!this.activeBranchId) return;\n this.repo.completeBranch(this.activeBranchId);\n debug('branches', 'Branch completed', { branchId: this.activeBranchId });\n this.state = 'idle';\n this.activeBranchId = null;\n this.toolPattern = {};\n }\n\n private extractToolName(source: string): string | null {\n // Source format: \"hook:Read\", \"hook:Write\", \"manual\", \"mcp:save_memory\"\n if (source.startsWith('hook:')) {\n return source.slice(5); // \"Read\", \"Write\", etc.\n }\n if (source.startsWith('mcp:')) {\n return source.slice(4);\n }\n return null;\n }\n}\n","/**\n * Main-thread bridge for the embedding worker.\n *\n * AnalysisWorker provides a Promise-based API (embed/embedBatch) that sends\n * messages to the worker thread and resolves when results arrive. All methods\n * degrade gracefully -- returning null on error/timeout rather than throwing.\n */\n\nimport { Worker } from 'node:worker_threads';\nimport { fileURLToPath } from 'node:url';\nimport { dirname, join } from 'node:path';\n\nimport { debug } from '../shared/debug.js';\n\n/** Timeout for worker startup (model loading). */\nconst STARTUP_TIMEOUT_MS = 30_000;\n\n/** Timeout for individual embed requests. */\nconst REQUEST_TIMEOUT_MS = 30_000;\n\ninterface PendingRequest<T> {\n resolve: (value: T) => void;\n timer: ReturnType<typeof setTimeout>;\n}\n\ninterface ReadyMessage {\n type: 'ready';\n engineName: string;\n dimensions: number;\n}\n\ninterface EmbedResultMessage {\n type: 'embed_result';\n id: string;\n embedding: Float32Array | null;\n}\n\ninterface EmbedBatchResultMessage {\n type: 'embed_batch_result';\n id: string;\n embeddings: (Float32Array | null)[];\n}\n\ntype WorkerResponse = ReadyMessage | EmbedResultMessage | EmbedBatchResultMessage;\n\n/**\n * Main-thread API for sending embed requests to the worker thread.\n *\n * Usage:\n * ```ts\n * const worker = new AnalysisWorker();\n * await worker.start();\n * const embedding = await worker.embed(\"some text\");\n * await worker.shutdown();\n * ```\n */\nexport class AnalysisWorker {\n private worker: Worker | null = null;\n private pending = new Map<string, PendingRequest<unknown>>();\n private nextId = 0;\n private ready = false;\n private engineName = 'unknown';\n private dimensions = 0;\n private workerPath: string;\n\n constructor(workerPath?: string) {\n if (workerPath) {\n this.workerPath = workerPath;\n } else {\n // Resolve worker.js relative to the running file's location.\n // When bundled, this file is inlined into dist/index.js so import.meta.url\n // points to dist/. The worker entry is built to dist/analysis/worker.js.\n const thisDir = dirname(fileURLToPath(import.meta.url));\n this.workerPath = join(thisDir, 'analysis', 'worker.js');\n }\n }\n\n /**\n * Starts the worker thread and waits for the 'ready' message.\n *\n * Resolves once the worker reports its engine name and dimensions.\n * Times out after 30 seconds if the worker never becomes ready.\n */\n async start(): Promise<void> {\n return new Promise<void>((resolve, reject) => {\n const timer = setTimeout(() => {\n debug('embed', 'Worker startup timed out');\n this.ready = false;\n reject(new Error('Worker startup timed out'));\n }, STARTUP_TIMEOUT_MS);\n\n try {\n this.worker = new Worker(this.workerPath);\n } catch (err) {\n clearTimeout(timer);\n debug('embed', 'Failed to create worker', { error: String(err) });\n reject(err);\n return;\n }\n\n // One-time handler for the ready message\n const onReady = (msg: WorkerResponse) => {\n if (msg.type === 'ready') {\n clearTimeout(timer);\n this.ready = true;\n this.engineName = msg.engineName;\n this.dimensions = msg.dimensions;\n debug('embed', 'Worker ready', { engineName: msg.engineName, dimensions: msg.dimensions });\n\n // Switch to the permanent message handler\n this.worker!.off('message', onReady);\n this.worker!.on('message', (m: WorkerResponse) => this.handleMessage(m));\n resolve();\n }\n };\n\n this.worker.on('message', onReady);\n\n this.worker.on('error', (err) => {\n clearTimeout(timer);\n debug('embed', 'Worker error', { error: String(err) });\n this.resolveAllPending();\n this.ready = false;\n });\n\n this.worker.on('exit', (code) => {\n debug('embed', 'Worker exited', { code });\n this.resolveAllPending();\n this.ready = false;\n this.worker = null;\n });\n });\n }\n\n /**\n * Embeds a single text string via the worker thread.\n *\n * Returns null if the worker is not ready, not started, or if the\n * request times out.\n */\n async embed(text: string): Promise<Float32Array | null> {\n if (!this.worker || !this.ready) {\n return null;\n }\n\n const id = String(this.nextId++);\n\n return new Promise<Float32Array | null>((resolve) => {\n const timer = setTimeout(() => {\n debug('embed', 'Embed request timed out', { id });\n this.pending.delete(id);\n resolve(null);\n }, REQUEST_TIMEOUT_MS);\n\n this.pending.set(id, { resolve: resolve as (value: unknown) => void, timer });\n this.worker!.postMessage({ type: 'embed', id, text });\n });\n }\n\n /**\n * Embeds multiple texts via the worker thread.\n *\n * Returns an array of nulls if the worker is not ready or times out.\n */\n async embedBatch(texts: string[]): Promise<(Float32Array | null)[]> {\n if (!this.worker || !this.ready) {\n return texts.map(() => null);\n }\n\n const id = String(this.nextId++);\n\n return new Promise<(Float32Array | null)[]>((resolve) => {\n const timer = setTimeout(() => {\n debug('embed', 'Batch embed request timed out', { id });\n this.pending.delete(id);\n resolve(texts.map(() => null));\n }, REQUEST_TIMEOUT_MS);\n\n this.pending.set(id, { resolve: resolve as (value: unknown) => void, timer });\n this.worker!.postMessage({ type: 'embed_batch', id, texts });\n });\n }\n\n /**\n * Sends a shutdown message and waits for the worker to exit.\n */\n async shutdown(): Promise<void> {\n if (!this.worker) {\n return;\n }\n\n return new Promise<void>((resolve) => {\n const w = this.worker!;\n w.once('exit', () => {\n this.worker = null;\n this.ready = false;\n this.resolveAllPending();\n resolve();\n });\n\n w.postMessage({ type: 'shutdown' });\n\n // Safety timeout -- force terminate if shutdown hangs\n setTimeout(() => {\n if (this.worker) {\n debug('embed', 'Worker shutdown timed out, terminating');\n this.worker.terminate();\n }\n }, 5_000);\n });\n }\n\n /** Whether the worker is started and ready. */\n isReady(): boolean {\n return this.ready;\n }\n\n /** The engine name reported by the worker. */\n getEngineName(): string {\n return this.engineName;\n }\n\n /** The embedding dimensions reported by the worker. */\n getDimensions(): number {\n return this.dimensions;\n }\n\n /**\n * Dispatches worker responses to the correct pending promise.\n */\n private handleMessage(msg: WorkerResponse): void {\n if (msg.type === 'embed_result' || msg.type === 'embed_batch_result') {\n const id = msg.id;\n const req = this.pending.get(id);\n\n if (req) {\n clearTimeout(req.timer);\n this.pending.delete(id);\n\n if (msg.type === 'embed_result') {\n req.resolve(msg.embedding);\n } else {\n req.resolve(msg.embeddings);\n }\n }\n }\n }\n\n /**\n * Resolves all pending requests with null (graceful degradation).\n *\n * Called on worker error or unexpected exit.\n */\n private resolveAllPending(): void {\n for (const [id, req] of this.pending) {\n clearTimeout(req.timer);\n req.resolve(null);\n this.pending.delete(id);\n }\n }\n}\n","// ---------------------------------------------------------------------------\n// Topic Shift Handler -- Integration Layer\n// ---------------------------------------------------------------------------\n// Orchestrates topic detection and context stashing. When a new observation\n// embedding has high cosine distance from the previous one, the current\n// context thread is automatically stashed and the user is notified.\n//\n// This is the integration layer that makes topic detection active:\n// - Plan 01 built the detector (TopicShiftDetector)\n// - Plan 02 built storage (StashManager)\n// - Plan 05 built adaptive threshold (AdaptiveThresholdManager)\n// - Plan 06 adds config (TopicDetectionConfig) and logging (DecisionLogger)\n// - This module connects them into the live hook flow\n// ---------------------------------------------------------------------------\n\nimport { debug } from '../shared/debug.js';\nimport type { TopicShiftDetector } from '../intelligence/topic-detector.js';\nimport type { AdaptiveThresholdManager } from '../intelligence/adaptive-threshold.js';\nimport type { TopicShiftDecisionLogger, ShiftDecision } from '../intelligence/decision-logger.js';\nimport type { TopicDetectionConfig } from '../config/topic-detection-config.js';\nimport type { StashManager } from '../storage/stash-manager.js';\nimport type { ObservationRepository } from '../storage/observations.js';\nimport type { StashObservation } from '../types/stash.js';\nimport type { Observation } from '../shared/types.js';\n\n/**\n * Result of handling an observation through the topic shift pipeline.\n */\nexport interface TopicShiftHandlerResult {\n /** Whether a stash was created due to topic shift */\n stashed: boolean;\n /** Notification message to surface to the user, or null if no shift */\n notification: string | null;\n}\n\n/**\n * Dependencies required by TopicShiftHandler.\n *\n * Core dependencies (detector, stashManager, observationStore) are required.\n * Optional dependencies (config, decisionLogger, adaptiveManager) enable\n * additional functionality when provided. When omitted, the handler falls\n * back to simpler behavior for backward compatibility and easier test setups.\n */\nexport interface TopicShiftHandlerDeps {\n detector: TopicShiftDetector;\n stashManager: StashManager;\n observationStore: ObservationRepository;\n /** Optional: config for enable/disable, manual override, sensitivity */\n config?: TopicDetectionConfig;\n /** Optional: logs every detection decision for debugging */\n decisionLogger?: TopicShiftDecisionLogger;\n /** Optional: adaptive threshold manager for EWMA updates */\n adaptiveManager?: AdaptiveThresholdManager;\n}\n\n/**\n * Orchestrates topic shift detection and automatic stashing.\n *\n * Full pipeline when all dependencies provided:\n * 1. Check config (enabled? manual override?)\n * 2. Run detector.detect(embedding)\n * 3. If adaptive manager: update EWMA and set new threshold\n * 4. Log decision via decision logger\n * 5. If shifted: gather observations, create stash, notify\n * 6. If not shifted: return no-op result\n *\n * When optional deps are omitted, steps 1/3/4 are skipped gracefully.\n */\nexport class TopicShiftHandler {\n private readonly detector: TopicShiftDetector;\n private readonly stashManager: StashManager;\n private readonly observationStore: ObservationRepository;\n private readonly config?: TopicDetectionConfig;\n private readonly decisionLogger?: TopicShiftDecisionLogger;\n private readonly adaptiveManager?: AdaptiveThresholdManager;\n\n constructor(deps: TopicShiftHandlerDeps) {\n this.detector = deps.detector;\n this.stashManager = deps.stashManager;\n this.observationStore = deps.observationStore;\n this.config = deps.config;\n this.decisionLogger = deps.decisionLogger;\n this.adaptiveManager = deps.adaptiveManager;\n\n debug('hook', 'TopicShiftHandler initialized', {\n hasConfig: !!deps.config,\n hasDecisionLogger: !!deps.decisionLogger,\n hasAdaptiveManager: !!deps.adaptiveManager,\n });\n }\n\n /**\n * Evaluate an observation for topic shift.\n *\n * If a shift is detected, gathers recent observations from the previous\n * topic, creates a stash snapshot, and returns a notification message\n * for the user.\n */\n async handleObservation(\n observation: Observation,\n sessionId: string,\n projectId: string,\n ): Promise<TopicShiftHandlerResult> {\n // 0. Config check: if detection is disabled, return early\n if (this.config && !this.config.enabled) {\n debug('hook', 'TopicShiftHandler: detection disabled by config');\n return { stashed: false, notification: null };\n }\n\n // 1. No embedding -> skip detection\n if (!observation.embedding) {\n debug('hook', 'TopicShiftHandler: no embedding, skipping', {\n id: observation.id,\n });\n return { stashed: false, notification: null };\n }\n\n // 2. Apply manual threshold override if configured\n if (this.config?.manualThreshold !== undefined && this.config.manualThreshold !== null) {\n this.detector.setThreshold(this.config.manualThreshold);\n }\n\n // 3. Run detector -- convert Float32Array to number[] for cosine distance\n const embeddingArray = Array.from(observation.embedding);\n const result = this.detector.detect(embeddingArray);\n\n debug('hook', 'TopicShiftHandler: detection result', {\n shifted: result.shifted,\n distance: result.distance,\n threshold: result.threshold,\n });\n\n // 4. Adaptive threshold update (if adaptive manager present and no manual override)\n if (this.adaptiveManager && !(this.config?.manualThreshold !== undefined && this.config.manualThreshold !== null)) {\n const newThreshold = this.adaptiveManager.update(result.distance);\n this.detector.setThreshold(newThreshold);\n debug('hook', 'TopicShiftHandler: adaptive threshold updated', {\n newThreshold,\n });\n }\n\n // 5. Determine stash ID (only available after stash creation below)\n let stashId: string | null = null;\n\n // 6. Handle shift: gather observations and create stash\n if (result.shifted) {\n // Gather previous topic observations (include unclassified -- observations\n // may not yet be classified by the background classifier when shift runs)\n const recentObservations = this.observationStore.list({\n sessionId,\n limit: 20,\n includeUnclassified: true,\n });\n\n // Filter to observations before the current one (by timestamp)\n const previousObservations = recentObservations.filter(\n (obs) => obs.createdAt < observation.createdAt,\n );\n\n // Nothing to stash (clean context / session start) -- skip\n if (previousObservations.length === 0) {\n debug('hook', 'TopicShiftHandler: no previous observations to stash, skipping');\n return { stashed: false, notification: null };\n }\n\n // Generate topic label from previous observations\n const topicLabel = this.generateTopicLabel(previousObservations);\n\n // Generate summary\n const summary = this.generateSummary(previousObservations);\n\n // Create stash observation snapshots\n const snapshots: StashObservation[] = previousObservations.map((obs) => ({\n id: obs.id,\n content: obs.content,\n type: obs.source,\n timestamp: obs.createdAt,\n embedding: obs.embedding ? Array.from(obs.embedding) : null,\n }));\n\n // Create stash\n const stash = this.stashManager.createStash({\n projectId,\n sessionId,\n topicLabel,\n summary,\n observations: snapshots,\n });\n\n stashId = stash.id;\n\n debug('hook', 'TopicShiftHandler: stash created', { topicLabel, stashId });\n\n // 7. Log decision (if logger present)\n if (this.decisionLogger) {\n const decision: ShiftDecision = {\n projectId,\n sessionId,\n observationId: observation.id,\n distance: result.distance,\n threshold: result.threshold,\n ewmaDistance: this.adaptiveManager?.getState().ewmaDistance ?? null,\n ewmaVariance: this.adaptiveManager?.getState().ewmaVariance ?? null,\n sensitivityMultiplier: this.config?.sensitivityMultiplier ?? 1.5,\n shifted: true,\n confidence: result.confidence,\n stashId,\n };\n this.decisionLogger.log(decision);\n }\n\n // Return notification\n const notification = `Topic shift detected. Previous context stashed: \"${topicLabel}\". Use /laminark:resume to return.`;\n return { stashed: true, notification };\n }\n\n // 8. Not shifted -- log decision and return no-op\n if (this.decisionLogger) {\n const decision: ShiftDecision = {\n projectId,\n sessionId,\n observationId: observation.id,\n distance: result.distance,\n threshold: result.threshold,\n ewmaDistance: this.adaptiveManager?.getState().ewmaDistance ?? null,\n ewmaVariance: this.adaptiveManager?.getState().ewmaVariance ?? null,\n sensitivityMultiplier: this.config?.sensitivityMultiplier ?? 1.5,\n shifted: false,\n confidence: result.confidence,\n stashId: null,\n };\n this.decisionLogger.log(decision);\n }\n\n return { stashed: false, notification: null };\n }\n\n /**\n * Generate a semantic topic label from the observations.\n *\n * Priority: use observation titles (most semantic), then fall back\n * to content. Scans all observations for the best available label\n * rather than just using the first one.\n */\n private generateTopicLabel(observations: Observation[]): string {\n if (observations.length === 0) {\n return 'Unknown topic';\n }\n\n // Prefer titled observations -- titles are explicitly authored and semantic\n for (const obs of observations) {\n if (obs.title) {\n const cleaned = obs.title.replace(/\\n/g, ' ').trim();\n if (cleaned.length > 0) {\n return cleaned.slice(0, 80);\n }\n }\n }\n\n // Fallback: use the oldest observation's content (list is DESC)\n const oldest = observations[observations.length - 1];\n const raw = oldest.content.replace(/\\n/g, ' ').trim();\n return raw.slice(0, 80) || 'Unknown topic';\n }\n\n /**\n * Generate a brief summary by concatenating the first 3 observation contents,\n * truncated to 200 characters total.\n */\n private generateSummary(observations: Observation[]): string {\n if (observations.length === 0) {\n return '';\n }\n\n // Take up to 3 oldest observations (list is DESC, so take from the end)\n const oldest = observations.slice(-3).reverse();\n const joined = oldest\n .map((obs) => obs.content.replace(/\\n/g, ' ').trim())\n .join(' | ');\n\n return joined.slice(0, 200);\n }\n}\n","// ---------------------------------------------------------------------------\n// Topic Shift Detection -- Static Threshold\n// ---------------------------------------------------------------------------\n// Computes cosine distance between consecutive observation embeddings and\n// determines whether a topic shift has occurred based on a static threshold.\n// Foundation for all topic detection in Phase 6 -- adaptive EWMA layered on\n// in Plan 05.\n// ---------------------------------------------------------------------------\n\n/**\n * Result of a topic shift detection check.\n */\nexport interface TopicShiftResult {\n /** Whether a topic shift was detected */\n shifted: boolean;\n /** Cosine distance between current and previous embedding */\n distance: number;\n /** Threshold used for this detection */\n threshold: number;\n /** Confidence 0-1 -- how far past the threshold (0 if not shifted) */\n confidence: number;\n /** Previous embedding (null if first observation) */\n previousEmbedding: number[] | null;\n /** Current embedding that was evaluated */\n currentEmbedding: number[];\n}\n\n/**\n * Compute cosine distance between two vectors.\n *\n * Returns 1 - cosineSimilarity(a, b).\n * Range: [0, 2] where 0 = identical, 1 = orthogonal, 2 = opposite.\n * Handles zero vectors gracefully by returning 0 (not NaN).\n */\nexport function cosineDistance(a: number[], b: number[]): number {\n let dot = 0;\n let magA = 0;\n let magB = 0;\n\n for (let i = 0; i < a.length; i++) {\n dot += a[i] * b[i];\n magA += a[i] * a[i];\n magB += b[i] * b[i];\n }\n\n const magnitudeProduct = Math.sqrt(magA) * Math.sqrt(magB);\n\n // Zero vector: treat as no distance (graceful, no NaN)\n if (magnitudeProduct === 0) {\n return 0;\n }\n\n const similarity = dot / magnitudeProduct;\n\n // Clamp to [-1, 1] to handle floating-point rounding\n const clampedSimilarity = Math.max(-1, Math.min(1, similarity));\n\n return 1 - clampedSimilarity;\n}\n\n/**\n * Detects topic shifts by comparing consecutive observation embeddings\n * against a static cosine distance threshold.\n */\nexport class TopicShiftDetector {\n private lastEmbedding: number[] | null = null;\n private threshold: number;\n\n constructor(options?: { threshold?: number }) {\n this.threshold = options?.threshold ?? 0.3;\n }\n\n /**\n * Evaluate a new embedding for topic shift against the previous one.\n * Updates internal state with the new embedding after evaluation.\n */\n detect(embedding: number[]): TopicShiftResult {\n const previous = this.lastEmbedding;\n this.lastEmbedding = embedding;\n\n // First observation -- no prior to compare against\n if (previous === null) {\n return {\n shifted: false,\n distance: 0,\n threshold: this.threshold,\n confidence: 0,\n previousEmbedding: null,\n currentEmbedding: embedding,\n };\n }\n\n const distance = cosineDistance(previous, embedding);\n const shifted = distance > this.threshold;\n const confidence = shifted\n ? Math.min((distance - this.threshold) / this.threshold, 1.0)\n : 0;\n\n return {\n shifted,\n distance,\n threshold: this.threshold,\n confidence,\n previousEmbedding: previous,\n currentEmbedding: embedding,\n };\n }\n\n /** Clear last embedding state -- next detect is treated as first observation */\n reset(): void {\n this.lastEmbedding = null;\n }\n\n /** Get current threshold value */\n getThreshold(): number {\n return this.threshold;\n }\n\n /** Set threshold value, bounded to [0.05, 0.95] */\n setThreshold(value: number): void {\n this.threshold = Math.max(0.05, Math.min(0.95, value));\n }\n}\n","// ---------------------------------------------------------------------------\n// EWMA Adaptive Topic Threshold\n// ---------------------------------------------------------------------------\n// Adjusts the topic shift detection threshold per-session based on observed\n// cosine distances using an Exponentially Weighted Moving Average (EWMA).\n//\n// A scattered session (high distances) raises the threshold over time.\n// A focused session (low distances) lowers the threshold over time.\n// New sessions seed from historical averages for cold start handling.\n// ---------------------------------------------------------------------------\n\n/**\n * Internal state of the adaptive threshold computation.\n */\nexport interface ThresholdState {\n /** Exponentially weighted moving average of observed distances */\n ewmaDistance: number;\n /** Exponentially weighted variance of observed distances */\n ewmaVariance: number;\n /** Decay factor for EWMA (0 < alpha <= 1) */\n alpha: number;\n /** Standard deviations above the mean for threshold */\n sensitivityMultiplier: number;\n /** Number of distance observations processed */\n observationCount: number;\n}\n\n/** Default EWMA distance when no history exists */\nconst DEFAULT_EWMA_DISTANCE = 0.3;\n\n/** Default EWMA variance when no history exists */\nconst DEFAULT_EWMA_VARIANCE = 0.01;\n\n/** Default decay factor */\nconst DEFAULT_ALPHA = 0.3;\n\n/** Default sensitivity (standard deviations above mean) */\nconst DEFAULT_SENSITIVITY_MULTIPLIER = 1.5;\n\n/** Hard lower bound for threshold -- prevents over-sensitive detection */\nconst THRESHOLD_MIN = 0.15;\n\n/** Hard upper bound for threshold -- prevents ignoring real shifts */\nconst THRESHOLD_MAX = 0.6;\n\n/**\n * Manages an EWMA-based adaptive threshold for topic shift detection.\n *\n * After each topic distance observation, call `update(distance)` to refine\n * the threshold. The threshold adapts:\n * - High distances (scattered topics) push the threshold up\n * - Low distances (focused topics) push the threshold down\n * - Threshold is bounded within [0.15, 0.6] to prevent extreme drift\n *\n * For cold start, call `seedFromHistory(avgDistance, avgVariance)` with\n * averages loaded from the ThresholdStore.\n */\nexport class AdaptiveThresholdManager {\n private ewmaDistance: number;\n private ewmaVariance: number;\n private alpha: number;\n private sensitivityMultiplier: number;\n private observationCount: number;\n\n constructor(options?: {\n alpha?: number;\n sensitivityMultiplier?: number;\n }) {\n this.alpha = options?.alpha ?? DEFAULT_ALPHA;\n this.sensitivityMultiplier =\n options?.sensitivityMultiplier ?? DEFAULT_SENSITIVITY_MULTIPLIER;\n this.ewmaDistance = DEFAULT_EWMA_DISTANCE;\n this.ewmaVariance = DEFAULT_EWMA_VARIANCE;\n this.observationCount = 0;\n }\n\n /**\n * Feed a new cosine distance observation and update the EWMA state.\n *\n * EWMA update formula:\n * 1. ewmaDistance = alpha * distance + (1 - alpha) * ewmaDistance\n * 2. diff = distance - ewmaDistance (after update)\n * 3. ewmaVariance = alpha * (diff * diff) + (1 - alpha) * ewmaVariance\n * 4. threshold = clamp(ewmaDistance + sensitivityMultiplier * sqrt(ewmaVariance), 0.15, 0.6)\n *\n * @param distance - Cosine distance from the latest topic detection\n * @returns The new adaptive threshold value\n */\n update(distance: number): number {\n // Step 1: Update EWMA distance\n this.ewmaDistance =\n this.alpha * distance + (1 - this.alpha) * this.ewmaDistance;\n\n // Step 2: Compute deviation from new mean\n const diff = distance - this.ewmaDistance;\n\n // Step 3: Update EWMA variance\n this.ewmaVariance =\n this.alpha * (diff * diff) + (1 - this.alpha) * this.ewmaVariance;\n\n // Step 4: Increment observation count\n this.observationCount++;\n\n // Step 5: Return clamped threshold\n return this.getThreshold();\n }\n\n /**\n * Seed the EWMA state from historical session averages (cold start).\n * Does not reset observation count -- only updates the statistical seed.\n */\n seedFromHistory(averageDistance: number, averageVariance: number): void {\n this.ewmaDistance = averageDistance;\n this.ewmaVariance = averageVariance;\n }\n\n /**\n * Compute the current threshold from EWMA state, clamped to bounds.\n *\n * Formula: ewmaDistance + sensitivityMultiplier * sqrt(ewmaVariance)\n * Bounded to [0.15, 0.6]\n */\n getThreshold(): number {\n const raw =\n this.ewmaDistance +\n this.sensitivityMultiplier * Math.sqrt(this.ewmaVariance);\n return Math.max(THRESHOLD_MIN, Math.min(THRESHOLD_MAX, raw));\n }\n\n /**\n * Return a snapshot of the current EWMA state.\n */\n getState(): ThresholdState {\n return {\n ewmaDistance: this.ewmaDistance,\n ewmaVariance: this.ewmaVariance,\n alpha: this.alpha,\n sensitivityMultiplier: this.sensitivityMultiplier,\n observationCount: this.observationCount,\n };\n }\n\n /**\n * Reset all EWMA state to defaults.\n */\n reset(): void {\n this.ewmaDistance = DEFAULT_EWMA_DISTANCE;\n this.ewmaVariance = DEFAULT_EWMA_VARIANCE;\n this.observationCount = 0;\n }\n}\n","// ---------------------------------------------------------------------------\n// Topic Shift Decision Logger\n// ---------------------------------------------------------------------------\n// Logs every topic shift decision (shifted or not) with all inputs for\n// debugging and threshold tuning. Requirement DQ-05 demands full\n// observability of the decision pipeline.\n//\n// Each decision record captures: distance, threshold, EWMA state,\n// sensitivity multiplier, shifted boolean, confidence, and optional\n// stash ID (when a shift triggered stashing).\n// ---------------------------------------------------------------------------\n\nimport type BetterSqlite3 from 'better-sqlite3';\nimport { randomBytes } from 'node:crypto';\n\nimport { debug } from '../shared/debug.js';\n\n/**\n * A single topic shift decision with all inputs recorded.\n */\nexport interface ShiftDecision {\n /** Project scoping */\n projectId: string;\n /** Session scoping */\n sessionId: string;\n /** The observation that triggered this decision (null for synthetic events) */\n observationId: string | null;\n /** Cosine distance between consecutive embeddings */\n distance: number;\n /** Threshold used for this detection */\n threshold: number;\n /** EWMA distance at decision time (null if adaptive disabled) */\n ewmaDistance: number | null;\n /** EWMA variance at decision time (null if adaptive disabled) */\n ewmaVariance: number | null;\n /** Sensitivity multiplier in effect */\n sensitivityMultiplier: number;\n /** Whether a topic shift was detected */\n shifted: boolean;\n /** Confidence score (0-1) */\n confidence: number;\n /** Stash ID created by this shift (null if not shifted) */\n stashId: string | null;\n}\n\n/**\n * Persists topic shift decisions for debugging and threshold tuning.\n *\n * Every call to detect() in the topic shift pipeline should result in\n * a corresponding log() call here, regardless of whether a shift was\n * detected. This provides complete visibility into the decision process.\n *\n * All SQL statements are prepared once in the constructor and reused\n * for every call (better-sqlite3 performance best practice).\n */\nexport class TopicShiftDecisionLogger {\n private readonly db: BetterSqlite3.Database;\n\n // Prepared statements\n private readonly stmtInsert: BetterSqlite3.Statement;\n private readonly stmtGetSessionDecisions: BetterSqlite3.Statement;\n private readonly stmtGetShiftRate: BetterSqlite3.Statement;\n\n constructor(db: BetterSqlite3.Database) {\n this.db = db;\n\n this.stmtInsert = db.prepare(`\n INSERT INTO shift_decisions\n (id, project_id, session_id, observation_id, distance, threshold,\n ewma_distance, ewma_variance, sensitivity_multiplier, shifted,\n confidence, stash_id)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)\n `);\n\n this.stmtGetSessionDecisions = db.prepare(`\n SELECT * FROM shift_decisions\n WHERE project_id = ? AND session_id = ?\n ORDER BY created_at DESC\n LIMIT ?\n `);\n\n this.stmtGetShiftRate = db.prepare(`\n SELECT\n COUNT(*) AS total,\n SUM(shifted) AS shifted_count\n FROM (\n SELECT shifted\n FROM shift_decisions\n WHERE project_id = ?\n ORDER BY created_at DESC\n LIMIT ?\n )\n `);\n\n debug('db', 'TopicShiftDecisionLogger initialized');\n }\n\n /**\n * Log a topic shift decision with all inputs.\n *\n * Should be called after every detect() call, regardless of outcome.\n */\n log(decision: ShiftDecision): void {\n const id = randomBytes(16).toString('hex');\n\n this.stmtInsert.run(\n id,\n decision.projectId,\n decision.sessionId,\n decision.observationId,\n decision.distance,\n decision.threshold,\n decision.ewmaDistance,\n decision.ewmaVariance,\n decision.sensitivityMultiplier,\n decision.shifted ? 1 : 0,\n decision.confidence,\n decision.stashId,\n );\n\n debug('db', 'Shift decision logged', {\n shifted: decision.shifted,\n distance: decision.distance,\n threshold: decision.threshold,\n });\n }\n\n /**\n * Retrieve decisions for a specific session, ordered by recency.\n *\n * Useful for debugging: \"What happened in this session?\"\n */\n getSessionDecisions(\n projectId: string,\n sessionId: string,\n limit: number = 50,\n ): ShiftDecision[] {\n const rows = this.stmtGetSessionDecisions.all(\n projectId,\n sessionId,\n limit,\n ) as DecisionRow[];\n\n return rows.map(rowToDecision);\n }\n\n /**\n * Compute shift rate statistics across recent decisions for a project.\n *\n * Returns the total number of decisions, how many were shifts, and\n * the rate (0-1). Useful for tuning: a rate of 0.3 means 30% of\n * observations triggered a topic shift.\n */\n getShiftRate(\n projectId: string,\n lastN: number = 100,\n ): { total: number; shifted: number; rate: number } {\n const row = this.stmtGetShiftRate.get(projectId, lastN) as {\n total: number;\n shifted_count: number | null;\n };\n\n const total = row.total;\n const shifted = row.shifted_count ?? 0;\n const rate = total > 0 ? shifted / total : 0;\n\n return { total, shifted, rate };\n }\n}\n\n// ---------------------------------------------------------------------------\n// Internal row mapping\n// ---------------------------------------------------------------------------\n\ninterface DecisionRow {\n id: string;\n project_id: string;\n session_id: string;\n observation_id: string | null;\n distance: number;\n threshold: number;\n ewma_distance: number | null;\n ewma_variance: number | null;\n sensitivity_multiplier: number;\n shifted: number; // 0 or 1\n confidence: number;\n stash_id: string | null;\n created_at: string;\n}\n\nfunction rowToDecision(row: DecisionRow): ShiftDecision {\n return {\n projectId: row.project_id,\n sessionId: row.session_id,\n observationId: row.observation_id,\n distance: row.distance,\n threshold: row.threshold,\n ewmaDistance: row.ewma_distance,\n ewmaVariance: row.ewma_variance,\n sensitivityMultiplier: row.sensitivity_multiplier,\n shifted: row.shifted === 1,\n confidence: row.confidence,\n stashId: row.stash_id,\n };\n}\n","// ---------------------------------------------------------------------------\n// Topic Detection Configuration\n// ---------------------------------------------------------------------------\n// User-configurable sensitivity dial for topic shift detection.\n// Supports three presets (sensitive/balanced/relaxed), custom multipliers,\n// manual threshold override, and an enable/disable toggle.\n//\n// Configuration is loaded from .laminark/topic-detection.json with\n// safe defaults when the file does not exist.\n// ---------------------------------------------------------------------------\n\nimport { readFileSync } from 'node:fs';\nimport { join } from 'node:path';\n\nimport { debug } from '../shared/debug.js';\nimport { getConfigDir } from '../shared/config.js';\nimport type { TopicShiftDetector } from '../intelligence/topic-detector.js';\nimport type { AdaptiveThresholdManager } from '../intelligence/adaptive-threshold.js';\n\n/**\n * Sensitivity preset names for topic detection.\n */\nexport type SensitivityPreset = 'sensitive' | 'balanced' | 'relaxed';\n\n/**\n * Full configuration for topic detection behavior.\n */\nexport interface TopicDetectionConfig {\n /** Named sensitivity preset */\n sensitivityPreset: SensitivityPreset;\n /** Derived from preset or custom -- multiplied with EWMA stddev for threshold */\n sensitivityMultiplier: number;\n /** If set, overrides adaptive threshold entirely */\n manualThreshold: number | null;\n /** EWMA decay factor (0 < alpha <= 1) */\n ewmaAlpha: number;\n /** Hard bounds for adaptive threshold */\n thresholdBounds: { min: number; max: number };\n /** Master toggle -- when false, topic detection is disabled */\n enabled: boolean;\n}\n\n/**\n * Raw JSON shape for the configuration file.\n * All fields are optional -- missing fields use defaults.\n */\ninterface RawConfigJson {\n sensitivityPreset?: string;\n sensitivityMultiplier?: number;\n manualThreshold?: number | null;\n ewmaAlpha?: number;\n thresholdBounds?: { min?: number; max?: number };\n enabled?: boolean;\n}\n\n/**\n * Maps a sensitivity preset to its multiplier value.\n *\n * - sensitive (1.0): Detects smaller shifts -- lower bar for topic change\n * - balanced (1.5): Default -- moderate sensitivity\n * - relaxed (2.5): Only detects large shifts -- higher bar\n */\nexport function sensitivityPresetToMultiplier(preset: SensitivityPreset): number {\n switch (preset) {\n case 'sensitive':\n return 1.0;\n case 'balanced':\n return 1.5;\n case 'relaxed':\n return 2.5;\n }\n}\n\n/** Default configuration values */\nconst DEFAULTS: TopicDetectionConfig = {\n sensitivityPreset: 'balanced',\n sensitivityMultiplier: 1.5,\n manualThreshold: null,\n ewmaAlpha: 0.3,\n thresholdBounds: { min: 0.15, max: 0.6 },\n enabled: true,\n};\n\n/**\n * Loads topic detection configuration from disk.\n *\n * Reads .laminark/topic-detection.json (relative to the Laminark data\n * directory). Falls back to defaults if the file does not exist or\n * cannot be parsed. Validates threshold bounds constraints.\n */\nexport function loadTopicDetectionConfig(): TopicDetectionConfig {\n const configPath = join(getConfigDir(), 'topic-detection.json');\n\n let raw: RawConfigJson = {};\n\n try {\n const content = readFileSync(configPath, 'utf-8');\n raw = JSON.parse(content) as RawConfigJson;\n debug('config', 'Loaded topic detection config', { path: configPath });\n } catch {\n // File doesn't exist or is invalid -- use all defaults\n debug('config', 'No topic detection config found, using defaults');\n return { ...DEFAULTS };\n }\n\n // Resolve sensitivity preset\n const validPresets: SensitivityPreset[] = ['sensitive', 'balanced', 'relaxed'];\n const preset: SensitivityPreset = validPresets.includes(raw.sensitivityPreset as SensitivityPreset)\n ? (raw.sensitivityPreset as SensitivityPreset)\n : DEFAULTS.sensitivityPreset;\n\n // Multiplier: explicit value > preset-derived > default\n const multiplier =\n typeof raw.sensitivityMultiplier === 'number' && raw.sensitivityMultiplier > 0\n ? raw.sensitivityMultiplier\n : sensitivityPresetToMultiplier(preset);\n\n // Manual threshold: null means use adaptive\n const manualThreshold =\n typeof raw.manualThreshold === 'number' ? raw.manualThreshold : null;\n\n // EWMA alpha\n const ewmaAlpha =\n typeof raw.ewmaAlpha === 'number' && raw.ewmaAlpha > 0 && raw.ewmaAlpha <= 1\n ? raw.ewmaAlpha\n : DEFAULTS.ewmaAlpha;\n\n // Threshold bounds with validation\n let boundsMin =\n typeof raw.thresholdBounds?.min === 'number'\n ? raw.thresholdBounds.min\n : DEFAULTS.thresholdBounds.min;\n let boundsMax =\n typeof raw.thresholdBounds?.max === 'number'\n ? raw.thresholdBounds.max\n : DEFAULTS.thresholdBounds.max;\n\n // Validate bounds: min >= 0.05, max <= 0.95, min < max\n if (boundsMin < 0.05) boundsMin = 0.05;\n if (boundsMax > 0.95) boundsMax = 0.95;\n if (boundsMin >= boundsMax) {\n boundsMin = DEFAULTS.thresholdBounds.min;\n boundsMax = DEFAULTS.thresholdBounds.max;\n }\n\n // Enabled toggle\n const enabled = typeof raw.enabled === 'boolean' ? raw.enabled : DEFAULTS.enabled;\n\n return {\n sensitivityPreset: preset,\n sensitivityMultiplier: multiplier,\n manualThreshold,\n ewmaAlpha,\n thresholdBounds: { min: boundsMin, max: boundsMax },\n enabled,\n };\n}\n\n/**\n * Applies a TopicDetectionConfig to a detector and adaptive manager.\n *\n * - If config.enabled is false, sets detector threshold to 999 (never triggers)\n * - If config.manualThreshold is set, uses it directly (bypasses adaptive)\n * - Otherwise, configures the adaptive manager with the sensitivity multiplier\n */\nexport function applyConfig(\n config: TopicDetectionConfig,\n detector: TopicShiftDetector,\n adaptiveManager: AdaptiveThresholdManager,\n): void {\n if (!config.enabled) {\n // Disabled mode: set threshold so high that nothing triggers\n detector.setThreshold(999);\n debug('config', 'Topic detection disabled -- threshold set to 999');\n return;\n }\n\n if (config.manualThreshold !== null) {\n // Manual override: bypass adaptive entirely\n detector.setThreshold(config.manualThreshold);\n debug('config', 'Manual threshold override applied', {\n threshold: config.manualThreshold,\n });\n return;\n }\n\n // Adaptive mode: configure the manager's sensitivity\n // The adaptive manager uses sensitivityMultiplier internally via constructor,\n // but we can influence it by seeding or updating its state.\n // For now, apply the threshold from the adaptive manager to the detector.\n const adaptiveThreshold = adaptiveManager.getThreshold();\n detector.setThreshold(adaptiveThreshold);\n\n debug('config', 'Adaptive config applied', {\n preset: config.sensitivityPreset,\n multiplier: config.sensitivityMultiplier,\n threshold: adaptiveThreshold,\n });\n}\n","/**\n * Graph Extraction Configuration\n *\n * User-configurable settings for knowledge graph extraction behavior.\n * Follows the same pattern as topic-detection-config.ts.\n *\n * Configuration is loaded from .laminark/graph-extraction.json with\n * safe defaults when the file does not exist.\n */\n\nimport { readFileSync } from 'node:fs';\nimport { join } from 'node:path';\n\nimport { debug } from '../shared/debug.js';\nimport { getConfigDir } from '../shared/config.js';\nimport type { EntityType } from '../graph/types.js';\n\n// =============================================================================\n// Types\n// =============================================================================\n\nexport interface GraphExtractionConfig {\n /** Master toggle -- when false, all graph extraction is disabled */\n enabled: boolean;\n\n /** Signal classifier settings */\n signalClassifier: {\n /** Sources that get full extraction (entities + relationships) */\n highSignalSources: string[];\n /** Sources that get entities only (no relationship edges) */\n mediumSignalSources: string[];\n /** Sources that skip graph extraction entirely */\n skipSources: string[];\n /** Minimum content length to process (chars) */\n minContentLength: number;\n };\n\n /** Write-quality gate settings */\n qualityGate: {\n /** Minimum entity name length */\n minNameLength: number;\n /** Maximum entity name length */\n maxNameLength: number;\n /** Maximum File nodes per observation */\n maxFilesPerObservation: number;\n /** Per-type minimum confidence thresholds */\n typeConfidenceThresholds: Record<EntityType, number>;\n /** Confidence multiplier for File paths from non-change observations */\n fileNonChangeMultiplier: number;\n };\n\n /** Relationship detector settings */\n relationshipDetector: {\n /** Minimum edge confidence to persist */\n minEdgeConfidence: number;\n };\n\n /** Temporal decay settings */\n temporalDecay: {\n /** Half-life in days */\n halfLifeDays: number;\n /** Minimum floor weight */\n minFloor: number;\n /** Edges below this weight are deleted during curation */\n deletionThreshold: number;\n /** Maximum age in days before forced deletion */\n maxAgeDays: number;\n };\n\n /** Fuzzy deduplication settings */\n fuzzyDedup: {\n /** Maximum Levenshtein distance for typo matching */\n maxLevenshteinDistance: number;\n /** Minimum Jaccard similarity for word matching */\n jaccardThreshold: number;\n };\n}\n\n// =============================================================================\n// Defaults\n// =============================================================================\n\nconst DEFAULTS: GraphExtractionConfig = {\n enabled: true,\n\n signalClassifier: {\n highSignalSources: ['manual', 'hook:Write', 'hook:Edit', 'hook:WebFetch', 'hook:WebSearch'],\n mediumSignalSources: ['hook:Bash', 'curation:merge'],\n skipSources: [\n 'hook:TaskUpdate', 'hook:TaskCreate', 'hook:EnterPlanMode',\n 'hook:ExitPlanMode', 'hook:Read', 'hook:Glob', 'hook:Grep',\n ],\n minContentLength: 30,\n },\n\n qualityGate: {\n minNameLength: 3,\n maxNameLength: 200,\n maxFilesPerObservation: 5,\n typeConfidenceThresholds: {\n File: 0.95,\n Project: 0.8,\n Reference: 0.85,\n Decision: 0.65,\n Problem: 0.6,\n Solution: 0.6,\n },\n fileNonChangeMultiplier: 0.74,\n },\n\n relationshipDetector: {\n minEdgeConfidence: 0.45,\n },\n\n temporalDecay: {\n halfLifeDays: 30,\n minFloor: 0.05,\n deletionThreshold: 0.08,\n maxAgeDays: 180,\n },\n\n fuzzyDedup: {\n maxLevenshteinDistance: 2,\n jaccardThreshold: 0.7,\n },\n};\n\n// =============================================================================\n// Raw JSON Type\n// =============================================================================\n\ninterface RawConfigJson {\n enabled?: boolean;\n signalClassifier?: {\n highSignalSources?: string[];\n mediumSignalSources?: string[];\n skipSources?: string[];\n minContentLength?: number;\n };\n qualityGate?: {\n minNameLength?: number;\n maxNameLength?: number;\n maxFilesPerObservation?: number;\n typeConfidenceThresholds?: Partial<Record<EntityType, number>>;\n fileNonChangeMultiplier?: number;\n };\n relationshipDetector?: {\n minEdgeConfidence?: number;\n };\n temporalDecay?: {\n halfLifeDays?: number;\n minFloor?: number;\n deletionThreshold?: number;\n maxAgeDays?: number;\n };\n fuzzyDedup?: {\n maxLevenshteinDistance?: number;\n jaccardThreshold?: number;\n };\n}\n\n// =============================================================================\n// Loader\n// =============================================================================\n\n/**\n * Loads graph extraction configuration from disk.\n *\n * Reads .laminark/graph-extraction.json (relative to the Laminark data\n * directory). Falls back to defaults if the file does not exist or\n * cannot be parsed. Validates threshold constraints.\n */\nexport function loadGraphExtractionConfig(): GraphExtractionConfig {\n const configPath = join(getConfigDir(), 'graph-extraction.json');\n\n let raw: RawConfigJson = {};\n\n try {\n const content = readFileSync(configPath, 'utf-8');\n raw = JSON.parse(content) as RawConfigJson;\n debug('config', 'Loaded graph extraction config', { path: configPath });\n } catch {\n debug('config', 'No graph extraction config found, using defaults');\n return { ...DEFAULTS };\n }\n\n // Enabled toggle\n const enabled = typeof raw.enabled === 'boolean' ? raw.enabled : DEFAULTS.enabled;\n\n // Signal classifier\n const signalClassifier = {\n highSignalSources: Array.isArray(raw.signalClassifier?.highSignalSources)\n ? raw.signalClassifier!.highSignalSources\n : DEFAULTS.signalClassifier.highSignalSources,\n mediumSignalSources: Array.isArray(raw.signalClassifier?.mediumSignalSources)\n ? raw.signalClassifier!.mediumSignalSources\n : DEFAULTS.signalClassifier.mediumSignalSources,\n skipSources: Array.isArray(raw.signalClassifier?.skipSources)\n ? raw.signalClassifier!.skipSources\n : DEFAULTS.signalClassifier.skipSources,\n minContentLength: typeof raw.signalClassifier?.minContentLength === 'number'\n && raw.signalClassifier.minContentLength >= 0\n ? raw.signalClassifier.minContentLength\n : DEFAULTS.signalClassifier.minContentLength,\n };\n\n // Quality gate\n const rawQG = raw.qualityGate;\n const typeConf = { ...DEFAULTS.qualityGate.typeConfidenceThresholds };\n if (rawQG?.typeConfidenceThresholds) {\n for (const [key, val] of Object.entries(rawQG.typeConfidenceThresholds)) {\n if (typeof val === 'number' && val >= 0 && val <= 1) {\n typeConf[key as EntityType] = val;\n }\n }\n }\n\n let fileMultiplier = typeof rawQG?.fileNonChangeMultiplier === 'number'\n ? rawQG.fileNonChangeMultiplier\n : DEFAULTS.qualityGate.fileNonChangeMultiplier;\n if (fileMultiplier < 0 || fileMultiplier > 1) {\n fileMultiplier = DEFAULTS.qualityGate.fileNonChangeMultiplier;\n }\n\n const qualityGate = {\n minNameLength: typeof rawQG?.minNameLength === 'number' && rawQG.minNameLength >= 1\n ? rawQG.minNameLength\n : DEFAULTS.qualityGate.minNameLength,\n maxNameLength: typeof rawQG?.maxNameLength === 'number' && rawQG.maxNameLength >= 10\n ? rawQG.maxNameLength\n : DEFAULTS.qualityGate.maxNameLength,\n maxFilesPerObservation: typeof rawQG?.maxFilesPerObservation === 'number'\n && rawQG.maxFilesPerObservation >= 1\n ? rawQG.maxFilesPerObservation\n : DEFAULTS.qualityGate.maxFilesPerObservation,\n typeConfidenceThresholds: typeConf,\n fileNonChangeMultiplier: fileMultiplier,\n };\n\n // Relationship detector\n let minEdge = typeof raw.relationshipDetector?.minEdgeConfidence === 'number'\n ? raw.relationshipDetector.minEdgeConfidence\n : DEFAULTS.relationshipDetector.minEdgeConfidence;\n if (minEdge < 0 || minEdge > 1) {\n minEdge = DEFAULTS.relationshipDetector.minEdgeConfidence;\n }\n const relationshipDetector = { minEdgeConfidence: minEdge };\n\n // Temporal decay\n const rawTD = raw.temporalDecay;\n const temporalDecay = {\n halfLifeDays: typeof rawTD?.halfLifeDays === 'number' && rawTD.halfLifeDays > 0\n ? rawTD.halfLifeDays\n : DEFAULTS.temporalDecay.halfLifeDays,\n minFloor: typeof rawTD?.minFloor === 'number' && rawTD.minFloor >= 0 && rawTD.minFloor < 1\n ? rawTD.minFloor\n : DEFAULTS.temporalDecay.minFloor,\n deletionThreshold: typeof rawTD?.deletionThreshold === 'number'\n && rawTD.deletionThreshold >= 0 && rawTD.deletionThreshold < 1\n ? rawTD.deletionThreshold\n : DEFAULTS.temporalDecay.deletionThreshold,\n maxAgeDays: typeof rawTD?.maxAgeDays === 'number' && rawTD.maxAgeDays > 0\n ? rawTD.maxAgeDays\n : DEFAULTS.temporalDecay.maxAgeDays,\n };\n\n // Fuzzy dedup\n const rawFD = raw.fuzzyDedup;\n const fuzzyDedup = {\n maxLevenshteinDistance: typeof rawFD?.maxLevenshteinDistance === 'number'\n && rawFD.maxLevenshteinDistance >= 1\n ? rawFD.maxLevenshteinDistance\n : DEFAULTS.fuzzyDedup.maxLevenshteinDistance,\n jaccardThreshold: typeof rawFD?.jaccardThreshold === 'number'\n && rawFD.jaccardThreshold > 0 && rawFD.jaccardThreshold <= 1\n ? rawFD.jaccardThreshold\n : DEFAULTS.fuzzyDedup.jaccardThreshold,\n };\n\n return {\n enabled,\n signalClassifier,\n qualityGate,\n relationshipDetector,\n temporalDecay,\n fuzzyDedup,\n };\n}\n","/**\n * Observation merging for knowledge graph curation.\n *\n * Detects near-duplicate observations linked to the same entity using:\n * - Cosine similarity on embeddings (threshold 0.95)\n * - Jaccard similarity on tokenized words (threshold 0.85) as fallback\n *\n * Merging creates consolidated summaries, preserves audit trails via\n * soft-deletion, and computes mean embeddings for consolidated observations.\n *\n * Low-value pruning removes very short, unlinked, old, auto-captured\n * observations using conservative AND-logic (all criteria must match).\n */\n\nimport type BetterSqlite3 from 'better-sqlite3';\nimport { randomBytes } from 'node:crypto';\n\nimport { jaccardSimilarity } from '../shared/similarity.js';\n\n// =============================================================================\n// Types\n// =============================================================================\n\n/**\n * A cluster of observations that are similar enough to merge.\n */\nexport interface MergeCluster {\n entityId: string;\n observations: Array<{\n id: string;\n text: string;\n embedding: number[] | null;\n created_at: string;\n }>;\n similarity: number;\n suggestedSummary: string;\n}\n\ninterface ObsRow {\n id: string;\n content: string;\n embedding: Buffer | null;\n created_at: string;\n source: string;\n deleted_at: string | null;\n}\n\ninterface NodeRow {\n id: string;\n observation_ids: string;\n}\n\n// =============================================================================\n// Similarity Functions\n// =============================================================================\n\n/**\n * Computes cosine similarity between two number arrays.\n * Returns 0 for zero-length or zero-norm vectors.\n */\nfunction cosineSimilarity(a: number[], b: number[]): number {\n if (a.length !== b.length || a.length === 0) return 0;\n\n let dot = 0;\n let normA = 0;\n let normB = 0;\n\n for (let i = 0; i < a.length; i++) {\n dot += a[i] * b[i];\n normA += a[i] * a[i];\n normB += b[i] * b[i];\n }\n\n const denom = Math.sqrt(normA) * Math.sqrt(normB);\n if (denom === 0) return 0;\n\n return dot / denom;\n}\n\n// jaccardSimilarity imported from ../shared/similarity.js\n\n/**\n * Converts a Buffer of Float32 values to a number array.\n */\nfunction bufferToNumbers(buf: Buffer): number[] {\n const floats = new Float32Array(\n buf.buffer,\n buf.byteOffset,\n buf.byteLength / 4,\n );\n return Array.from(floats);\n}\n\n// =============================================================================\n// Clustering\n// =============================================================================\n\n/**\n * Generates a consolidated summary from a cluster of observations.\n *\n * Strategy:\n * 1. Take the longest observation as the base\n * 2. Find unique keywords in shorter observations\n * 3. Append unique info in parentheses\n * 4. Prepend \"[Consolidated from N observations]\"\n */\nfunction generateSummary(\n observations: Array<{ text: string }>,\n): string {\n if (observations.length === 0) return '';\n if (observations.length === 1) return observations[0].text;\n\n // Find longest observation as base\n const sorted = [...observations].sort(\n (a, b) => b.text.length - a.text.length,\n );\n const base = sorted[0];\n const baseWords = new Set(\n base.text\n .toLowerCase()\n .split(/\\s+/)\n .filter((w) => w.length > 2),\n );\n\n // Collect unique keywords from shorter observations\n const uniqueKeywords: string[] = [];\n for (let i = 1; i < sorted.length; i++) {\n const words = sorted[i].text\n .split(/\\s+/)\n .filter((w) => w.length > 2);\n for (const word of words) {\n if (\n !baseWords.has(word.toLowerCase()) &&\n !uniqueKeywords.includes(word.toLowerCase())\n ) {\n uniqueKeywords.push(word.toLowerCase());\n }\n }\n }\n\n let summary = base.text;\n if (uniqueKeywords.length > 0) {\n const extras = uniqueKeywords.slice(0, 10).join(', ');\n summary += ` (also: ${extras})`;\n }\n\n return `[Consolidated from ${observations.length} observations] ${summary}`;\n}\n\n/**\n * Finds clusters of similar observations for the same entity.\n *\n * For each entity with 3+ observations:\n * 1. Compute pairwise similarities (cosine on embeddings, Jaccard on text)\n * 2. Cluster observations where ALL pairwise similarities exceed threshold\n * 3. Generate suggested summaries for each cluster\n *\n * Only clusters with 2+ observations are returned, sorted by size DESC.\n *\n * @param db - better-sqlite3 Database handle\n * @param opts - threshold (default 0.95 cosine / 0.85 Jaccard), entityId filter\n * @returns Mergeable observation clusters sorted by size descending\n */\nexport function findMergeableClusters(\n db: BetterSqlite3.Database,\n opts?: { threshold?: number; entityId?: string },\n): MergeCluster[] {\n const embeddingThreshold = opts?.threshold ?? 0.95;\n const textThreshold = 0.85;\n\n // Get entity nodes with 3+ observations\n let nodes: NodeRow[];\n if (opts?.entityId) {\n const row = db\n .prepare('SELECT id, observation_ids FROM graph_nodes WHERE id = ?')\n .get(opts.entityId) as NodeRow | undefined;\n nodes = row ? [row] : [];\n } else {\n nodes = db\n .prepare('SELECT id, observation_ids FROM graph_nodes')\n .all() as NodeRow[];\n }\n\n const clusters: MergeCluster[] = [];\n\n for (const node of nodes) {\n const obsIds = JSON.parse(node.observation_ids) as string[];\n if (obsIds.length < 3) continue;\n\n // Fetch observations (only non-deleted, non-merged)\n const placeholders = obsIds.map(() => '?').join(', ');\n const rows = db\n .prepare(\n `SELECT id, content, embedding, created_at, source, deleted_at\n FROM observations\n WHERE id IN (${placeholders}) AND deleted_at IS NULL`,\n )\n .all(...obsIds) as ObsRow[];\n\n if (rows.length < 2) continue;\n\n // Build observation objects with parsed embeddings\n const observations = rows.map((r) => ({\n id: r.id,\n text: r.content,\n embedding: r.embedding ? bufferToNumbers(r.embedding) : null,\n created_at: r.created_at,\n }));\n\n // Find clusters using greedy algorithm\n const used = new Set<string>();\n\n for (let i = 0; i < observations.length; i++) {\n if (used.has(observations[i].id)) continue;\n\n const cluster = [observations[i]];\n let totalSim = 0;\n let pairCount = 0;\n\n for (let j = i + 1; j < observations.length; j++) {\n if (used.has(observations[j].id)) continue;\n\n // Check if candidate is similar to ALL members of the current cluster\n let allSimilar = true;\n let candidateSim = 0;\n let candidatePairs = 0;\n\n for (const member of cluster) {\n const sim = computeSimilarity(\n member,\n observations[j],\n embeddingThreshold,\n textThreshold,\n );\n\n if (sim === null) {\n allSimilar = false;\n break;\n }\n\n candidateSim += sim;\n candidatePairs++;\n }\n\n if (allSimilar && candidatePairs > 0) {\n cluster.push(observations[j]);\n totalSim += candidateSim;\n pairCount += candidatePairs;\n }\n }\n\n if (cluster.length >= 2) {\n // Mark all observations in this cluster as used\n for (const obs of cluster) {\n used.add(obs.id);\n }\n\n const avgSim = pairCount > 0 ? totalSim / pairCount : 0;\n\n clusters.push({\n entityId: node.id,\n observations: cluster,\n similarity: avgSim,\n suggestedSummary: generateSummary(cluster),\n });\n }\n }\n }\n\n // Sort by cluster size DESC (largest first)\n clusters.sort((a, b) => b.observations.length - a.observations.length);\n\n return clusters;\n}\n\n/**\n * Computes similarity between two observations.\n * Returns the similarity score if it exceeds the threshold, or null if not.\n */\nfunction computeSimilarity(\n a: { text: string; embedding: number[] | null },\n b: { text: string; embedding: number[] | null },\n embeddingThreshold: number,\n textThreshold: number,\n): number | null {\n // Prefer cosine similarity on embeddings\n if (a.embedding && b.embedding) {\n const sim = cosineSimilarity(a.embedding, b.embedding);\n return sim >= embeddingThreshold ? sim : null;\n }\n\n // Fallback to Jaccard similarity on text\n const sim = jaccardSimilarity(a.text, b.text);\n return sim >= textThreshold ? sim : null;\n}\n\n// =============================================================================\n// Merging\n// =============================================================================\n\n/**\n * Merges a cluster of similar observations into a consolidated observation.\n *\n * Steps:\n * 1. Create new consolidated observation with suggestedSummary text\n * 2. Store merge metadata (merged_from, merged_at, original_count)\n * 3. Update entity's observation_ids: remove old, add new merged ID\n * 4. Soft-delete originals (set deleted_at, do NOT hard delete)\n * 5. Compute mean embedding if originals have embeddings\n *\n * Runs in a transaction for atomicity.\n *\n * @param db - better-sqlite3 Database handle\n * @param cluster - The cluster to merge\n * @returns The new merged observation ID and removed IDs\n */\nexport function mergeObservationCluster(\n db: BetterSqlite3.Database,\n cluster: MergeCluster,\n): { mergedId: string; removedIds: string[] } {\n const merge = db.transaction(() => {\n const mergedId = randomBytes(16).toString('hex');\n const now = new Date().toISOString();\n const removedIds = cluster.observations.map((o) => o.id);\n\n // Step 1 & 2: Create consolidated observation with merge metadata\n const metadata = JSON.stringify({\n merged_from: removedIds,\n merged_at: now,\n original_count: cluster.observations.length,\n });\n\n // Compute mean embedding if available\n let meanEmbedding: Buffer | null = null;\n const embeddingsWithValues = cluster.observations.filter(\n (o) => o.embedding !== null,\n );\n\n if (embeddingsWithValues.length > 0) {\n const dim = embeddingsWithValues[0].embedding!.length;\n const mean = new Float32Array(dim);\n\n for (const obs of embeddingsWithValues) {\n const emb = obs.embedding!;\n for (let i = 0; i < dim; i++) {\n mean[i] += emb[i];\n }\n }\n\n for (let i = 0; i < dim; i++) {\n mean[i] /= embeddingsWithValues.length;\n }\n\n meanEmbedding = Buffer.from(mean.buffer);\n }\n\n // Get project_hash from the first original observation\n const firstObs = db\n .prepare('SELECT project_hash, source FROM observations WHERE id = ?')\n .get(cluster.observations[0].id) as\n | { project_hash: string; source: string }\n | undefined;\n\n const projectHash = firstObs?.project_hash ?? 'unknown';\n\n db.prepare(\n `INSERT INTO observations (id, project_hash, content, title, source, session_id, embedding, created_at, updated_at)\n VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?)`,\n ).run(\n mergedId,\n projectHash,\n cluster.suggestedSummary,\n `[Merged] ${metadata}`,\n 'curation:merge',\n null,\n meanEmbedding,\n now,\n now,\n );\n\n // Step 3: Update entity's observation_ids\n const nodeRow = db\n .prepare('SELECT observation_ids FROM graph_nodes WHERE id = ?')\n .get(cluster.entityId) as { observation_ids: string } | undefined;\n\n if (nodeRow) {\n const currentIds = JSON.parse(nodeRow.observation_ids) as string[];\n const removedSet = new Set(removedIds);\n const updatedIds = currentIds.filter((id) => !removedSet.has(id));\n updatedIds.push(mergedId);\n\n db.prepare(\n `UPDATE graph_nodes SET observation_ids = ?, updated_at = datetime('now') WHERE id = ?`,\n ).run(JSON.stringify(updatedIds), cluster.entityId);\n }\n\n // Step 4: Soft-delete original observations\n const softDeleteStmt = db.prepare(\n `UPDATE observations SET deleted_at = ? WHERE id = ?`,\n );\n for (const obsId of removedIds) {\n softDeleteStmt.run(now, obsId);\n }\n\n return { mergedId, removedIds };\n });\n\n return merge();\n}\n\n// =============================================================================\n// Low-Value Pruning\n// =============================================================================\n\n/**\n * Prunes low-value observations using conservative AND-logic.\n *\n * An observation is pruned ONLY if ALL of:\n * a. Very short (< minTextLength characters, default 20)\n * b. No linked entities (not in any graph_node's observation_ids)\n * c. Older than maxAge days (default 90)\n * d. Auto-captured (source is NOT 'mcp:save_memory' or 'slash:remember')\n * e. Not already deleted\n *\n * Pruning is soft-delete only -- sets deleted_at, never hard deletes.\n *\n * @param db - better-sqlite3 Database handle\n * @param opts - Configurable thresholds\n * @returns Count of pruned observations\n */\nexport function pruneLowValue(\n db: BetterSqlite3.Database,\n opts?: { minTextLength?: number; maxAge?: number },\n): { pruned: number } {\n const minTextLength = opts?.minTextLength ?? 20;\n const maxAgeDays = opts?.maxAge ?? 90;\n\n const now = new Date();\n const cutoffDate = new Date(\n now.getTime() - maxAgeDays * 24 * 60 * 60 * 1000,\n );\n const cutoffISO = cutoffDate.toISOString();\n\n // Find candidate observations: short, old, auto-captured, not deleted\n const candidates = db\n .prepare(\n `SELECT id, content, source, created_at\n FROM observations\n WHERE deleted_at IS NULL\n AND LENGTH(content) < ?\n AND created_at < ?\n AND source NOT IN ('mcp:save_memory', 'slash:remember')`,\n )\n .all(minTextLength, cutoffISO) as Array<{\n id: string;\n content: string;\n source: string;\n created_at: string;\n }>;\n\n if (candidates.length === 0) return { pruned: 0 };\n\n // Check which candidates have NO linked entities\n // Build a set of all observation IDs referenced by any graph node\n const allNodeObsIds = new Set<string>();\n const nodes = db\n .prepare('SELECT observation_ids FROM graph_nodes')\n .all() as Array<{ observation_ids: string }>;\n\n for (const node of nodes) {\n const ids = JSON.parse(node.observation_ids) as string[];\n for (const id of ids) {\n allNodeObsIds.add(id);\n }\n }\n\n // Filter: only prune candidates that are NOT linked to any entity\n const toPrune = candidates.filter((c) => !allNodeObsIds.has(c.id));\n\n if (toPrune.length === 0) return { pruned: 0 };\n\n // Soft-delete\n const nowISO = now.toISOString();\n const softDeleteStmt = db.prepare(\n 'UPDATE observations SET deleted_at = ? WHERE id = ?',\n );\n\n const prune = db.transaction(() => {\n for (const obs of toPrune) {\n softDeleteStmt.run(nowISO, obs.id);\n }\n return toPrune.length;\n });\n\n const pruned = prune();\n\n return { pruned };\n}\n","/**\n * Fuzzy deduplication strategies for knowledge graph entities.\n *\n * Extends the existing exact-match, abbreviation, and path normalization\n * strategies with:\n * - Levenshtein distance (max 2 chars) for typo tolerance\n * - Jaccard word similarity (0.7 threshold) on tokenized names\n * - Path suffix matching for File type\n *\n * These are integrated into the entity deduplication pipeline via\n * findFuzzyDuplicates(), called from constraints.ts.\n */\n\nimport type { GraphNode } from './types.js';\nimport type { GraphExtractionConfig } from '../config/graph-extraction-config.js';\n\n// =============================================================================\n// Constants\n// =============================================================================\n\nconst DEFAULT_MAX_LEVENSHTEIN = 2;\nconst DEFAULT_JACCARD_THRESHOLD = 0.7;\n\n// =============================================================================\n// Levenshtein Distance\n// =============================================================================\n\n/**\n * Computes Levenshtein edit distance between two strings.\n * Uses the iterative matrix approach with O(min(m,n)) space.\n */\nexport function levenshteinDistance(a: string, b: string): number {\n if (a === b) return 0;\n if (a.length === 0) return b.length;\n if (b.length === 0) return a.length;\n\n // Ensure a is the shorter string for space optimization\n if (a.length > b.length) {\n [a, b] = [b, a];\n }\n\n const aLen = a.length;\n const bLen = b.length;\n\n let prev = new Array<number>(aLen + 1);\n let curr = new Array<number>(aLen + 1);\n\n for (let i = 0; i <= aLen; i++) {\n prev[i] = i;\n }\n\n for (let j = 1; j <= bLen; j++) {\n curr[0] = j;\n for (let i = 1; i <= aLen; i++) {\n const cost = a[i - 1] === b[j - 1] ? 0 : 1;\n curr[i] = Math.min(\n prev[i] + 1, // deletion\n curr[i - 1] + 1, // insertion\n prev[i - 1] + cost, // substitution\n );\n }\n [prev, curr] = [curr, prev];\n }\n\n return prev[aLen];\n}\n\n// =============================================================================\n// Jaccard Word Similarity\n// =============================================================================\n\n/**\n * Tokenizes a name by splitting on common delimiters: / . _ -\n * and lowercasing all tokens.\n */\nexport function tokenizeName(name: string): Set<string> {\n const tokens = name.toLowerCase().split(/[/._\\-\\s]+/).filter(t => t.length > 0);\n return new Set(tokens);\n}\n\n/**\n * Computes Jaccard similarity between two token sets.\n * J(A,B) = |A ∩ B| / |A ∪ B|\n * Returns 0 if both sets are empty.\n */\nexport function jaccardSimilarity(a: Set<string>, b: Set<string>): number {\n if (a.size === 0 && b.size === 0) return 0;\n\n let intersection = 0;\n for (const token of a) {\n if (b.has(token)) intersection++;\n }\n\n const union = a.size + b.size - intersection;\n return union === 0 ? 0 : intersection / union;\n}\n\n// =============================================================================\n// Path Suffix Matching\n// =============================================================================\n\n/**\n * Checks if two File paths refer to the same file via suffix matching.\n * For example: \"src/graph/types.ts\" and \"graph/types.ts\" match because\n * one is a suffix of the other.\n */\nexport function isPathSuffixMatch(path1: string, path2: string): boolean {\n // Normalize paths\n const norm1 = path1.replace(/^\\.\\//, '').replace(/\\\\/g, '/').toLowerCase();\n const norm2 = path2.replace(/^\\.\\//, '').replace(/\\\\/g, '/').toLowerCase();\n\n if (norm1 === norm2) return false; // Exact match handled elsewhere\n\n // Check if one is a suffix of the other\n return norm1.endsWith('/' + norm2) || norm2.endsWith('/' + norm1);\n}\n\n// =============================================================================\n// Fuzzy Duplicate Detection\n// =============================================================================\n\n/**\n * Finds fuzzy duplicates among a list of same-type nodes.\n *\n * Strategies applied:\n * 1. Levenshtein distance ≤ max (default 2) for typo tolerance\n * 2. Jaccard word similarity ≥ threshold (default 0.7)\n * 3. Path suffix matching for File type\n *\n * Only compares nodes of the same type. Returns grouped duplicate\n * candidates with reasons.\n *\n * @param nodes - Nodes to check (should be same type for best results)\n * @param config - Optional configuration overrides\n * @returns Array of duplicate groups with entities and reason\n */\nexport function findFuzzyDuplicates(\n nodes: GraphNode[],\n config?: GraphExtractionConfig,\n): Array<{ entities: GraphNode[]; reason: string }> {\n const maxLev = config?.fuzzyDedup?.maxLevenshteinDistance ?? DEFAULT_MAX_LEVENSHTEIN;\n const jaccardThresh = config?.fuzzyDedup?.jaccardThreshold ?? DEFAULT_JACCARD_THRESHOLD;\n\n const duplicates: Array<{ entities: GraphNode[]; reason: string }> = [];\n const seen = new Set<string>(); // Track already-grouped pairs\n\n for (let i = 0; i < nodes.length; i++) {\n for (let j = i + 1; j < nodes.length; j++) {\n const a = nodes[i];\n const b = nodes[j];\n\n // Only compare same-type entities\n if (a.type !== b.type) continue;\n\n const pairKey = [a.id, b.id].sort().join(',');\n if (seen.has(pairKey)) continue;\n\n const aLower = a.name.toLowerCase();\n const bLower = b.name.toLowerCase();\n\n // Skip exact case-insensitive matches (handled by existing strategy)\n if (aLower === bLower) continue;\n\n // Strategy 1: Levenshtein distance for short names (avoid expensive computation on long strings)\n if (aLower.length <= 50 && bLower.length <= 50) {\n // Only apply if lengths are similar (within maxLev difference)\n if (Math.abs(aLower.length - bLower.length) <= maxLev) {\n const dist = levenshteinDistance(aLower, bLower);\n if (dist > 0 && dist <= maxLev) {\n seen.add(pairKey);\n duplicates.push({\n entities: [a, b],\n reason: `Fuzzy match (Levenshtein distance ${dist}): \"${a.name}\" ↔ \"${b.name}\"`,\n });\n continue;\n }\n }\n }\n\n // Strategy 2: Jaccard word similarity\n const tokensA = tokenizeName(a.name);\n const tokensB = tokenizeName(b.name);\n // Only apply if both have multiple tokens (single-token names use Levenshtein)\n if (tokensA.size >= 2 && tokensB.size >= 2) {\n const similarity = jaccardSimilarity(tokensA, tokensB);\n if (similarity >= jaccardThresh) {\n seen.add(pairKey);\n duplicates.push({\n entities: [a, b],\n reason: `Fuzzy match (Jaccard similarity ${similarity.toFixed(2)}): \"${a.name}\" ↔ \"${b.name}\"`,\n });\n continue;\n }\n }\n\n // Strategy 3: Path suffix matching (File type only)\n if (a.type === 'File') {\n if (isPathSuffixMatch(a.name, b.name)) {\n seen.add(pairKey);\n duplicates.push({\n entities: [a, b],\n reason: `Path suffix match: \"${a.name}\" ↔ \"${b.name}\"`,\n });\n }\n }\n }\n }\n\n return duplicates;\n}\n","/**\n * Graph constraint enforcement.\n *\n * Maintains graph health through:\n * - Entity type taxonomy validation (defense-in-depth with SQL CHECK)\n * - Relationship type taxonomy validation\n * - Max degree enforcement (prune lowest-weight edges when cap exceeded)\n * - Entity deduplication detection and merging\n * - Graph health dashboard metrics\n *\n * All enforcement functions use transactions for atomicity.\n * Significant actions (pruning, merging) are logged with [laminark:graph] prefix.\n */\n\nimport type BetterSqlite3 from 'better-sqlite3';\n\nimport type { EntityType, RelationshipType, GraphNode, GraphEdge } from './types.js';\nimport {\n ENTITY_TYPES,\n RELATIONSHIP_TYPES,\n MAX_NODE_DEGREE,\n} from './types.js';\nimport {\n getEdgesForNode,\n countEdgesForNode,\n getNodesByType,\n} from './schema.js';\nimport { findFuzzyDuplicates } from './fuzzy-dedup.js';\nimport type { GraphExtractionConfig } from '../config/graph-extraction-config.js';\n\n// =============================================================================\n// Type Validation\n// =============================================================================\n\n/**\n * Runtime validation for entity types. Defense-in-depth alongside SQL CHECK.\n */\nexport function validateEntityType(type: string): type is EntityType {\n return (ENTITY_TYPES as readonly string[]).includes(type);\n}\n\n/**\n * Runtime validation for relationship types. Defense-in-depth alongside SQL CHECK.\n */\nexport function validateRelationshipType(type: string): type is RelationshipType {\n return (RELATIONSHIP_TYPES as readonly string[]).includes(type);\n}\n\n// =============================================================================\n// Max Degree Enforcement\n// =============================================================================\n\n/**\n * Enforces maximum edge count on a node by pruning lowest-weight edges.\n *\n * When a node exceeds maxDegree edges:\n * 1. Get all edges for the node\n * 2. Sort by weight ascending (lowest first)\n * 3. Delete lowest-weight edges until count <= maxDegree\n * 4. Log pruned count with [laminark:graph] prefix\n *\n * Runs in a transaction to prevent race conditions.\n *\n * @param db - Database handle\n * @param nodeId - The node to enforce degree cap on\n * @param maxDegree - Maximum allowed edges (default: MAX_NODE_DEGREE = 50)\n * @returns Object with pruned count and remaining count\n */\nexport function enforceMaxDegree(\n db: BetterSqlite3.Database,\n nodeId: string,\n maxDegree: number = MAX_NODE_DEGREE,\n): { pruned: number; remaining: number } {\n const enforce = db.transaction(() => {\n const currentCount = countEdgesForNode(db, nodeId, null);\n\n if (currentCount <= maxDegree) {\n return { pruned: 0, remaining: currentCount };\n }\n\n // Get all edges, sorted by weight ascending (lowest first)\n const edges = getEdgesForNode(db, nodeId, { projectHash: null });\n edges.sort((a, b) => a.weight - b.weight);\n\n const toPrune = currentCount - maxDegree;\n const edgesToDelete = edges.slice(0, toPrune);\n\n const deleteStmt = db.prepare('DELETE FROM graph_edges WHERE id = ?');\n for (const edge of edgesToDelete) {\n deleteStmt.run(edge.id);\n }\n\n const remaining = currentCount - toPrune;\n\n process.stderr.write(\n `[laminark:graph] Pruned ${toPrune} lowest-weight edges from node ${nodeId} (${remaining} remaining)\\n`,\n );\n\n return { pruned: toPrune, remaining };\n });\n\n return enforce();\n}\n\n// =============================================================================\n// Entity Merging\n// =============================================================================\n\n/**\n * Merges one entity node into another. The keepId node survives.\n *\n * Steps:\n * 1. Union observation_ids from both nodes (no duplicates)\n * 2. Reroute all edges from mergeId to keepId\n * 3. Handle duplicate edge conflicts (keep higher weight)\n * 4. Delete the mergeId node\n *\n * Runs in a transaction for atomicity.\n *\n * @param db - Database handle\n * @param keepId - The node to keep (survives merge)\n * @param mergeId - The node to merge and delete\n */\nexport function mergeEntities(\n db: BetterSqlite3.Database,\n keepId: string,\n mergeId: string,\n): void {\n const merge = db.transaction(() => {\n // Step 1: Get both nodes and merge observation_ids\n const keepRow = db\n .prepare('SELECT * FROM graph_nodes WHERE id = ?')\n .get(keepId) as { observation_ids: string; metadata: string } | undefined;\n const mergeRow = db\n .prepare('SELECT * FROM graph_nodes WHERE id = ?')\n .get(mergeId) as { observation_ids: string; metadata: string } | undefined;\n\n if (!keepRow || !mergeRow) {\n throw new Error(`Cannot merge: one or both nodes not found (keep=${keepId}, merge=${mergeId})`);\n }\n\n const keepObsIds = JSON.parse(keepRow.observation_ids) as string[];\n const mergeObsIds = JSON.parse(mergeRow.observation_ids) as string[];\n const mergedObsIds = [...new Set([...keepObsIds, ...mergeObsIds])];\n\n // Merge metadata (merge node values fill gaps, keep node values take priority)\n const keepMeta = JSON.parse(keepRow.metadata) as Record<string, unknown>;\n const mergeMeta = JSON.parse(mergeRow.metadata) as Record<string, unknown>;\n const mergedMeta = { ...mergeMeta, ...keepMeta };\n\n db.prepare(\n `UPDATE graph_nodes SET observation_ids = ?, metadata = ?, updated_at = datetime('now') WHERE id = ?`,\n ).run(JSON.stringify(mergedObsIds), JSON.stringify(mergedMeta), keepId);\n\n // Step 2: Get all edges connected to the merge node\n const mergeEdges = getEdgesForNode(db, mergeId, { projectHash: null });\n\n // Step 3: Reroute edges from mergeId to keepId\n for (const edge of mergeEdges) {\n let newSourceId = edge.source_id;\n let newTargetId = edge.target_id;\n\n if (edge.source_id === mergeId) {\n newSourceId = keepId;\n }\n if (edge.target_id === mergeId) {\n newTargetId = keepId;\n }\n\n // Skip self-loops (would happen if both source and target are the merge/keep pair)\n if (newSourceId === newTargetId) {\n db.prepare('DELETE FROM graph_edges WHERE id = ?').run(edge.id);\n continue;\n }\n\n // Check if rerouted edge would create a duplicate\n const existing = db\n .prepare(\n 'SELECT id, weight FROM graph_edges WHERE source_id = ? AND target_id = ? AND type = ?',\n )\n .get(newSourceId, newTargetId, edge.type) as\n | { id: string; weight: number }\n | undefined;\n\n if (existing && existing.id !== edge.id) {\n // Duplicate: keep higher weight, delete the lower one\n if (edge.weight > existing.weight) {\n db.prepare('UPDATE graph_edges SET weight = ? WHERE id = ?').run(\n edge.weight,\n existing.id,\n );\n }\n db.prepare('DELETE FROM graph_edges WHERE id = ?').run(edge.id);\n } else if (!existing) {\n // No duplicate: update the edge to point to keepId\n db.prepare(\n 'UPDATE graph_edges SET source_id = ?, target_id = ? WHERE id = ?',\n ).run(newSourceId, newTargetId, edge.id);\n }\n // If existing.id === edge.id, it's already correct (no-op)\n }\n\n // Step 4: Delete the merged node\n db.prepare('DELETE FROM graph_nodes WHERE id = ?').run(mergeId);\n\n process.stderr.write(\n `[laminark:graph] Merged entity ${mergeId} into ${keepId} (${mergeEdges.length} edges rerouted)\\n`,\n );\n });\n\n merge();\n}\n\n// =============================================================================\n// Duplicate Detection\n// =============================================================================\n\n/**\n * Common abbreviation mappings for duplicate detection.\n * Maps lowercase abbreviation -> lowercase full name.\n */\nconst ABBREVIATION_MAP: Record<string, string> = {\n // File extension abbreviations (for File entity dedup)\n ts: 'typescript',\n js: 'javascript',\n py: 'python',\n rb: 'ruby',\n rs: 'rust',\n};\n\n/**\n * Finds potential duplicate entities in the graph.\n *\n * Detection strategies:\n * a. Case-insensitive name match (e.g., \"React\" and \"react\")\n * b. Common abbreviation match (e.g., \"TS\" and \"TypeScript\")\n * c. Path normalization for Files (strip ./, normalize separators)\n *\n * Returns grouped duplicate candidates with reasons. This is a\n * SUGGESTION function -- use mergeEntities() to act on results.\n *\n * @param db - Database handle\n * @param opts - Optional filter by entity type\n * @returns Array of duplicate groups with entities and reason\n */\nexport function findDuplicateEntities(\n db: BetterSqlite3.Database,\n opts?: { type?: EntityType; graphConfig?: GraphExtractionConfig },\n): Array<{ entities: GraphNode[]; reason: string }> {\n // Get all nodes, optionally filtered by type\n let nodes: GraphNode[];\n if (opts?.type) {\n nodes = getNodesByType(db, opts.type, null);\n } else {\n const allTypes = ENTITY_TYPES;\n nodes = [];\n for (const type of allTypes) {\n nodes.push(...getNodesByType(db, type, null));\n }\n }\n\n const duplicates: Array<{ entities: GraphNode[]; reason: string }> = [];\n const seen = new Set<string>(); // Track already-grouped node IDs\n\n // Strategy A: Case-insensitive name match within same type\n const byTypeAndLowerName = new Map<string, GraphNode[]>();\n for (const node of nodes) {\n const key = `${node.type}:${node.name.toLowerCase()}`;\n const group = byTypeAndLowerName.get(key) ?? [];\n group.push(node);\n byTypeAndLowerName.set(key, group);\n }\n\n for (const [, group] of byTypeAndLowerName) {\n if (group.length > 1) {\n const ids = group.map((n) => n.id).sort().join(',');\n if (!seen.has(ids)) {\n seen.add(ids);\n duplicates.push({\n entities: group,\n reason: `Case-insensitive name match: \"${group[0].name}\" and \"${group[1].name}\"`,\n });\n }\n }\n }\n\n // Strategy B: Common abbreviation match within same type\n const byTypeAndCanonical = new Map<string, GraphNode[]>();\n for (const node of nodes) {\n const lower = node.name.toLowerCase();\n const canonical = ABBREVIATION_MAP[lower] ?? lower;\n const key = `${node.type}:${canonical}`;\n const group = byTypeAndCanonical.get(key) ?? [];\n group.push(node);\n byTypeAndCanonical.set(key, group);\n }\n\n for (const [, group] of byTypeAndCanonical) {\n if (group.length > 1) {\n // Check if this is a genuine abbreviation match (not just case match already found)\n const names = new Set(group.map((n) => n.name.toLowerCase()));\n if (names.size > 1) {\n const ids = group.map((n) => n.id).sort().join(',');\n if (!seen.has(ids)) {\n seen.add(ids);\n duplicates.push({\n entities: group,\n reason: `Common abbreviation match: \"${group[0].name}\" and \"${group[1].name}\"`,\n });\n }\n }\n }\n }\n\n // Strategy C: Path normalization for File type\n if (!opts?.type || opts.type === 'File') {\n const fileNodes = nodes.filter((n) => n.type === 'File');\n const byNormalizedPath = new Map<string, GraphNode[]>();\n\n for (const node of fileNodes) {\n let normalized = node.name;\n // Strip leading ./\n if (normalized.startsWith('./')) normalized = normalized.slice(2);\n // Normalize separators\n normalized = normalized.replace(/\\\\/g, '/');\n // Collapse double slashes\n normalized = normalized.replace(/\\/\\//g, '/');\n // Lowercase for comparison\n normalized = normalized.toLowerCase();\n\n const key = `File:${normalized}`;\n const group = byNormalizedPath.get(key) ?? [];\n group.push(node);\n byNormalizedPath.set(key, group);\n }\n\n for (const [, group] of byNormalizedPath) {\n if (group.length > 1) {\n const ids = group.map((n) => n.id).sort().join(',');\n if (!seen.has(ids)) {\n seen.add(ids);\n duplicates.push({\n entities: group,\n reason: `Path normalization match: \"${group[0].name}\" and \"${group[1].name}\"`,\n });\n }\n }\n }\n }\n\n // Strategy D: Fuzzy deduplication (Levenshtein, Jaccard, path suffix)\n // Group nodes by type for efficient comparison\n const byType = new Map<string, GraphNode[]>();\n for (const node of nodes) {\n const group = byType.get(node.type) ?? [];\n group.push(node);\n byType.set(node.type, group);\n }\n\n for (const [, typeNodes] of byType) {\n const fuzzyResults = findFuzzyDuplicates(typeNodes, opts?.graphConfig);\n for (const result of fuzzyResults) {\n const ids = result.entities.map((n) => n.id).sort().join(',');\n if (!seen.has(ids)) {\n seen.add(ids);\n duplicates.push(result);\n }\n }\n }\n\n return duplicates;\n}\n\n// =============================================================================\n// Graph Health Dashboard\n// =============================================================================\n\ninterface NodeRow {\n id: string;\n type: string;\n name: string;\n metadata: string;\n observation_ids: string;\n created_at: string;\n updated_at: string;\n}\n\n/**\n * Returns dashboard-style health metrics for the knowledge graph.\n *\n * Metrics:\n * - totalNodes: total entity count\n * - totalEdges: total relationship count\n * - avgDegree: average edges per node\n * - maxDegree: highest edge count on any single node\n * - hotspots: nodes with degree > 0.8 * MAX_NODE_DEGREE\n * - duplicateCandidates: number of detected duplicate groups\n */\nexport function getGraphHealth(db: BetterSqlite3.Database): {\n totalNodes: number;\n totalEdges: number;\n avgDegree: number;\n maxDegree: number;\n hotspots: Array<{ node: GraphNode; degree: number }>;\n duplicateCandidates: number;\n} {\n const totalNodes =\n (db.prepare('SELECT COUNT(*) as cnt FROM graph_nodes').get() as { cnt: number }).cnt;\n const totalEdges =\n (db.prepare('SELECT COUNT(*) as cnt FROM graph_edges').get() as { cnt: number }).cnt;\n\n const avgDegree = totalNodes > 0 ? (totalEdges * 2) / totalNodes : 0;\n\n // Find max degree and hotspot nodes\n const hotspotThreshold = Math.floor(0.8 * MAX_NODE_DEGREE);\n let maxDeg = 0;\n const hotspots: Array<{ node: GraphNode; degree: number }> = [];\n\n if (totalNodes > 0) {\n // Get degree for each node using a correlated subquery\n const degreeRows = db\n .prepare(\n `SELECT n.*,\n (SELECT COUNT(*) FROM graph_edges WHERE source_id = n.id OR target_id = n.id) as degree\n FROM graph_nodes n\n WHERE (SELECT COUNT(*) FROM graph_edges WHERE source_id = n.id OR target_id = n.id) > 0\n ORDER BY degree DESC`,\n )\n .all() as Array<NodeRow & { degree: number }>;\n\n for (const row of degreeRows) {\n if (row.degree > maxDeg) maxDeg = row.degree;\n\n if (row.degree > hotspotThreshold) {\n hotspots.push({\n node: {\n id: row.id,\n type: row.type as EntityType,\n name: row.name,\n metadata: JSON.parse(row.metadata) as Record<string, unknown>,\n observation_ids: JSON.parse(row.observation_ids) as string[],\n created_at: row.created_at,\n updated_at: row.updated_at,\n },\n degree: row.degree,\n });\n }\n }\n }\n\n // Count duplicate candidates\n const dupes = findDuplicateEntities(db);\n\n return {\n totalNodes,\n totalEdges,\n avgDegree,\n maxDegree: maxDeg,\n hotspots,\n duplicateCandidates: dupes.length,\n };\n}\n","/**\n * Temporal decay for graph edge weights.\n *\n * Edge weights decay over time using exponential function inspired by\n * memento-mcp and agent-memory servers. Edges that aren't reinforced\n * (re-detected) gradually lose weight and eventually get deleted.\n *\n * Formula: weight * e^(-ln(2)/halfLife * ageDays)\n *\n * This maintains a living graph where recent knowledge is prominent\n * and old, unreinforced knowledge fades naturally.\n */\n\nimport type BetterSqlite3 from 'better-sqlite3';\n\nimport type { GraphExtractionConfig } from '../config/graph-extraction-config.js';\n\n// =============================================================================\n// Types\n// =============================================================================\n\nexport interface TemporalDecayConfig {\n /** Half-life in days. After this many days, unreinforced edges are at 50% weight. */\n halfLifeDays: number;\n /** Minimum floor weight. Edges never decay below this value (until deletion). */\n minFloor: number;\n /** Edges below this weight are deleted during curation. */\n deletionThreshold: number;\n /** Maximum age in days. Edges older than this are always deleted. */\n maxAgeDays: number;\n}\n\nexport interface DecayResult {\n updated: number;\n deleted: number;\n}\n\n// =============================================================================\n// Constants\n// =============================================================================\n\nconst DEFAULTS: TemporalDecayConfig = {\n halfLifeDays: 30,\n minFloor: 0.05,\n deletionThreshold: 0.08,\n maxAgeDays: 180,\n};\n\n// =============================================================================\n// Core Decay Functions\n// =============================================================================\n\n/**\n * Calculates the decayed weight for an edge based on its age.\n *\n * Uses exponential decay: weight * e^(-ln(2)/halfLife * ageDays)\n * Result is clamped to the minimum floor.\n *\n * @param originalWeight - The edge's current stored weight\n * @param ageDays - Age of the edge in days\n * @param config - Decay parameters\n * @returns The decayed weight value\n */\nexport function calculateDecayedWeight(\n originalWeight: number,\n ageDays: number,\n config?: Partial<TemporalDecayConfig>,\n): number {\n const halfLife = config?.halfLifeDays ?? DEFAULTS.halfLifeDays;\n const minFloor = config?.minFloor ?? DEFAULTS.minFloor;\n\n if (ageDays <= 0) return originalWeight;\n\n const decayRate = Math.LN2 / halfLife;\n const decayed = originalWeight * Math.exp(-decayRate * ageDays);\n\n return Math.max(decayed, minFloor);\n}\n\n/**\n * Applies temporal decay to all edges in the graph.\n *\n * For each edge:\n * 1. Calculate age from created_at timestamp\n * 2. Apply exponential decay formula\n * 3. Delete edges below deletion threshold or older than max age\n * 4. Update remaining edges with new decayed weights\n *\n * Runs in a transaction for atomicity.\n *\n * @param db - Database handle\n * @param graphConfig - Optional configuration from graph-extraction-config\n * @returns Count of updated and deleted edges\n */\nexport function applyTemporalDecay(\n db: BetterSqlite3.Database,\n graphConfig?: GraphExtractionConfig,\n): DecayResult {\n const halfLife = graphConfig?.temporalDecay?.halfLifeDays ?? DEFAULTS.halfLifeDays;\n const minFloor = graphConfig?.temporalDecay?.minFloor ?? DEFAULTS.minFloor;\n const deletionThreshold = graphConfig?.temporalDecay?.deletionThreshold ?? DEFAULTS.deletionThreshold;\n const maxAgeDays = graphConfig?.temporalDecay?.maxAgeDays ?? DEFAULTS.maxAgeDays;\n\n let updated = 0;\n let deleted = 0;\n\n const run = db.transaction(() => {\n // Get all edges with their age in days\n const edges = db.prepare(`\n SELECT id, weight,\n julianday('now') - julianday(created_at) as age_days\n FROM graph_edges\n `).all() as Array<{ id: string; weight: number; age_days: number }>;\n\n const deleteStmt = db.prepare('DELETE FROM graph_edges WHERE id = ?');\n const updateStmt = db.prepare('UPDATE graph_edges SET weight = ? WHERE id = ?');\n\n for (const edge of edges) {\n // Delete edges older than max age\n if (edge.age_days > maxAgeDays) {\n deleteStmt.run(edge.id);\n deleted++;\n continue;\n }\n\n // Calculate decayed weight\n const decayed = calculateDecayedWeight(edge.weight, edge.age_days, {\n halfLifeDays: halfLife,\n minFloor,\n });\n\n // Delete edges below deletion threshold\n if (decayed < deletionThreshold) {\n deleteStmt.run(edge.id);\n deleted++;\n continue;\n }\n\n // Update weight if it changed meaningfully (avoid unnecessary writes)\n if (Math.abs(decayed - edge.weight) > 0.001) {\n updateStmt.run(decayed, edge.id);\n updated++;\n }\n }\n });\n\n run();\n return { updated, deleted };\n}\n","/**\n * Background curation agent for knowledge graph maintenance.\n *\n * Runs during quiet periods (session end, long pauses) to keep the\n * knowledge base high-quality as it grows. Performs:\n * 1. Observation merging (near-duplicate consolidation)\n * 2. Entity deduplication (case-insensitive, abbreviation, path)\n * 3. Graph constraint enforcement (approaching degree cap)\n * 4. Staleness sweep (contradiction flagging)\n * 5. Low-value pruning (short + unlinked + old + auto-captured)\n *\n * Each step is isolated -- one failure does not stop others.\n * The agent is idempotent: running twice produces the same result.\n * Curation NEVER crashes the main process.\n */\n\nimport type BetterSqlite3 from 'better-sqlite3';\n\nimport {\n findMergeableClusters,\n mergeObservationCluster,\n pruneLowValue,\n} from './observation-merger.js';\nimport {\n findDuplicateEntities,\n mergeEntities,\n enforceMaxDegree,\n} from './constraints.js';\nimport { countEdgesForNode } from './schema.js';\nimport {\n detectStaleness,\n flagStaleObservation,\n initStalenessSchema,\n} from './staleness.js';\nimport { MAX_NODE_DEGREE } from './types.js';\nimport { applyTemporalDecay } from './temporal-decay.js';\nimport type { GraphExtractionConfig } from '../config/graph-extraction-config.js';\n\n// =============================================================================\n// Types\n// =============================================================================\n\n/**\n * Report of a completed curation cycle.\n */\nexport interface CurationReport {\n startedAt: string;\n completedAt: string;\n observationsMerged: number;\n entitiesDeduplicated: number;\n stalenessFlagsAdded: number;\n lowValuePruned: number;\n temporalDecayUpdated: number;\n temporalDecayDeleted: number;\n errors: string[];\n}\n\n// =============================================================================\n// Standalone Curation Function\n// =============================================================================\n\n/**\n * Runs a single curation cycle on the knowledge graph.\n *\n * Executes five steps in order:\n * 1. Merge similar observations\n * 2. Deduplicate entities\n * 3. Enforce graph constraints (approaching degree cap)\n * 4. Staleness sweep\n * 5. Low-value pruning\n *\n * Each step is wrapped in try/catch -- if one fails, the rest continue.\n * Returns a CurationReport documenting all actions taken.\n *\n * This function is idempotent: running it twice in a row produces the\n * same result (merged observations do not re-merge, already-flagged\n * stale observations do not get re-flagged, etc.)\n *\n * @param db - better-sqlite3 Database handle\n * @returns CurationReport with counts and any errors\n */\nexport async function runCuration(\n db: BetterSqlite3.Database,\n graphConfig?: GraphExtractionConfig,\n): Promise<CurationReport> {\n const startedAt = new Date().toISOString();\n const errors: string[] = [];\n let observationsMerged = 0;\n let entitiesDeduplicated = 0;\n let stalenessFlagsAdded = 0;\n let lowValuePruned = 0;\n let temporalDecayUpdated = 0;\n let temporalDecayDeleted = 0;\n\n // Ensure staleness schema exists\n try {\n initStalenessSchema(db);\n } catch (err) {\n errors.push(`Schema init: ${err instanceof Error ? err.message : String(err)}`);\n }\n\n // -----------------------------------------------------------------------\n // Step 1: Merge similar observations\n // -----------------------------------------------------------------------\n try {\n const clusters = findMergeableClusters(db);\n for (const cluster of clusters) {\n try {\n const result = mergeObservationCluster(db, cluster);\n observationsMerged += result.removedIds.length;\n } catch (err) {\n errors.push(\n `Merge cluster (entity ${cluster.entityId}): ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n }\n } catch (err) {\n errors.push(\n `Step 1 (merge): ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n\n // -----------------------------------------------------------------------\n // Step 2: Deduplicate entities\n // -----------------------------------------------------------------------\n try {\n const duplicates = findDuplicateEntities(db);\n for (const group of duplicates) {\n if (group.entities.length < 2) continue;\n\n // Keep the entity with more observation_ids\n const sorted = [...group.entities].sort(\n (a, b) => b.observation_ids.length - a.observation_ids.length,\n );\n const keepId = sorted[0].id;\n\n for (let i = 1; i < sorted.length; i++) {\n try {\n mergeEntities(db, keepId, sorted[i].id);\n entitiesDeduplicated++;\n } catch (err) {\n errors.push(\n `Dedup (${sorted[0].name} <- ${sorted[i].name}): ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n }\n }\n } catch (err) {\n errors.push(\n `Step 2 (dedup): ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n\n // -----------------------------------------------------------------------\n // Step 3: Enforce graph constraints (nodes approaching degree cap)\n // -----------------------------------------------------------------------\n try {\n const threshold = Math.floor(MAX_NODE_DEGREE * 0.9);\n const nodeRows = db\n .prepare('SELECT id FROM graph_nodes')\n .all() as Array<{ id: string }>;\n\n for (const row of nodeRows) {\n try {\n const degree = countEdgesForNode(db, row.id, null);\n if (degree > threshold) {\n enforceMaxDegree(db, row.id);\n }\n } catch (err) {\n errors.push(\n `Constraint (node ${row.id}): ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n }\n } catch (err) {\n errors.push(\n `Step 3 (constraints): ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n\n // -----------------------------------------------------------------------\n // Step 4: Staleness sweep\n // -----------------------------------------------------------------------\n try {\n // Check recently updated entities for contradictions\n const recentNodes = db\n .prepare(\n `SELECT id FROM graph_nodes WHERE updated_at >= datetime('now', '-24 hours')`,\n )\n .all() as Array<{ id: string }>;\n\n // Get already-flagged observation IDs to avoid re-flagging\n const existingFlags = new Set<string>();\n try {\n const flagRows = db\n .prepare('SELECT observation_id FROM staleness_flags WHERE resolved = 0')\n .all() as Array<{ observation_id: string }>;\n for (const row of flagRows) {\n existingFlags.add(row.observation_id);\n }\n } catch {\n // Table might not exist yet -- that's fine\n }\n\n for (const node of recentNodes) {\n try {\n const reports = detectStaleness(db, node.id);\n for (const report of reports) {\n // Only flag if not already flagged (idempotency)\n if (!existingFlags.has(report.olderObservation.id)) {\n flagStaleObservation(db, report.olderObservation.id, report.reason);\n existingFlags.add(report.olderObservation.id);\n stalenessFlagsAdded++;\n }\n }\n } catch (err) {\n errors.push(\n `Staleness (node ${node.id}): ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n }\n } catch (err) {\n errors.push(\n `Step 4 (staleness): ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n\n // -----------------------------------------------------------------------\n // Step 5: Low-value pruning\n // -----------------------------------------------------------------------\n try {\n const result = pruneLowValue(db);\n lowValuePruned = result.pruned;\n } catch (err) {\n errors.push(\n `Step 5 (prune): ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n\n // -----------------------------------------------------------------------\n // Step 6: Temporal decay of edge weights\n // -----------------------------------------------------------------------\n try {\n const decayResult = applyTemporalDecay(db, graphConfig);\n temporalDecayUpdated = decayResult.updated;\n temporalDecayDeleted = decayResult.deleted;\n } catch (err) {\n errors.push(\n `Step 6 (temporal decay): ${err instanceof Error ? err.message : String(err)}`,\n );\n }\n\n const completedAt = new Date().toISOString();\n\n const report: CurationReport = {\n startedAt,\n completedAt,\n observationsMerged,\n entitiesDeduplicated,\n stalenessFlagsAdded,\n lowValuePruned,\n temporalDecayUpdated,\n temporalDecayDeleted,\n errors,\n };\n\n process.stderr.write(\n `[laminark:curation] Cycle complete: ${observationsMerged} merged, ${entitiesDeduplicated} deduped, ${stalenessFlagsAdded} flagged stale, ${lowValuePruned} pruned, ${temporalDecayUpdated} decayed, ${temporalDecayDeleted} decay-deleted\\n`,\n );\n\n return report;\n}\n\n// =============================================================================\n// CurationAgent Class\n// =============================================================================\n\n/**\n * Background curation agent that runs periodically or on-demand.\n *\n * Manages scheduling, lifecycle, and reporting. Uses the standalone\n * runCuration() function for the actual curation logic.\n */\nexport class CurationAgent {\n private db: BetterSqlite3.Database;\n private intervalMs: number;\n private onComplete?: (report: CurationReport) => void;\n private graphConfig?: GraphExtractionConfig;\n private running: boolean = false;\n private cycling: boolean = false;\n private lastRun: string | null = null;\n private timer: ReturnType<typeof setInterval> | null = null;\n\n constructor(\n db: BetterSqlite3.Database,\n opts?: {\n intervalMs?: number;\n onComplete?: (report: CurationReport) => void;\n graphConfig?: GraphExtractionConfig;\n },\n ) {\n this.db = db;\n this.intervalMs = opts?.intervalMs ?? 300_000; // 5 minutes default\n this.onComplete = opts?.onComplete;\n this.graphConfig = opts?.graphConfig;\n }\n\n /**\n * Start periodic curation on setInterval.\n */\n start(): void {\n if (this.running) return;\n\n this.running = true;\n this.timer = setInterval(() => {\n void this.runOnce();\n }, this.intervalMs);\n\n process.stderr.write(\n `[laminark:curation] Agent started, interval: ${this.intervalMs}ms\\n`,\n );\n }\n\n /**\n * Stop the periodic curation timer.\n */\n stop(): void {\n if (this.timer) {\n clearInterval(this.timer);\n this.timer = null;\n }\n this.running = false;\n\n process.stderr.write('[laminark:curation] Agent stopped\\n');\n }\n\n /**\n * Execute one curation cycle. This is the main entry point.\n */\n async runOnce(): Promise<CurationReport> {\n if (this.cycling) return { startedAt: '', completedAt: '', observationsMerged: 0, entitiesDeduplicated: 0, stalenessFlagsAdded: 0, lowValuePruned: 0, temporalDecayUpdated: 0, temporalDecayDeleted: 0, errors: ['skipped: previous cycle still running'] };\n this.cycling = true;\n try {\n const report = await runCuration(this.db, this.graphConfig);\n this.lastRun = report.completedAt;\n\n if (this.onComplete) {\n this.onComplete(report);\n }\n\n return report;\n } finally {\n this.cycling = false;\n }\n }\n\n /**\n * Whether the agent is currently running.\n */\n isRunning(): boolean {\n return this.running;\n }\n\n /**\n * Timestamp of the last completed curation run.\n */\n getLastRun(): string | null {\n return this.lastRun;\n }\n}\n\n// =============================================================================\n// Integration Trigger Functions\n// =============================================================================\n\n/**\n * Triggered at session end. Runs a targeted curation cycle\n * focusing on the current session's observations only (faster\n * than a full sweep).\n *\n * Note: Currently runs the full cycle since targeted per-session\n * filtering would require session_id awareness in all curation steps.\n * The full cycle is fast enough for session-end triggers.\n */\nexport async function onSessionEnd(\n db: BetterSqlite3.Database,\n): Promise<CurationReport> {\n return runCuration(db);\n}\n\n/**\n * Triggered when no activity detected for 5+ minutes.\n * Runs the full curation cycle.\n */\nexport async function onQuietPeriod(\n db: BetterSqlite3.Database,\n): Promise<CurationReport> {\n return runCuration(db);\n}\n","/**\n * Combined noise/signal and observation classification agent.\n *\n * Uses a single Haiku call to determine:\n * 1. Whether an observation is noise or signal\n * 2. If signal, what kind of observation it is (discovery/problem/solution)\n * 3. Whether the observation contains debug signals (error/resolution detection)\n *\n * Replaces both the regex-based noise-patterns.ts/signal-classifier.ts and the\n * broken MCP sampling observation-classifier.ts with a single focused LLM call.\n */\n\nimport { z } from 'zod';\n\nimport type { ObservationClassification } from '../shared/types.js';\nimport { callHaiku, extractJsonFromResponse } from './haiku-client.js';\n\n// ---------------------------------------------------------------------------\n// Zod validation schema\n// ---------------------------------------------------------------------------\n\nconst DebugSignalSchema = z.object({\n is_error: z.boolean(),\n is_resolution: z.boolean(),\n waypoint_hint: z.enum([\n 'error', 'attempt', 'failure', 'success',\n 'pivot', 'revert', 'discovery', 'resolution',\n ]).nullable(),\n confidence: z.number(),\n}).nullable();\n\nconst ClassificationSchema = z.object({\n signal: z.enum(['noise', 'signal']),\n classification: z.enum(['discovery', 'problem', 'solution']).nullable(),\n reason: z.string(),\n debug_signal: DebugSignalSchema.default(null),\n});\n\n// ---------------------------------------------------------------------------\n// Exported types\n// ---------------------------------------------------------------------------\n\nexport type DebugSignal = {\n is_error: boolean;\n is_resolution: boolean;\n waypoint_hint: 'error' | 'attempt' | 'failure' | 'success' | 'pivot' | 'revert' | 'discovery' | 'resolution' | null;\n confidence: number;\n};\n\nexport type ClassificationResult = {\n signal: 'noise' | 'signal';\n classification: ObservationClassification | null;\n reason: string;\n debug_signal: DebugSignal | null;\n};\n\n// ---------------------------------------------------------------------------\n// System prompt\n// ---------------------------------------------------------------------------\n\nconst SYSTEM_PROMPT = `You classify developer observations for a knowledge management system.\n\nFor each observation, determine:\n1. signal: Is this noise or signal?\n - \"noise\": build output, linter spam, package install logs, empty/trivial content, routine navigation, repeated boilerplate, test runner output with no failures\n - \"signal\": meaningful findings, decisions, problems, solutions, reference material, architectural insights\n\n2. classification (only if signal): What kind of observation is this?\n - \"discovery\": new understanding, finding, insight, or reference material\n - \"problem\": error, bug, failure, or obstacle encountered\n - \"solution\": fix, resolution, workaround, or decision that resolved something\n\n3. debug_signal (always, even for noise): Is this related to ACTIVE debugging (the developer hit an actual error)?\n - is_error: Did an actual error/failure OCCUR in this observation? An error message, stack trace, test failure, or build failure that happened RIGHT NOW. NOT research about errors — searching for \"reconnection problems\" or reading docs about error handling is NOT is_error. The tool itself must have failed or produced an error.\n - is_resolution: Does this indicate a successful fix, passing test, or resolved error?\n - waypoint_hint: If debug-related, what type? \"error\" (an actual error occurred), \"attempt\" (trying a fix), \"failure\" (fix didn't work), \"success\" (something passed), \"pivot\" (changing approach), \"revert\" (undoing a change), \"discovery\" (learned something new), \"resolution\" (final fix). null if not debug-related. WebSearch/WebFetch/AskUserQuestion are typically \"discovery\" or null, NOT \"error\".\n - confidence: 0.0-1.0 how confident this is debug activity\n\nReturn JSON: {\"signal\": \"noise\"|\"signal\", \"classification\": \"discovery\"|\"problem\"|\"solution\"|null, \"reason\": \"brief\", \"debug_signal\": {\"is_error\": bool, \"is_resolution\": bool, \"waypoint_hint\": \"type\"|null, \"confidence\": 0.0-1.0}|null}\nIf noise, classification must be null. debug_signal can be non-null even for noise (e.g., build failure output is noise but debug-relevant).\nNo markdown, no explanation, ONLY the JSON object.`;\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Classifies an observation as noise/signal and determines its kind using Haiku.\n *\n * @param text - The observation content to classify\n * @param source - Optional source context (e.g., \"PostToolUse:Read\", \"UserMessage\")\n * @returns Classification result with signal/noise determination and observation kind\n */\nexport async function classifyWithHaiku(\n text: string,\n source?: string,\n): Promise<ClassificationResult> {\n let userContent = text;\n if (source) {\n userContent = `Source: ${source}\\n\\nObservation:\\n${text}`;\n }\n\n const response = await callHaiku(SYSTEM_PROMPT, userContent, 512);\n const parsed = extractJsonFromResponse(response);\n return ClassificationSchema.parse(parsed) as ClassificationResult;\n}\n","/**\n * Entity extraction agent.\n *\n * Uses Haiku to extract typed entities from observation text.\n * Replaces the regex-based extraction-rules.ts with LLM-powered analysis.\n * Returns entities validated against the fixed 6-type taxonomy from graph/types.ts.\n */\n\nimport { z } from 'zod';\n\nimport { ENTITY_TYPES, type EntityType } from '../graph/types.js';\nimport { callHaiku, extractJsonFromResponse } from './haiku-client.js';\n\n// ---------------------------------------------------------------------------\n// Zod validation schemas\n// ---------------------------------------------------------------------------\n\nconst EntitySchema = z.object({\n name: z.string().min(1),\n type: z.enum(ENTITY_TYPES),\n confidence: z.number().min(0).max(1),\n});\n\nconst EntityArraySchema = z.array(EntitySchema);\n\n// ---------------------------------------------------------------------------\n// System prompt\n// ---------------------------------------------------------------------------\n\nconst SYSTEM_PROMPT = `You extract structured entities from developer observations.\n\nEntity types (use ONLY these exact strings):\n- File: file paths (src/foo/bar.ts, package.json, ./config.yml)\n- Project: repository names (org/repo), npm packages (@scope/pkg)\n- Reference: URLs (https://...)\n- Decision: explicit choices made (\"decided to use X\", \"chose Y over Z\")\n- Problem: bugs, errors, failures, obstacles encountered\n- Solution: fixes, resolutions, workarounds applied\n\nRules:\n- Extract ALL entities present in the text\n- For Decision/Problem/Solution, extract the descriptive phrase (not just the keyword)\n- Confidence: 0.9+ for unambiguous (file paths, URLs), 0.7-0.8 for clear context, 0.5-0.6 for inferred\n- Return a JSON array: [{\"name\": \"...\", \"type\": \"...\", \"confidence\": 0.0-1.0}]\n- Return [] if no entities found\n- No markdown, no explanation, ONLY the JSON array`;\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Extracts entities from observation text using Haiku.\n *\n * @param text - The observation content to analyze\n * @returns Validated array of extracted entities with type and confidence\n */\nexport async function extractEntitiesWithHaiku(\n text: string,\n): Promise<Array<{ name: string; type: EntityType; confidence: number }>> {\n const response = await callHaiku(SYSTEM_PROMPT, text, 512);\n const parsed = extractJsonFromResponse(response);\n return EntityArraySchema.parse(parsed);\n}\n","/**\n * Relationship inference agent.\n *\n * Uses Haiku to infer typed relationships between entities extracted from\n * observation text. Replaces the regex-based relationship-detector.ts with\n * LLM-powered contextual inference.\n * Returns relationships validated against the fixed 8-type taxonomy from graph/types.ts.\n */\n\nimport { z } from 'zod';\n\nimport { RELATIONSHIP_TYPES, type RelationshipType, type EntityType } from '../graph/types.js';\nimport { callHaiku, extractJsonFromResponse } from './haiku-client.js';\n\n// ---------------------------------------------------------------------------\n// Zod validation schemas\n// ---------------------------------------------------------------------------\n\nconst RelationshipSchema = z.object({\n source: z.string(),\n target: z.string(),\n type: z.enum(RELATIONSHIP_TYPES),\n confidence: z.number().min(0).max(1),\n});\n\nconst RelationshipArraySchema = z.array(RelationshipSchema);\n\n// ---------------------------------------------------------------------------\n// System prompt\n// ---------------------------------------------------------------------------\n\nconst SYSTEM_PROMPT = `You infer relationships between entities extracted from a developer observation.\n\nGiven observation text and a list of entities, determine which entities are related and how.\n\nRelationship types (use ONLY these exact strings):\n- modifies: entity A changed/edited/created entity B\n- informed_by: entity A was researched/consulted using entity B\n- verified_by: entity A was tested/confirmed by entity B\n- caused_by: entity A was caused by entity B\n- solved_by: entity A was resolved by entity B\n- references: entity A references/links to entity B\n- preceded_by: entity A came after entity B temporally\n- related_to: generic relationship (use sparingly, prefer specific types)\n\nRules:\n- Only infer relationships with clear textual evidence\n- Source and target must both be in the provided entity list\n- Confidence: 0.8+ for explicit language, 0.5-0.7 for implied\n- Return JSON array: [{\"source\": \"entity name\", \"target\": \"entity name\", \"type\": \"...\", \"confidence\": 0.0-1.0}]\n- Return [] if no relationships found\n- No markdown, no explanation, ONLY the JSON array`;\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Infers relationships between entities using Haiku.\n *\n * @param text - The observation content providing context\n * @param entities - Array of entities extracted from the same observation\n * @returns Validated array of inferred relationships with type and confidence\n */\nexport async function inferRelationshipsWithHaiku(\n text: string,\n entities: Array<{ name: string; type: EntityType }>,\n): Promise<Array<{ source: string; target: string; type: RelationshipType; confidence: number }>> {\n const entityList = JSON.stringify(entities.map((e) => ({ name: e.name, type: e.type })));\n const userContent = `Observation:\\n${text}\\n\\nEntities found:\\n${entityList}`;\n\n const response = await callHaiku(SYSTEM_PROMPT, userContent, 512);\n const parsed = extractJsonFromResponse(response);\n return RelationshipArraySchema.parse(parsed);\n}\n","/**\n * Write-quality gate for entity extraction filtering.\n *\n * Filters individual entities before they enter the knowledge graph\n * to prevent low-quality nodes from accumulating. Applies:\n * - Name length bounds (min 3, max 200 chars)\n * - Vague/filler name rejection\n * - Per-type minimum confidence thresholds\n * - File node cap per observation\n * - Context-aware confidence adjustment (File paths from non-change\n * observations get confidence reduced)\n */\n\nimport type { EntityType } from './types.js';\nimport type { GraphExtractionConfig } from '../config/graph-extraction-config.js';\n\n// =============================================================================\n// Types\n// =============================================================================\n\nexport interface QualityGateEntity {\n name: string;\n type: EntityType;\n confidence: number;\n}\n\nexport interface QualityGateResult {\n passed: QualityGateEntity[];\n rejected: Array<{ entity: QualityGateEntity; reason: string }>;\n}\n\n// =============================================================================\n// Constants\n// =============================================================================\n\nconst DEFAULT_MIN_NAME_LENGTH = 3;\nconst DEFAULT_MAX_NAME_LENGTH = 200;\nconst DEFAULT_MAX_FILES_PER_OBSERVATION = 5;\n\n/**\n * Vague name prefixes that indicate low-quality entity names.\n * Case-insensitive match against the start of the entity name.\n */\nconst VAGUE_PREFIXES = [\n 'the ', 'this ', 'that ', 'it ', 'some ', 'a ', 'an ',\n 'here ', 'there ', 'now ', 'just ',\n 'ok ', 'yes ', 'no ', 'maybe ', 'done ', 'tmp ',\n];\n\n/**\n * Per-type minimum confidence thresholds.\n * High-signal types (Decision, Problem, Solution) have lower thresholds\n * to capture more of the valuable knowledge. File has the highest\n * threshold to reduce noise.\n */\nconst DEFAULT_TYPE_CONFIDENCE: Record<EntityType, number> = {\n File: 0.95,\n Project: 0.8,\n Reference: 0.85,\n Decision: 0.65,\n Problem: 0.6,\n Solution: 0.6,\n};\n\n/**\n * Context-aware confidence multiplier for File paths from non-change\n * observations. Reduces 0.95 -> ~0.70, below the 0.95 File threshold.\n */\nconst DEFAULT_FILE_NON_CHANGE_MULTIPLIER = 0.74;\n\n// =============================================================================\n// Core Quality Gate\n// =============================================================================\n\n/**\n * Applies quality gate filters to a list of extracted entities.\n *\n * Steps:\n * 1. Apply context-aware confidence adjustment (File paths in non-change obs)\n * 2. Reject entities with names outside length bounds\n * 3. Reject entities with vague/filler name prefixes\n * 4. Apply per-type confidence thresholds\n * 5. Cap File nodes to max per observation (keep highest confidence)\n *\n * @param entities - Extracted entities to filter\n * @param isChangeObservation - Whether the source observation is a change/write\n * @param config - Optional configuration overrides\n * @returns Entities that passed the gate, plus rejected entities with reasons\n */\nexport function applyQualityGate(\n entities: QualityGateEntity[],\n isChangeObservation: boolean,\n config?: GraphExtractionConfig,\n): QualityGateResult {\n const minNameLen = config?.qualityGate?.minNameLength ?? DEFAULT_MIN_NAME_LENGTH;\n const maxNameLen = config?.qualityGate?.maxNameLength ?? DEFAULT_MAX_NAME_LENGTH;\n const maxFiles = config?.qualityGate?.maxFilesPerObservation ?? DEFAULT_MAX_FILES_PER_OBSERVATION;\n const typeConfidence = config?.qualityGate?.typeConfidenceThresholds ?? DEFAULT_TYPE_CONFIDENCE;\n const fileMultiplier = config?.qualityGate?.fileNonChangeMultiplier ?? DEFAULT_FILE_NON_CHANGE_MULTIPLIER;\n\n const passed: QualityGateEntity[] = [];\n const rejected: Array<{ entity: QualityGateEntity; reason: string }> = [];\n\n for (const entity of entities) {\n // Step 1: Context-aware confidence adjustment\n let adjustedConfidence = entity.confidence;\n if (entity.type === 'File' && !isChangeObservation) {\n adjustedConfidence = entity.confidence * fileMultiplier;\n }\n\n const adjusted = { ...entity, confidence: adjustedConfidence };\n\n // Step 2: Name length bounds\n if (adjusted.name.length < minNameLen) {\n rejected.push({ entity: adjusted, reason: `Name too short (${adjusted.name.length} < ${minNameLen})` });\n continue;\n }\n if (adjusted.name.length > maxNameLen) {\n rejected.push({ entity: adjusted, reason: `Name too long (${adjusted.name.length} > ${maxNameLen})` });\n continue;\n }\n\n // Step 3: Vague name rejection\n const lowerName = adjusted.name.toLowerCase();\n const isVague = VAGUE_PREFIXES.some(prefix => lowerName.startsWith(prefix));\n if (isVague) {\n rejected.push({ entity: adjusted, reason: `Vague name prefix: \"${adjusted.name}\"` });\n continue;\n }\n\n // Step 4: Per-type confidence threshold\n const threshold = typeConfidence[adjusted.type] ?? DEFAULT_TYPE_CONFIDENCE[adjusted.type] ?? 0.5;\n if (adjusted.confidence < threshold) {\n rejected.push({\n entity: adjusted,\n reason: `Below ${adjusted.type} confidence threshold (${adjusted.confidence.toFixed(2)} < ${threshold})`,\n });\n continue;\n }\n\n passed.push(adjusted);\n }\n\n // Step 5: Cap File nodes per observation\n const fileEntities = passed.filter(e => e.type === 'File');\n if (fileEntities.length > maxFiles) {\n // Sort by confidence descending, keep top N\n fileEntities.sort((a, b) => b.confidence - a.confidence);\n const toRemove = new Set(\n fileEntities.slice(maxFiles).map(e => e.name),\n );\n const finalPassed: QualityGateEntity[] = [];\n for (const e of passed) {\n if (e.type === 'File' && toRemove.has(e.name)) {\n rejected.push({ entity: e, reason: `File cap exceeded (max ${maxFiles} per observation)` });\n } else {\n finalPassed.push(e);\n }\n }\n return { passed: finalPassed, rejected };\n }\n\n return { passed, rejected };\n}\n","/**\n * Server-Sent Events endpoint for live updates.\n *\n * Maintains a set of connected SSE clients and provides a broadcast\n * function for pushing real-time events to all connected browsers.\n * Includes a ring buffer for event replay on reconnection via\n * Last-Event-ID header support.\n *\n * Supported event types:\n * - connected: initial handshake\n * - heartbeat: keepalive ping (every 30s)\n * - new_observation: new observation stored\n * - topic_shift: topic shift detected\n * - entity_updated: graph entity created/modified\n * - session_start: new session started\n * - session_end: session ended\n *\n * @module web/routes/sse\n */\n\nimport { Hono } from 'hono';\nimport type { Context } from 'hono';\nimport { debug } from '../../shared/debug.js';\n\n// ---------------------------------------------------------------------------\n// Client management\n// ---------------------------------------------------------------------------\n\ninterface SSEClient {\n id: string;\n controller: ReadableStreamDefaultController;\n heartbeatTimer: ReturnType<typeof setInterval>;\n}\n\nconst clients = new Set<SSEClient>();\n\nlet clientIdCounter = 0;\n\n// ---------------------------------------------------------------------------\n// Event ID counter and ring buffer for replay\n// ---------------------------------------------------------------------------\n\nlet lastEventId = 0;\n\ninterface BufferedEvent {\n id: number;\n event: string;\n data: string;\n}\n\nconst RING_BUFFER_SIZE = 100;\nconst eventRingBuffer: BufferedEvent[] = [];\n\n/**\n * Adds an event to the ring buffer, evicting the oldest if full.\n */\nfunction pushToRingBuffer(entry: BufferedEvent): void {\n if (eventRingBuffer.length >= RING_BUFFER_SIZE) {\n eventRingBuffer.shift();\n }\n eventRingBuffer.push(entry);\n}\n\n/**\n * Returns all events with id > sinceId from the ring buffer.\n */\nfunction getEventsSince(sinceId: number): BufferedEvent[] {\n return eventRingBuffer.filter((e) => e.id > sinceId);\n}\n\n// ---------------------------------------------------------------------------\n// SSE formatting helpers\n// ---------------------------------------------------------------------------\n\nfunction formatSSE(event: string, data: string, id?: number): string {\n let msg = '';\n if (id !== undefined) {\n msg += `id: ${id}\\n`;\n }\n msg += `event: ${event}\\ndata: ${data}\\n\\n`;\n return msg;\n}\n\nfunction sendToClient(client: SSEClient, event: string, data: string, id?: number): boolean {\n try {\n const message = formatSSE(event, data, id);\n client.controller.enqueue(new TextEncoder().encode(message));\n return true;\n } catch {\n // Client disconnected or stream errored\n return false;\n }\n}\n\n// ---------------------------------------------------------------------------\n// Route group\n// ---------------------------------------------------------------------------\n\nexport const sseRoutes = new Hono();\n\n/**\n * GET /api/sse\n *\n * Server-Sent Events endpoint. Keeps the connection alive with heartbeats\n * and receives broadcast events for live UI updates.\n *\n * Supports Last-Event-ID header for replay of missed events on reconnection.\n */\nsseRoutes.get('/sse', (c: Context) => {\n const clientId = String(++clientIdCounter);\n const lastEventIdHeader = c.req.header('Last-Event-ID');\n const replayFromId = lastEventIdHeader ? parseInt(lastEventIdHeader, 10) : 0;\n\n let client: SSEClient;\n\n const stream = new ReadableStream({\n start(controller) {\n // Create client with heartbeat\n const heartbeatTimer = setInterval(() => {\n const ok = sendToClient(client, 'heartbeat', JSON.stringify({ timestamp: Date.now() }));\n if (!ok) {\n clearInterval(heartbeatTimer);\n clients.delete(client);\n debug('db', 'SSE client heartbeat failed, removed', { clientId });\n }\n }, 30_000);\n\n client = { id: clientId, controller, heartbeatTimer };\n clients.add(client);\n\n debug('db', 'SSE client connected', { clientId, total: clients.size });\n\n // Send initial connected event\n sendToClient(client, 'connected', JSON.stringify({\n timestamp: Date.now(),\n clientId,\n }));\n\n // Replay missed events if client is reconnecting with Last-Event-ID\n if (replayFromId > 0) {\n const missed = getEventsSince(replayFromId);\n for (const entry of missed) {\n sendToClient(client, entry.event, entry.data, entry.id);\n }\n if (missed.length > 0) {\n debug('db', 'SSE replayed missed events', { clientId, count: missed.length, sinceId: replayFromId });\n }\n }\n },\n cancel() {\n // Client disconnected\n if (client) {\n clearInterval(client.heartbeatTimer);\n clients.delete(client);\n debug('db', 'SSE client disconnected', { clientId, total: clients.size });\n }\n },\n });\n\n return new Response(stream, {\n headers: {\n 'Content-Type': 'text/event-stream',\n 'Cache-Control': 'no-cache',\n 'Connection': 'keep-alive',\n 'X-Accel-Buffering': 'no', // Disable nginx buffering if proxied\n },\n });\n});\n\n// ---------------------------------------------------------------------------\n// Broadcast\n// ---------------------------------------------------------------------------\n\n/**\n * Broadcasts an event to all connected SSE clients.\n *\n * Each broadcast increments a monotonic event ID that is included in the\n * SSE `id:` field. Events are stored in an in-memory ring buffer (last 100)\n * so reconnecting clients can replay missed events via Last-Event-ID.\n *\n * Automatically removes disconnected clients that fail to receive\n * the message.\n *\n * @param event - Event name (e.g., 'new_observation', 'topic_shift')\n * @param data - Data object to serialize as JSON\n */\nexport function broadcast(event: string, data: object): void {\n const eventId = ++lastEventId;\n const json = JSON.stringify(data);\n\n // Store in ring buffer for replay\n pushToRingBuffer({ id: eventId, event, data: json });\n\n if (clients.size === 0) return;\n\n const dead: SSEClient[] = [];\n\n for (const client of clients) {\n const ok = sendToClient(client, event, json, eventId);\n if (!ok) {\n dead.push(client);\n }\n }\n\n // Clean up dead clients\n for (const client of dead) {\n clearInterval(client.heartbeatTimer);\n clients.delete(client);\n }\n\n if (dead.length > 0) {\n debug('db', 'SSE broadcast cleaned dead clients', { dead: dead.length, remaining: clients.size });\n }\n}\n\n/**\n * Returns the current number of connected SSE clients.\n * Useful for health/stats endpoints.\n */\nexport function getClientCount(): number {\n return clients.size;\n}\n","/**\n * Background Haiku processing orchestrator.\n *\n * Runs on a timer, picks up unclassified observations, and processes them\n * through the Haiku agent pipeline:\n * 1. Classify (noise/signal + discovery/problem/solution)\n * 2. Extract entities via Haiku\n * 3. Infer relationships via Haiku\n *\n * Noise observations are soft-deleted after classification (store-then-soft-delete).\n * Replaces the broken MCP sampling ObservationClassifier and the regex-based\n * entity extraction / relationship detection in the embedding loop.\n */\n\nimport type BetterSqlite3 from 'better-sqlite3';\n\nimport { classifyWithHaiku } from './haiku-classifier-agent.js';\nimport { extractEntitiesWithHaiku } from './haiku-entity-agent.js';\nimport { inferRelationshipsWithHaiku } from './haiku-relationship-agent.js';\nimport { isHaikuEnabled } from './haiku-client.js';\nimport { ObservationRepository } from '../storage/observations.js';\nimport { upsertNode, getNodeByNameAndType, insertEdge } from '../graph/schema.js';\nimport { applyQualityGate } from '../graph/write-quality-gate.js';\nimport { enforceMaxDegree } from '../graph/constraints.js';\nimport { broadcast } from '../web/routes/sse.js';\nimport { debug } from '../shared/debug.js';\nimport type { EntityType } from '../graph/types.js';\nimport type { PathTracker } from '../paths/path-tracker.js';\nimport type { BranchTracker } from '../branches/branch-tracker.js';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\ninterface HaikuProcessorOptions {\n intervalMs?: number;\n batchSize?: number;\n concurrency?: number;\n pathTracker?: PathTracker;\n branchTracker?: BranchTracker;\n}\n\n// ---------------------------------------------------------------------------\n// HaikuProcessor\n// ---------------------------------------------------------------------------\n\nexport class HaikuProcessor {\n private readonly db: BetterSqlite3.Database;\n private readonly projectHash: string;\n private readonly intervalMs: number;\n private readonly batchSize: number;\n private readonly concurrency: number;\n private readonly pathTracker: PathTracker | null;\n private readonly branchTracker: BranchTracker | null;\n private timer: ReturnType<typeof setInterval> | null = null;\n\n constructor(\n db: BetterSqlite3.Database,\n projectHash: string,\n opts?: HaikuProcessorOptions,\n ) {\n this.db = db;\n this.projectHash = projectHash;\n this.intervalMs = opts?.intervalMs ?? 30_000;\n this.batchSize = opts?.batchSize ?? 10;\n this.concurrency = opts?.concurrency ?? 3;\n this.pathTracker = opts?.pathTracker ?? null;\n this.branchTracker = opts?.branchTracker ?? null;\n }\n\n start(): void {\n if (this.timer) return;\n debug('haiku', 'HaikuProcessor started', {\n intervalMs: this.intervalMs,\n batchSize: this.batchSize,\n concurrency: this.concurrency,\n });\n this.timer = setInterval(() => {\n this.processOnce().catch((err) => {\n const msg = err instanceof Error ? err.message : String(err);\n debug('haiku', 'HaikuProcessor cycle error', { error: msg });\n });\n }, this.intervalMs);\n }\n\n stop(): void {\n if (this.timer) {\n clearInterval(this.timer);\n this.timer = null;\n debug('haiku', 'HaikuProcessor stopped');\n }\n }\n\n async processOnce(): Promise<void> {\n if (!isHaikuEnabled()) return;\n\n // Query unclassified observations across ALL projects to avoid missing\n // observations when the MCP server's project hash doesn't match the\n // actual project (e.g., server started from plugin install directory).\n const unclassified = ObservationRepository.listAllUnclassified(this.db, this.batchSize);\n\n if (unclassified.length === 0) return;\n\n debug('haiku', 'Processing unclassified observations', {\n count: unclassified.length,\n });\n\n // Group by project hash so each gets the correct ObservationRepository\n const byProject = new Map<string, typeof unclassified>();\n for (const obs of unclassified) {\n const hash = obs.projectHash;\n if (!byProject.has(hash)) byProject.set(hash, []);\n byProject.get(hash)!.push(obs);\n }\n\n for (const [hash, projectObs] of byProject) {\n const repo = new ObservationRepository(this.db, hash);\n for (let i = 0; i < projectObs.length; i += this.concurrency) {\n const batch = projectObs.slice(i, i + this.concurrency);\n await Promise.all(batch.map((obs) => this.processOne(obs, repo, hash)));\n }\n }\n\n // Step 4: Branch maintenance\n if (this.branchTracker) {\n try {\n await this.branchTracker.runMaintenance();\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n debug('haiku', 'Branch maintenance error (non-fatal)', { error: msg });\n }\n }\n }\n\n private async processOne(\n obs: { id: string; content: string; source: string },\n repo: ObservationRepository,\n obsProjectHash?: string,\n ): Promise<void> {\n const projectHash = obsProjectHash ?? this.projectHash;\n try {\n // Step 1: Classify via Haiku\n let classification: string;\n try {\n const result = await classifyWithHaiku(obs.content, obs.source);\n\n // Step 1.5: Feed debug signal to path tracker (between classify and extract)\n // Runs BEFORE noise early-return — even noise can contain debug-relevant errors\n if (this.pathTracker && result.debug_signal) {\n try {\n this.pathTracker.processSignal(result.debug_signal, obs.id, obs.content);\n } catch (pathErr) {\n const msg = pathErr instanceof Error ? pathErr.message : String(pathErr);\n debug('haiku', 'Path tracking failed (non-fatal)', { id: obs.id, error: msg });\n }\n }\n\n // Step 1.6: Feed to branch tracker\n if (this.branchTracker) {\n try {\n this.branchTracker.processObservation({\n id: obs.id,\n content: obs.content,\n source: obs.source,\n projectHash: obsProjectHash ?? this.projectHash,\n sessionId: undefined,\n classification: result.classification,\n createdAt: new Date().toISOString(),\n });\n } catch (branchErr) {\n const msg = branchErr instanceof Error ? branchErr.message : String(branchErr);\n debug('haiku', 'Branch tracking failed (non-fatal)', { id: obs.id, error: msg });\n }\n }\n\n if (result.signal === 'noise') {\n // Mark as noise and soft-delete\n repo.updateClassification(obs.id, 'noise');\n repo.softDelete(obs.id);\n debug('haiku', 'Observation classified as noise, soft-deleted', { id: obs.id });\n return;\n }\n\n classification = result.classification ?? 'discovery';\n repo.updateClassification(\n obs.id,\n classification as 'discovery' | 'problem' | 'solution',\n );\n debug('haiku', 'Observation classified', {\n id: obs.id,\n classification,\n });\n } catch (classifyErr) {\n const msg = classifyErr instanceof Error ? classifyErr.message : String(classifyErr);\n debug('haiku', 'Classification failed, will retry next cycle', {\n id: obs.id,\n error: msg,\n });\n // Leave unclassified for retry\n return;\n }\n\n // Step 2: Extract entities via Haiku\n let entities: Array<{ name: string; type: EntityType; confidence: number }> = [];\n try {\n entities = await extractEntitiesWithHaiku(obs.content);\n } catch (entityErr) {\n const msg = entityErr instanceof Error ? entityErr.message : String(entityErr);\n debug('haiku', 'Entity extraction failed (non-fatal)', {\n id: obs.id,\n error: msg,\n });\n // Classification succeeded, entity extraction failed -- acceptable\n return;\n }\n\n if (entities.length === 0) return;\n\n // Apply quality gate and persist entities\n const isChange = obs.source === 'hook:Write' || obs.source === 'hook:Edit';\n const gateResult = applyQualityGate(entities, isChange);\n\n const persistedNodes: Array<{ id: string; name: string; type: string }> = [];\n for (const entity of gateResult.passed) {\n try {\n const node = upsertNode(this.db, {\n type: entity.type,\n name: entity.name,\n metadata: { confidence: entity.confidence },\n observation_ids: [String(obs.id)],\n project_hash: projectHash,\n });\n persistedNodes.push(node);\n } catch {\n // Skip individual entity failures\n continue;\n }\n }\n\n if (persistedNodes.length > 0) {\n // Broadcast entity updates to SSE clients\n for (const node of persistedNodes) {\n broadcast('entity_updated', {\n id: node.name,\n label: node.name,\n type: node.type,\n observationCount: 1,\n createdAt: new Date().toISOString(),\n projectHash,\n });\n }\n\n debug('haiku', 'Entities persisted', {\n id: obs.id,\n count: persistedNodes.length,\n });\n }\n\n // Step 3: Infer relationships via Haiku (only if 2+ entities)\n if (persistedNodes.length >= 2) {\n try {\n const entityPairs = persistedNodes.map((n) => ({\n name: n.name,\n type: n.type as EntityType,\n }));\n const relationships = await inferRelationshipsWithHaiku(\n obs.content,\n entityPairs,\n );\n\n const affectedNodeIds = new Set<string>();\n for (const rel of relationships) {\n const sourceNode = getNodeByNameAndType(this.db, rel.source, entityPairs.find((e) => e.name === rel.source)?.type ?? 'File', projectHash);\n const targetNode = getNodeByNameAndType(this.db, rel.target, entityPairs.find((e) => e.name === rel.target)?.type ?? 'File', projectHash);\n\n if (!sourceNode || !targetNode) continue;\n\n try {\n insertEdge(this.db, {\n source_id: sourceNode.id,\n target_id: targetNode.id,\n type: rel.type,\n weight: rel.confidence,\n metadata: { source: 'haiku' },\n project_hash: projectHash,\n });\n affectedNodeIds.add(sourceNode.id);\n affectedNodeIds.add(targetNode.id);\n } catch {\n // Edge may already exist, skip\n }\n }\n\n // Enforce max degree on affected nodes\n for (const nodeId of affectedNodeIds) {\n enforceMaxDegree(this.db, nodeId);\n }\n\n debug('haiku', 'Relationships persisted', {\n id: obs.id,\n count: relationships.length,\n });\n } catch (relErr) {\n const msg = relErr instanceof Error ? relErr.message : String(relErr);\n debug('haiku', 'Relationship inference failed (non-fatal)', {\n id: obs.id,\n error: msg,\n });\n }\n }\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n debug('haiku', 'processOne failed (non-fatal)', {\n id: obs.id,\n error: msg,\n });\n }\n }\n}\n","/**\n * KISS summary agent — generates actionable \"next time, just do X\" summaries.\n *\n * When a debug path resolves, this agent analyzes the waypoints (errors,\n * attempts, failures, resolution) and produces a multi-layer summary:\n * - kiss_summary: The one-liner takeaway\n * - root_cause: What actually caused the issue\n * - what_fixed_it: The specific fix that resolved it\n * - dimensions: logical, programmatic, development perspectives\n *\n * Uses the shared Haiku client (callHaiku + extractJsonFromResponse) following\n * the same pattern as haiku-classifier-agent.ts.\n */\n\nimport { z } from 'zod';\n\nimport { callHaiku, extractJsonFromResponse } from '../intelligence/haiku-client.js';\nimport type { PathWaypoint } from './types.js';\n\n// ---------------------------------------------------------------------------\n// Zod validation schema\n// ---------------------------------------------------------------------------\n\nconst KissSummarySchema = z.object({\n kiss_summary: z.string(),\n root_cause: z.string(),\n what_fixed_it: z.string(),\n dimensions: z.object({\n logical: z.string(),\n programmatic: z.string(),\n development: z.string(),\n }),\n});\n\n// ---------------------------------------------------------------------------\n// Exported types\n// ---------------------------------------------------------------------------\n\nexport type KissSummary = z.infer<typeof KissSummarySchema>;\n\n// ---------------------------------------------------------------------------\n// System prompt\n// ---------------------------------------------------------------------------\n\nconst SYSTEM_PROMPT = `You analyze completed debug resolution paths and produce actionable summaries.\n\nGiven a debug path with its trigger, waypoints (errors, attempts, failures, resolution), and resolution summary, generate:\n\n1. kiss_summary: A \"Next time, just do X\" one-liner. This is the actionable takeaway a developer should remember.\n2. root_cause: What actually caused the issue (1-2 sentences max).\n3. what_fixed_it: The specific fix or change that resolved it (1-2 sentences max).\n4. dimensions:\n - logical: What mental model was wrong? What assumption led the developer astray? (1-2 sentences)\n - programmatic: What code-level change fixed it? Be specific about files, functions, or patterns. (1-2 sentences)\n - development: What workflow improvement would catch this faster next time? (1-2 sentences)\n\nKeep every field concise. Developers will scan these quickly.\nReturn ONLY JSON, no markdown, no explanation.`;\n\n// ---------------------------------------------------------------------------\n// Key waypoint types worth including in the summary prompt\n// ---------------------------------------------------------------------------\n\nconst KEY_WAYPOINT_TYPES = new Set([\n 'error',\n 'failure',\n 'success',\n 'resolution',\n 'discovery',\n]);\n\n// ---------------------------------------------------------------------------\n// Public API\n// ---------------------------------------------------------------------------\n\n/**\n * Generates a KISS summary for a resolved debug path.\n *\n * Pre-filters waypoints to key types (error, failure, success, resolution,\n * discovery) and caps at 10 to keep the prompt small. Returns a structured\n * KissSummary with multi-layer dimensions.\n *\n * @param triggerSummary - What started the debug path\n * @param waypoints - All waypoints from the path\n * @param resolutionSummary - How the path was resolved\n * @returns Structured KISS summary with dimensions\n */\nexport async function generateKissSummary(\n triggerSummary: string,\n waypoints: PathWaypoint[],\n resolutionSummary: string,\n): Promise<KissSummary> {\n // Pre-filter to key waypoint types, skip 'attempt' noise\n const filtered = waypoints\n .filter((w) => KEY_WAYPOINT_TYPES.has(w.waypoint_type))\n .slice(0, 10);\n\n // Format waypoints for the prompt\n const waypointLines = filtered\n .map((w) => `- [${w.waypoint_type}] ${w.summary}`)\n .join('\\n');\n\n const userContent = `Trigger: ${triggerSummary}\n\nWaypoints:\n${waypointLines}\n\nResolution: ${resolutionSummary}`;\n\n const response = await callHaiku(SYSTEM_PROMPT, userContent);\n const parsed = extractJsonFromResponse(response);\n return KissSummarySchema.parse(parsed);\n}\n","/**\n * PathTracker — state machine for automatic debug path detection.\n *\n * Consumes DebugSignal from the Haiku classifier and manages the lifecycle\n * of debug paths: idle -> potential_debug -> active_debug -> resolved.\n *\n * Lives in the MCP server process (not the ephemeral hook handler) so it\n * can maintain in-memory state across observations. Persists paths and\n * waypoints via PathRepository for restart recovery.\n *\n * Implements:\n * PATH-01: Auto-detect debug sessions from error patterns\n * PATH-02: Capture waypoints during active debug paths\n * PATH-03: Detect resolution via consecutive success signals\n * PATH-04: Persistence across restarts (via SQLite recovery)\n * PATH-05: Dead end tracking via failure waypoint type\n */\n\nimport type { DebugSignal } from '../intelligence/haiku-classifier-agent.js';\nimport type { PathRepository } from './path-repository.js';\nimport type { WaypointType } from './types.js';\nimport { debug } from '../shared/debug.js';\nimport { generateKissSummary } from './kiss-summary-agent.js';\n\n// ---------------------------------------------------------------------------\n// Types\n// ---------------------------------------------------------------------------\n\ntype TrackerState = 'idle' | 'potential_debug' | 'active_debug' | 'resolved';\n\ninterface ErrorBufferEntry {\n timestamp: number;\n summary: string;\n}\n\ninterface PathTrackerOptions {\n /** Number of errors needed to confirm debug session (default: 3) */\n errorThreshold?: number;\n /** Time window for error threshold in ms (default: 5 minutes) */\n windowMs?: number;\n /** Consecutive successes needed to auto-resolve (default: 3) */\n resolutionThreshold?: number;\n /** Maximum waypoints per path (default: 30) */\n maxWaypoints?: number;\n}\n\n// ---------------------------------------------------------------------------\n// PathTracker\n// ---------------------------------------------------------------------------\n\nexport class PathTracker {\n private state: TrackerState = 'idle';\n private errorBuffer: ErrorBufferEntry[] = [];\n private consecutiveSuccesses: number = 0;\n private currentPathId: string | null = null;\n\n private readonly errorThreshold: number;\n private readonly windowMs: number;\n private readonly resolutionThreshold: number;\n private readonly maxWaypoints: number;\n\n constructor(\n private readonly repo: PathRepository,\n opts?: PathTrackerOptions,\n ) {\n this.errorThreshold = opts?.errorThreshold ?? 3;\n this.windowMs = opts?.windowMs ?? 5 * 60 * 1000;\n this.resolutionThreshold = opts?.resolutionThreshold ?? 3;\n this.maxWaypoints = opts?.maxWaypoints ?? 30;\n\n // PATH-04: Recover active path from SQLite on server restart\n const activePath = this.repo.getActivePath();\n if (activePath) {\n this.state = 'active_debug';\n this.currentPathId = activePath.id;\n debug('paths', 'Recovered active path from SQLite', { pathId: activePath.id });\n }\n }\n\n /**\n * Process a debug signal from the Haiku classifier.\n *\n * Called for every classified observation (both noise and signal).\n * Drives state transitions and persists waypoints when in active_debug.\n */\n processSignal(\n signal: DebugSignal,\n observationId: string,\n observationContent: string,\n ): void {\n // Filter: skip low-confidence signals entirely\n if (signal.confidence < 0.3) {\n return;\n }\n\n const summary = observationContent.substring(0, 200).trim();\n\n switch (this.state) {\n case 'idle':\n this.handleIdle(signal, summary);\n break;\n\n case 'potential_debug':\n this.handlePotentialDebug(signal, summary, observationId);\n break;\n\n case 'active_debug':\n this.handleActiveDebug(signal, summary, observationId);\n break;\n\n case 'resolved':\n // Resolved is transient — immediately return to idle\n this.state = 'idle';\n debug('paths', 'Transitioned resolved -> idle');\n this.handleIdle(signal, summary);\n break;\n }\n }\n\n // -------------------------------------------------------------------------\n // State handlers\n // -------------------------------------------------------------------------\n\n private handleIdle(signal: DebugSignal, summary: string): void {\n if (signal.is_error && signal.confidence >= 0.5) {\n this.errorBuffer.push({ timestamp: Date.now(), summary });\n this.state = 'potential_debug';\n debug('paths', 'Transitioned idle -> potential_debug', {\n bufferSize: this.errorBuffer.length,\n });\n }\n }\n\n private handlePotentialDebug(\n signal: DebugSignal,\n summary: string,\n observationId: string,\n ): void {\n if (signal.is_error && signal.confidence >= 0.5) {\n this.errorBuffer.push({ timestamp: Date.now(), summary });\n }\n\n // Prune entries older than windowMs\n const cutoff = Date.now() - this.windowMs;\n this.errorBuffer = this.errorBuffer.filter((e) => e.timestamp >= cutoff);\n\n // All expired — back to idle\n if (this.errorBuffer.length === 0) {\n this.state = 'idle';\n debug('paths', 'Error buffer expired, potential_debug -> idle');\n return;\n }\n\n // Threshold met — transition to active_debug\n if (this.errorBuffer.length >= this.errorThreshold) {\n const triggerSummary = this.errorBuffer[0].summary;\n const path = this.repo.createPath(triggerSummary);\n this.currentPathId = path.id;\n this.state = 'active_debug';\n this.consecutiveSuccesses = 0;\n\n debug('paths', 'Debug path confirmed, potential_debug -> active_debug', {\n pathId: path.id,\n errorCount: this.errorBuffer.length,\n });\n\n // Add waypoints for all buffered errors\n for (const entry of this.errorBuffer) {\n this.repo.addWaypoint(path.id, 'error', entry.summary, observationId);\n }\n\n // Clear buffer — errors are now waypoints\n this.errorBuffer = [];\n }\n }\n\n private handleActiveDebug(\n signal: DebugSignal,\n summary: string,\n observationId: string,\n ): void {\n if (!this.currentPathId) return;\n\n // Cap enforcement\n if (this.repo.countWaypoints(this.currentPathId) >= this.maxWaypoints) {\n debug('paths', 'Waypoint cap reached, skipping', {\n pathId: this.currentPathId,\n cap: this.maxWaypoints,\n });\n // Still process resolution detection even if we can't add waypoints\n this.updateResolutionCounter(signal, summary, observationId);\n return;\n }\n\n // Determine waypoint type\n let waypointType: WaypointType;\n if (signal.waypoint_hint) {\n waypointType = signal.waypoint_hint;\n } else if (signal.is_error) {\n waypointType = 'error';\n } else if (signal.is_resolution) {\n waypointType = 'success';\n } else {\n waypointType = 'attempt';\n }\n\n // Add waypoint\n this.repo.addWaypoint(this.currentPathId, waypointType, summary, observationId);\n\n debug('paths', 'Waypoint added', {\n pathId: this.currentPathId,\n type: waypointType,\n observationId,\n });\n\n // Resolution detection\n this.updateResolutionCounter(signal, summary, observationId);\n }\n\n private updateResolutionCounter(\n signal: DebugSignal,\n summary: string,\n observationId: string,\n ): void {\n if (!this.currentPathId) return;\n\n if (signal.is_resolution) {\n this.consecutiveSuccesses++;\n\n if (this.consecutiveSuccesses >= this.resolutionThreshold) {\n // Add final resolution waypoint (if under cap)\n if (this.repo.countWaypoints(this.currentPathId) < this.maxWaypoints) {\n this.repo.addWaypoint(this.currentPathId, 'resolution', summary, observationId);\n }\n\n // Resolve the path\n this.repo.resolvePath(this.currentPathId, summary);\n\n debug('paths', 'Path auto-resolved', {\n pathId: this.currentPathId,\n consecutiveSuccesses: this.consecutiveSuccesses,\n });\n\n // Fire-and-forget KISS generation (non-blocking)\n const savedPathId = this.currentPathId;\n const savedResolutionSummary = summary;\n this.generateAndStoreKiss(savedPathId, savedResolutionSummary).catch((err) => {\n debug('paths', 'KISS generation failed (fire-and-forget)', { error: String(err) });\n });\n\n // Reset state\n this.state = 'idle';\n this.currentPathId = null;\n this.consecutiveSuccesses = 0;\n this.errorBuffer = [];\n }\n } else if (signal.is_error) {\n // Error resets the consecutive success counter\n this.consecutiveSuccesses = 0;\n }\n }\n\n // -------------------------------------------------------------------------\n // KISS summary generation\n // -------------------------------------------------------------------------\n\n /**\n * Generates and stores a KISS summary for a resolved path.\n * Non-fatal — failures are logged but do not affect path resolution.\n */\n private async generateAndStoreKiss(\n pathId: string,\n resolutionSummary: string,\n ): Promise<void> {\n try {\n const path = this.repo.getPath(pathId);\n if (!path) return;\n\n const waypoints = this.repo.getWaypoints(pathId);\n const kiss = await generateKissSummary(\n path.trigger_summary,\n waypoints,\n resolutionSummary,\n );\n\n this.repo.updateKissSummary(pathId, JSON.stringify(kiss));\n debug('paths', 'KISS summary stored', { pathId });\n } catch (err) {\n debug('paths', 'KISS generation failed', {\n pathId,\n error: String(err),\n });\n }\n }\n\n // -------------------------------------------------------------------------\n // Manual control (for MCP tools — Plan 02)\n // -------------------------------------------------------------------------\n\n /**\n * Manually starts a debug path. Used by MCP tools.\n * If already tracking, returns the existing path ID.\n */\n startManually(triggerSummary: string): string | null {\n if (this.state === 'active_debug' && this.currentPathId) {\n return this.currentPathId;\n }\n\n const path = this.repo.createPath(triggerSummary);\n this.state = 'active_debug';\n this.currentPathId = path.id;\n this.consecutiveSuccesses = 0;\n this.errorBuffer = [];\n\n debug('paths', 'Path started manually', { pathId: path.id });\n return path.id;\n }\n\n /**\n * Manually resolves the active debug path. Used by MCP tools.\n * Adds a resolution waypoint, resolves the path, and fires KISS generation.\n */\n resolveManually(resolutionSummary: string): void {\n if (!this.currentPathId || this.state !== 'active_debug') return;\n\n // Add resolution waypoint\n this.repo.addWaypoint(this.currentPathId, 'resolution', resolutionSummary);\n\n // Resolve the path\n this.repo.resolvePath(this.currentPathId, resolutionSummary);\n\n // Fire-and-forget KISS generation\n const savedPathId = this.currentPathId;\n this.generateAndStoreKiss(savedPathId, resolutionSummary).catch((err) => {\n debug('paths', 'KISS generation failed (fire-and-forget)', { error: String(err) });\n });\n\n // Reset state\n this.state = 'idle';\n this.currentPathId = null;\n this.consecutiveSuccesses = 0;\n this.errorBuffer = [];\n\n debug('paths', 'Path resolved manually', { pathId: savedPathId });\n }\n\n /**\n * Returns the active path ID, or null if no path is being tracked.\n */\n getActivePathId(): string | null {\n return this.currentPathId;\n }\n}\n","/**\n * REST API routes for the Laminark visualization.\n *\n * Provides endpoints for graph data, timeline data, and individual node\n * details. All endpoints read from the better-sqlite3 database instance\n * set on the Hono context by the server middleware.\n *\n * @module web/routes/api\n */\n\nimport { Hono } from 'hono';\nimport type BetterSqlite3 from 'better-sqlite3';\nimport { PathRepository } from '../../paths/path-repository.js';\nimport type { PathWaypoint } from '../../paths/types.js';\n\ntype AppEnv = {\n Variables: {\n db: BetterSqlite3.Database;\n defaultProject: string;\n };\n};\n\n// ---------------------------------------------------------------------------\n// Raw row interfaces for SQL results\n// ---------------------------------------------------------------------------\n\ninterface GraphNodeRow {\n id: string;\n name: string;\n type: string;\n observation_ids: string; // JSON array\n created_at: string;\n}\n\ninterface GraphEdgeRow {\n id: string;\n source_id: string;\n target_id: string;\n type: string;\n name?: string; // joined from graph_nodes for label\n weight: number;\n}\n\ninterface SessionRow {\n id: string;\n started_at: string;\n ended_at: string | null;\n summary: string | null;\n}\n\ninterface ObservationRow {\n id: string;\n content: string;\n title: string | null;\n source: string;\n created_at: string;\n session_id: string | null;\n}\n\ninterface ShiftDecisionRow {\n id: string;\n session_id: string;\n distance: number;\n threshold: number;\n confidence: number | null;\n created_at: string;\n}\n\n// ---------------------------------------------------------------------------\n// Helper: get db from Hono context\n// ---------------------------------------------------------------------------\n\nfunction getDb(c: { get: (key: 'db') => BetterSqlite3.Database }): BetterSqlite3.Database {\n return c.get('db');\n}\n\nfunction getProjectHash(c: { get: (key: 'defaultProject') => string; req: { query: (key: string) => string | undefined } }): string | null {\n return c.req.query('project') || c.get('defaultProject') || null;\n}\n\n// ---------------------------------------------------------------------------\n// Route group\n// ---------------------------------------------------------------------------\n\nexport const apiRoutes = new Hono<AppEnv>();\n\n/**\n * GET /api/projects\n *\n * Returns list of known projects from project_metadata table.\n */\napiRoutes.get('/projects', (c) => {\n const db = getDb(c);\n const defaultProject = c.get('defaultProject') || null;\n\n interface ProjectRow {\n project_hash: string;\n project_path: string;\n display_name: string | null;\n last_seen_at: string;\n }\n\n let projects: ProjectRow[] = [];\n try {\n projects = db.prepare(\n 'SELECT project_hash, project_path, display_name, last_seen_at FROM project_metadata ORDER BY last_seen_at DESC'\n ).all() as ProjectRow[];\n } catch { /* table may not exist yet */ }\n\n // Prefer the most recently active project as default (first in list, sorted by last_seen_at DESC)\n const resolvedDefault = (projects.length > 0 ? projects[0].project_hash : null) || defaultProject;\n\n return c.json({\n projects: projects.map(p => ({\n hash: p.project_hash,\n path: p.project_path,\n displayName: p.display_name || p.project_path.split('/').pop() || p.project_hash.substring(0, 8),\n lastSeenAt: p.last_seen_at,\n })),\n defaultProject: resolvedDefault,\n });\n});\n\n/**\n * GET /api/graph\n *\n * Returns the knowledge graph as JSON with nodes and edges arrays.\n * Accepts optional query params:\n * ?type=File,Decision - comma-separated entity types to include\n * ?since=ISO8601 - only entities created after this timestamp\n */\napiRoutes.get('/graph', (c) => {\n const db = getDb(c);\n const typeFilter = c.req.query('type');\n const sinceFilter = c.req.query('since');\n const untilFilter = c.req.query('until');\n const projectFilter = getProjectHash(c);\n\n // Build nodes query\n let nodesSql = 'SELECT id, name, type, observation_ids, created_at FROM graph_nodes';\n const nodeParams: unknown[] = [];\n const nodeConditions: string[] = [];\n\n if (projectFilter) {\n nodeConditions.push('project_hash = ?');\n nodeParams.push(projectFilter);\n }\n\n if (typeFilter) {\n const types = typeFilter.split(',').map(t => t.trim()).filter(Boolean);\n if (types.length > 0) {\n nodeConditions.push(`type IN (${types.map(() => '?').join(', ')})`);\n nodeParams.push(...types);\n }\n }\n\n if (sinceFilter) {\n nodeConditions.push('created_at >= ?');\n nodeParams.push(sinceFilter);\n }\n\n if (untilFilter) {\n nodeConditions.push('created_at <= ?');\n nodeParams.push(untilFilter);\n }\n\n if (nodeConditions.length > 0) {\n nodesSql += ' WHERE ' + nodeConditions.join(' AND ');\n }\n\n nodesSql += ' ORDER BY created_at DESC';\n\n let nodeRows: GraphNodeRow[];\n try {\n nodeRows = db.prepare(nodesSql).all(...nodeParams) as GraphNodeRow[];\n } catch {\n nodeRows = [];\n }\n\n const nodes = nodeRows.map(row => ({\n id: row.id,\n label: row.name,\n type: row.type,\n observationCount: safeParseJsonArray(row.observation_ids).length,\n createdAt: row.created_at,\n }));\n\n // Build edges query -- only include edges where both nodes are in the result set\n let edgeRows: GraphEdgeRow[];\n try {\n let edgesSql = `\n SELECT e.id, e.source_id, e.target_id, e.type, e.weight,\n tn.name AS name\n FROM graph_edges e\n LEFT JOIN graph_nodes tn ON tn.id = e.target_id`;\n const edgeParams: unknown[] = [];\n if (projectFilter) {\n edgesSql += ' WHERE e.project_hash = ?';\n edgeParams.push(projectFilter);\n }\n edgesSql += ' ORDER BY e.created_at DESC';\n edgeRows = db.prepare(edgesSql).all(...edgeParams) as GraphEdgeRow[];\n } catch {\n edgeRows = [];\n }\n\n // Only include edges where both endpoints exist in the node set\n const nodeIdSet = new Set(nodes.map(n => n.id));\n const filteredEdges = edgeRows.filter(e => nodeIdSet.has(e.source_id) && nodeIdSet.has(e.target_id));\n\n const edges = filteredEdges.map(row => ({\n id: row.id,\n source: row.source_id,\n target: row.target_id,\n type: row.type,\n label: row.name ?? row.type,\n }));\n\n return c.json({ nodes, edges });\n});\n\n/**\n * GET /api/timeline\n *\n * Returns timeline data: sessions, observations, and topic shifts.\n * Accepts optional query params:\n * ?from=ISO8601 - start of time range\n * ?to=ISO8601 - end of time range\n * ?limit=N - max observations (default 500)\n */\napiRoutes.get('/timeline', (c) => {\n const db = getDb(c);\n const from = c.req.query('from');\n const to = c.req.query('to');\n const limitStr = c.req.query('limit');\n const limit = limitStr ? Math.min(parseInt(limitStr, 10) || 500, 2000) : 500;\n const offsetStr = c.req.query('offset');\n const offset = offsetStr ? Math.max(parseInt(offsetStr, 10) || 0, 0) : 0;\n const projectFilter = getProjectHash(c);\n\n // Sessions\n let sessions: Array<{ id: string; startedAt: string; endedAt: string | null; observationCount: number; summary: string | null }> = [];\n try {\n let sessionsSql = 'SELECT id, started_at, ended_at, summary FROM sessions';\n const sessionParams: unknown[] = [];\n const sessionConds: string[] = [];\n\n if (projectFilter) {\n sessionConds.push('project_hash = ?');\n sessionParams.push(projectFilter);\n }\n\n if (from) {\n sessionConds.push('started_at >= ?');\n sessionParams.push(from);\n }\n if (to) {\n sessionConds.push('(ended_at IS NULL OR ended_at <= ?)');\n sessionParams.push(to);\n }\n\n if (sessionConds.length > 0) {\n sessionsSql += ' WHERE ' + sessionConds.join(' AND ');\n }\n sessionsSql += ' ORDER BY started_at DESC LIMIT 50 OFFSET ?';\n sessionParams.push(offset);\n\n const sessionRows = db.prepare(sessionsSql).all(...sessionParams) as SessionRow[];\n\n // Count observations per session\n const countStmt = db.prepare(\n 'SELECT COUNT(*) AS cnt FROM observations WHERE session_id = ? AND deleted_at IS NULL'\n );\n\n sessions = sessionRows.map(row => {\n let obsCount = 0;\n try {\n const countRow = countStmt.get(row.id) as { cnt: number } | undefined;\n obsCount = countRow?.cnt ?? 0;\n } catch { /* empty */ }\n\n return {\n id: row.id,\n startedAt: row.started_at,\n endedAt: row.ended_at,\n observationCount: obsCount,\n summary: row.summary,\n };\n });\n } catch { /* tables may not exist yet */ }\n\n // Observations\n let observations: Array<{ id: string; text: string; createdAt: string; sessionId: string | null; type: string }> = [];\n try {\n let obsSql = 'SELECT id, content, title, source, created_at, session_id FROM observations WHERE deleted_at IS NULL';\n const obsParams: unknown[] = [];\n\n if (projectFilter) {\n obsSql += ' AND project_hash = ?';\n obsParams.push(projectFilter);\n }\n\n if (from) {\n obsSql += ' AND created_at >= ?';\n obsParams.push(from);\n }\n if (to) {\n obsSql += ' AND created_at <= ?';\n obsParams.push(to);\n }\n\n obsSql += ' ORDER BY created_at DESC LIMIT ? OFFSET ?';\n obsParams.push(limit);\n obsParams.push(offset);\n\n const obsRows = db.prepare(obsSql).all(...obsParams) as ObservationRow[];\n\n observations = obsRows.map(row => ({\n id: row.id,\n text: row.title ? `${row.title}: ${row.content}` : row.content,\n createdAt: row.created_at,\n sessionId: row.session_id,\n type: row.source,\n }));\n } catch { /* table may not exist yet */ }\n\n // Topic shifts\n let topicShifts: Array<{ id: string; fromTopic: string | null; toTopic: string | null; timestamp: string; confidence: number | null }> = [];\n try {\n let shiftSql = 'SELECT id, session_id, distance, threshold, confidence, created_at FROM shift_decisions WHERE shifted = 1';\n const shiftParams: unknown[] = [];\n\n if (projectFilter) {\n shiftSql += ' AND project_id = ?';\n shiftParams.push(projectFilter);\n }\n\n if (from) {\n shiftSql += ' AND created_at >= ?';\n shiftParams.push(from);\n }\n if (to) {\n shiftSql += ' AND created_at <= ?';\n shiftParams.push(to);\n }\n\n shiftSql += ' ORDER BY created_at DESC LIMIT 100';\n\n const shiftRows = db.prepare(shiftSql).all(...shiftParams) as ShiftDecisionRow[];\n\n topicShifts = shiftRows.map(row => ({\n id: row.id,\n fromTopic: null, // shift_decisions doesn't store topic labels directly\n toTopic: null,\n timestamp: row.created_at,\n confidence: row.confidence,\n }));\n } catch { /* table may not exist yet */ }\n\n return c.json({ sessions, observations, topicShifts });\n});\n\n/**\n * GET /api/node/:id\n *\n * Returns details for a single entity node including its observations\n * and relationships. Powers the detail panel.\n */\napiRoutes.get('/node/:id', (c) => {\n const db = getDb(c);\n const nodeId = c.req.param('id');\n\n // Get the entity node\n interface FullNodeRow {\n id: string;\n name: string;\n type: string;\n observation_ids: string;\n metadata: string;\n created_at: string;\n updated_at: string;\n }\n\n let nodeRow: FullNodeRow | undefined;\n try {\n nodeRow = db.prepare(\n 'SELECT id, name, type, observation_ids, metadata, created_at, updated_at FROM graph_nodes WHERE id = ?'\n ).get(nodeId) as FullNodeRow | undefined;\n } catch { /* table may not exist */ }\n\n if (!nodeRow) {\n return c.json({ error: 'Node not found' }, 404);\n }\n\n const entity = {\n id: nodeRow.id,\n label: nodeRow.name,\n type: nodeRow.type,\n createdAt: nodeRow.created_at,\n updatedAt: nodeRow.updated_at,\n metadata: safeParseJson(nodeRow.metadata),\n };\n\n // Get observations for this entity\n const observationIds = safeParseJsonArray(nodeRow.observation_ids);\n let nodeObservations: Array<{ id: string; text: string; createdAt: string }> = [];\n\n if (observationIds.length > 0) {\n try {\n const placeholders = observationIds.map(() => '?').join(', ');\n const obsRows = db.prepare(\n `SELECT id, content, title, created_at FROM observations WHERE id IN (${placeholders}) AND deleted_at IS NULL ORDER BY created_at DESC`\n ).all(...observationIds) as Array<{ id: string; content: string; title: string | null; created_at: string }>;\n\n nodeObservations = obsRows.map(row => ({\n id: row.id,\n text: row.title ? `${row.title}: ${row.content}` : row.content,\n createdAt: row.created_at,\n }));\n } catch { /* table may not exist */ }\n }\n\n // Get relationships\n interface RelRow {\n id: string;\n source_id: string;\n target_id: string;\n type: string;\n weight: number;\n target_name: string | null;\n target_type: string | null;\n source_name: string | null;\n source_type: string | null;\n }\n\n let relationships: Array<{ id: string; targetId: string; targetLabel: string; type: string; direction: string }> = [];\n try {\n const relRows = db.prepare(`\n SELECT\n e.id, e.source_id, e.target_id, e.type, e.weight,\n tn.name AS target_name, tn.type AS target_type,\n sn.name AS source_name, sn.type AS source_type\n FROM graph_edges e\n LEFT JOIN graph_nodes tn ON tn.id = e.target_id\n LEFT JOIN graph_nodes sn ON sn.id = e.source_id\n WHERE e.source_id = ? OR e.target_id = ?\n ORDER BY e.weight DESC\n `).all(nodeId, nodeId) as RelRow[];\n\n relationships = relRows.map(row => {\n const isSource = row.source_id === nodeId;\n return {\n id: row.id,\n targetId: isSource ? row.target_id : row.source_id,\n targetLabel: isSource ? (row.target_name ?? row.target_id) : (row.source_name ?? row.source_id),\n type: row.type,\n direction: isSource ? 'outgoing' : 'incoming',\n };\n });\n } catch { /* table may not exist */ }\n\n return c.json({ entity, observations: nodeObservations, relationships });\n});\n\n/**\n * GET /api/node/:id/neighborhood\n *\n * Returns the N-hop subgraph around a node. Powers the focus/drill-down view.\n * Query params:\n * ?depth=1 - hop count (1 or 2, default 1)\n */\napiRoutes.get('/node/:id/neighborhood', (c) => {\n const db = getDb(c);\n const centerId = c.req.param('id');\n const depthParam = c.req.query('depth');\n const depth = Math.min(Math.max(parseInt(depthParam || '1', 10) || 1, 1), 2);\n\n // Verify the center node exists\n let centerRow: GraphNodeRow | undefined;\n try {\n centerRow = db.prepare(\n 'SELECT id, name, type, observation_ids, created_at FROM graph_nodes WHERE id = ?'\n ).get(centerId) as GraphNodeRow | undefined;\n } catch { /* table may not exist */ }\n\n if (!centerRow) {\n return c.json({ error: 'Node not found' }, 404);\n }\n\n // Collect node IDs at each depth level\n const visitedNodeIds = new Set<string>([centerId]);\n let frontier = new Set<string>([centerId]);\n\n interface EdgeRow {\n id: string;\n source_id: string;\n target_id: string;\n type: string;\n weight: number;\n }\n\n const allEdgeRows: EdgeRow[] = [];\n const seenEdgeIds = new Set<string>();\n\n for (let d = 0; d < depth; d++) {\n if (frontier.size === 0) break;\n\n const frontierIds = Array.from(frontier);\n const placeholders = frontierIds.map(() => '?').join(', ');\n const nextFrontier = new Set<string>();\n\n try {\n const edgeRows = db.prepare(\n `SELECT id, source_id, target_id, type, weight FROM graph_edges\n WHERE source_id IN (${placeholders}) OR target_id IN (${placeholders})`\n ).all(...frontierIds, ...frontierIds) as EdgeRow[];\n\n for (const edge of edgeRows) {\n if (!seenEdgeIds.has(edge.id)) {\n seenEdgeIds.add(edge.id);\n allEdgeRows.push(edge);\n }\n\n if (!visitedNodeIds.has(edge.source_id)) {\n visitedNodeIds.add(edge.source_id);\n nextFrontier.add(edge.source_id);\n }\n if (!visitedNodeIds.has(edge.target_id)) {\n visitedNodeIds.add(edge.target_id);\n nextFrontier.add(edge.target_id);\n }\n }\n } catch { /* table may not exist */ }\n\n frontier = nextFrontier;\n }\n\n // Fetch full node data for all collected node IDs\n const nodeIds = Array.from(visitedNodeIds);\n let nodeRows: GraphNodeRow[] = [];\n if (nodeIds.length > 0) {\n try {\n const placeholders = nodeIds.map(() => '?').join(', ');\n nodeRows = db.prepare(\n `SELECT id, name, type, observation_ids, created_at FROM graph_nodes WHERE id IN (${placeholders})`\n ).all(...nodeIds) as GraphNodeRow[];\n } catch { /* table may not exist */ }\n }\n\n const nodes = nodeRows.map(row => ({\n id: row.id,\n label: row.name,\n type: row.type,\n observationCount: safeParseJsonArray(row.observation_ids).length,\n createdAt: row.created_at,\n }));\n\n // Only include edges where both endpoints are in our node set\n const nodeIdSet = new Set(nodeIds);\n const edges = allEdgeRows\n .filter(e => nodeIdSet.has(e.source_id) && nodeIdSet.has(e.target_id))\n .map(row => ({\n id: row.id,\n source: row.source_id,\n target: row.target_id,\n type: row.type,\n }));\n\n return c.json({ center: centerId, nodes, edges });\n});\n\n/**\n * GET /api/graph/search\n *\n * Two-tier search: name-based LIKE matching on graph_nodes, then FTS fallback\n * on observations_fts for richer content matching.\n * Query params:\n * ?q= - search query (required)\n * ?type= - entity type filter\n * ?limit=20 - max results\n * ?project= - project hash filter\n */\napiRoutes.get('/graph/search', (c) => {\n const db = getDb(c);\n const query = (c.req.query('q') || '').trim();\n const typeFilter = c.req.query('type') || null;\n const limitStr = c.req.query('limit');\n const limit = limitStr ? Math.min(parseInt(limitStr, 10) || 20, 50) : 20;\n const projectFilter = getProjectHash(c);\n\n if (!query) {\n return c.json({ results: [] });\n }\n\n interface SearchResult {\n id: string;\n label: string;\n type: string;\n observationCount: number;\n matchSource: 'exact' | 'prefix' | 'contains' | 'fts';\n snippet: string | null;\n }\n\n const results: SearchResult[] = [];\n const seenIds = new Set<string>();\n\n // Pass 1: Name-based matching ranked by exact > prefix > contains\n try {\n let nameSql = `SELECT id, name, type, observation_ids FROM graph_nodes WHERE name LIKE ? COLLATE NOCASE`;\n const nameParams: unknown[] = [`%${query}%`];\n\n if (projectFilter) {\n nameSql += ' AND project_hash = ?';\n nameParams.push(projectFilter);\n }\n if (typeFilter) {\n nameSql += ' AND type = ?';\n nameParams.push(typeFilter);\n }\n\n nameSql += ' LIMIT 100';\n\n const rows = db.prepare(nameSql).all(...nameParams) as GraphNodeRow[];\n\n // Rank results: exact > prefix > contains\n const lowerQuery = query.toLowerCase();\n const ranked = rows.map(row => {\n const lowerName = row.name.toLowerCase();\n let rank: 'exact' | 'prefix' | 'contains';\n if (lowerName === lowerQuery) {\n rank = 'exact';\n } else if (lowerName.startsWith(lowerQuery)) {\n rank = 'prefix';\n } else {\n rank = 'contains';\n }\n return { row, rank };\n });\n\n const rankOrder = { exact: 0, prefix: 1, contains: 2 };\n ranked.sort((a, b) => rankOrder[a.rank] - rankOrder[b.rank]);\n\n for (const { row, rank } of ranked) {\n if (results.length >= limit) break;\n seenIds.add(row.id);\n results.push({\n id: row.id,\n label: row.name,\n type: row.type,\n observationCount: safeParseJsonArray(row.observation_ids).length,\n matchSource: rank,\n snippet: null,\n });\n }\n } catch { /* graph_nodes may not exist */ }\n\n // Pass 2: FTS fallback if name matching returned sparse results\n if (results.length < limit) {\n try {\n // Check if observations_fts table exists\n const ftsCheck = db.prepare(\n \"SELECT name FROM sqlite_master WHERE type='table' AND name='observations_fts'\"\n ).get();\n\n if (ftsCheck) {\n let ftsSql = `\n SELECT o.id AS obs_id, o.content, o.title,\n gn.id AS node_id, gn.name, gn.type, gn.observation_ids\n FROM observations_fts fts\n JOIN observations o ON o.id = fts.rowid\n JOIN graph_nodes gn ON EXISTS (\n SELECT 1 FROM json_each(gn.observation_ids) je WHERE je.value = o.id\n )\n WHERE observations_fts MATCH ?\n AND o.deleted_at IS NULL`;\n const ftsParams: unknown[] = [query + '*'];\n\n if (projectFilter) {\n ftsSql += ' AND gn.project_hash = ?';\n ftsParams.push(projectFilter);\n }\n if (typeFilter) {\n ftsSql += ' AND gn.type = ?';\n ftsParams.push(typeFilter);\n }\n\n ftsSql += ' LIMIT 50';\n\n interface FtsRow {\n obs_id: string;\n content: string;\n title: string | null;\n node_id: string;\n name: string;\n type: string;\n observation_ids: string;\n }\n\n const ftsRows = db.prepare(ftsSql).all(...ftsParams) as FtsRow[];\n\n for (const row of ftsRows) {\n if (results.length >= limit) break;\n if (seenIds.has(row.node_id)) continue;\n seenIds.add(row.node_id);\n\n // Build snippet from matching observation content\n const text = row.title ? `${row.title}: ${row.content}` : row.content;\n const snippet = text.length > 120 ? text.substring(0, 120) + '...' : text;\n\n results.push({\n id: row.node_id,\n label: row.name,\n type: row.type,\n observationCount: safeParseJsonArray(row.observation_ids).length,\n matchSource: 'fts',\n snippet,\n });\n }\n }\n } catch { /* FTS table may not exist */ }\n }\n\n return c.json({ results });\n});\n\n/**\n * GET /api/graph/analysis\n *\n * Returns graph analysis insights: type distributions, top entities by degree,\n * connected components, and recent activity stats.\n * 30-second in-memory cache to avoid recomputation.\n */\n\nlet analysisCache: { key: string; data: unknown; expiry: number } | null = null;\n\napiRoutes.get('/graph/analysis', (c) => {\n const db = getDb(c);\n const projectFilter = getProjectHash(c);\n const cacheKey = `analysis:${projectFilter || 'all'}`;\n const now = Date.now();\n\n // Check cache\n if (analysisCache && analysisCache.key === cacheKey && analysisCache.expiry > now) {\n return c.json(analysisCache.data as Record<string, unknown>);\n }\n\n // Entity type distribution\n let entityTypes: Array<{ type: string; count: number }> = [];\n try {\n let sql = 'SELECT type, COUNT(*) as count FROM graph_nodes';\n const params: unknown[] = [];\n if (projectFilter) {\n sql += ' WHERE project_hash = ?';\n params.push(projectFilter);\n }\n sql += ' GROUP BY type ORDER BY count DESC';\n entityTypes = db.prepare(sql).all(...params) as Array<{ type: string; count: number }>;\n } catch { /* table may not exist */ }\n\n // Relationship type distribution\n let relationshipTypes: Array<{ type: string; count: number }> = [];\n try {\n let sql = 'SELECT type, COUNT(*) as count FROM graph_edges';\n const params: unknown[] = [];\n if (projectFilter) {\n sql += ' WHERE project_hash = ?';\n params.push(projectFilter);\n }\n sql += ' GROUP BY type ORDER BY count DESC';\n relationshipTypes = db.prepare(sql).all(...params) as Array<{ type: string; count: number }>;\n } catch { /* table may not exist */ }\n\n // Top 10 entities by degree (most connected)\n let topEntities: Array<{ id: string; label: string; type: string; degree: number }> = [];\n try {\n let sql = `\n SELECT gn.id, gn.name AS label, gn.type,\n (SELECT COUNT(*) FROM graph_edges e WHERE e.source_id = gn.id${projectFilter ? ' AND e.project_hash = ?' : ''})\n + (SELECT COUNT(*) FROM graph_edges e WHERE e.target_id = gn.id${projectFilter ? ' AND e.project_hash = ?' : ''})\n AS degree\n FROM graph_nodes gn`;\n const params: unknown[] = [];\n if (projectFilter) {\n sql += ' WHERE gn.project_hash = ?';\n params.push(projectFilter);\n // Two extra params for the subqueries\n params.unshift(projectFilter, projectFilter);\n }\n sql += ' ORDER BY degree DESC LIMIT 10';\n topEntities = db.prepare(sql).all(...params) as Array<{ id: string; label: string; type: string; degree: number }>;\n } catch { /* table may not exist */ }\n\n // Connected components via shared BFS helper\n let components: Array<{ id: number; label: string; nodeIds: string[]; nodeCount: number; edgeCount: number }> = [];\n try {\n const bfs = findConnectedComponents(db, projectFilter);\n components = bfs.components.map((comp, i) => ({\n id: i,\n label: comp.label,\n nodeIds: comp.nodeIds,\n nodeCount: comp.nodeIds.length,\n edgeCount: comp.edgeCount,\n }));\n } catch { /* tables may not exist */ }\n\n // Recent activity stats\n let recentActivity = { lastDay: 0, lastWeek: 0 };\n try {\n const dayAgo = new Date(Date.now() - 24 * 60 * 60 * 1000).toISOString();\n const weekAgo = new Date(Date.now() - 7 * 24 * 60 * 60 * 1000).toISOString();\n\n let daySql = 'SELECT COUNT(*) as count FROM graph_nodes WHERE created_at >= ?';\n let weekSql = 'SELECT COUNT(*) as count FROM graph_nodes WHERE created_at >= ?';\n const dayParams: unknown[] = [dayAgo];\n const weekParams: unknown[] = [weekAgo];\n\n if (projectFilter) {\n daySql += ' AND project_hash = ?';\n weekSql += ' AND project_hash = ?';\n dayParams.push(projectFilter);\n weekParams.push(projectFilter);\n }\n\n const dayRow = db.prepare(daySql).get(...dayParams) as { count: number } | undefined;\n const weekRow = db.prepare(weekSql).get(...weekParams) as { count: number } | undefined;\n recentActivity = {\n lastDay: dayRow?.count ?? 0,\n lastWeek: weekRow?.count ?? 0,\n };\n } catch { /* table may not exist */ }\n\n const result = {\n entityTypes,\n relationshipTypes,\n topEntities,\n components,\n recentActivity,\n };\n\n // Cache for 30 seconds\n analysisCache = { key: cacheKey, data: result, expiry: now + 30_000 };\n\n return c.json(result);\n});\n\n/**\n * GET /api/graph/communities\n *\n * Returns community assignments with colors from a 10-color palette.\n * Builds on the same BFS component detection as analysis.\n */\napiRoutes.get('/graph/communities', (c) => {\n const db = getDb(c);\n const projectFilter = getProjectHash(c);\n\n const COMMUNITY_COLORS = [\n '#58a6ff', '#3fb950', '#d2a8ff', '#f0883e', '#f85149',\n '#79c0ff', '#d29922', '#7ee787', '#f778ba', '#a5d6ff',\n ];\n\n interface Community {\n id: number;\n label: string;\n color: string;\n nodeIds: string[];\n }\n\n const communities: Community[] = [];\n let isolatedNodes: string[] = [];\n\n try {\n const bfs = findConnectedComponents(db, projectFilter);\n isolatedNodes = bfs.isolatedNodes;\n for (let i = 0; i < bfs.components.length; i++) {\n const comp = bfs.components[i];\n communities.push({\n id: i,\n label: comp.label,\n color: COMMUNITY_COLORS[i % COMMUNITY_COLORS.length],\n nodeIds: comp.nodeIds,\n });\n }\n } catch { /* tables may not exist */ }\n\n return c.json({ communities, isolatedNodes });\n});\n\n// ---------------------------------------------------------------------------\n// Debug Path endpoints\n// ---------------------------------------------------------------------------\n\n/**\n * GET /api/paths\n *\n * Returns a list of recent debug paths for the current project.\n * Query params:\n * ?limit=20 - max results (default 20, max 50)\n */\napiRoutes.get('/paths', (c) => {\n const db = getDb(c);\n const projectHash = getProjectHash(c);\n\n if (!projectHash) {\n return c.json({ paths: [] });\n }\n\n const limitStr = c.req.query('limit');\n const limit = limitStr ? Math.min(Math.max(parseInt(limitStr, 10) || 20, 1), 50) : 20;\n\n try {\n const repo = new PathRepository(db, projectHash);\n const paths = repo.listPaths(limit);\n return c.json({ paths });\n } catch (err) {\n console.error('[laminark] Failed to list paths:', err);\n return c.json({ paths: [] });\n }\n});\n\n/**\n * GET /api/paths/active\n *\n * Returns the currently active debug path for the current project.\n */\napiRoutes.get('/paths/active', (c) => {\n const db = getDb(c);\n const projectHash = getProjectHash(c);\n\n if (!projectHash) {\n return c.json({ path: null });\n }\n\n try {\n const repo = new PathRepository(db, projectHash);\n const path = repo.getActivePath();\n return c.json({ path });\n } catch (err) {\n console.error('[laminark] Failed to get active path:', err);\n return c.json({ path: null });\n }\n});\n\n/**\n * GET /api/paths/:id\n *\n * Returns a single debug path with its waypoints.\n */\napiRoutes.get('/paths/:id', (c) => {\n const db = getDb(c);\n const projectHash = getProjectHash(c);\n const pathId = c.req.param('id');\n\n if (!projectHash) {\n return c.json({ error: 'Path not found' }, 404);\n }\n\n try {\n const repo = new PathRepository(db, projectHash);\n const path = repo.getPath(pathId);\n\n if (!path) {\n return c.json({ error: 'Path not found' }, 404);\n }\n\n const waypoints: PathWaypoint[] = repo.getWaypoints(pathId);\n\n // Parse kiss_summary from JSON string back to object if present\n let kissSummary: unknown = null;\n if (path.kiss_summary) {\n try {\n kissSummary = JSON.parse(path.kiss_summary);\n } catch {\n kissSummary = path.kiss_summary;\n }\n }\n\n return c.json({\n path: { ...path, kiss_summary: kissSummary },\n waypoints,\n });\n } catch (err) {\n console.error('[laminark] Failed to get path:', err);\n return c.json({ error: 'Path not found' }, 404);\n }\n});\n\n// ---------------------------------------------------------------------------\n// Tool topology endpoints\n// ---------------------------------------------------------------------------\n\n/**\n * GET /api/tools\n *\n * Returns all tools from tool_registry with usage stats.\n */\napiRoutes.get('/tools', (c) => {\n const db = getDb(c);\n\n interface ToolRow {\n id: number;\n name: string;\n tool_type: string;\n scope: string;\n status: string;\n usage_count: number;\n server_name: string | null;\n description: string | null;\n last_used_at: string | null;\n discovered_at: string;\n }\n\n let tools: ToolRow[] = [];\n try {\n tools = db.prepare(`\n SELECT id, name, tool_type, scope, status, usage_count, server_name, description, last_used_at, discovered_at\n FROM tool_registry\n ORDER BY usage_count DESC, discovered_at DESC\n `).all() as ToolRow[];\n } catch { /* table may not exist */ }\n\n return c.json({\n tools: tools.map(t => ({\n id: t.id,\n name: t.name,\n toolType: t.tool_type,\n scope: t.scope,\n status: t.status,\n usageCount: t.usage_count,\n serverName: t.server_name,\n description: t.description,\n lastUsedAt: t.last_used_at,\n discoveredAt: t.discovered_at,\n })),\n });\n});\n\n/**\n * GET /api/tools/flows\n *\n * Returns edges for the tool topology graph:\n * 1. Pre-computed routing_patterns (preceding_tools -> target_tool)\n * 2. Pairwise co-occurrence from tool_usage_events session sequences\n */\napiRoutes.get('/tools/flows', (c) => {\n const db = getDb(c);\n const projectFilter = getProjectHash(c);\n\n interface FlowEdge {\n source: string;\n target: string;\n frequency: number;\n edgeType: 'pattern' | 'session';\n }\n\n const edges: FlowEdge[] = [];\n const edgeKey = new Set<string>();\n\n // 1. routing_patterns edges\n try {\n let sql = 'SELECT target_tool, preceding_tools, frequency FROM routing_patterns';\n const params: unknown[] = [];\n if (projectFilter) {\n sql += ' WHERE project_hash = ?';\n params.push(projectFilter);\n }\n sql += ' ORDER BY frequency DESC LIMIT 200';\n\n const rows = db.prepare(sql).all(...params) as Array<{\n target_tool: string;\n preceding_tools: string;\n frequency: number;\n }>;\n\n for (const row of rows) {\n let preceding: string[];\n try {\n preceding = JSON.parse(row.preceding_tools);\n } catch {\n preceding = [];\n }\n for (const src of preceding) {\n const key = src + '->' + row.target_tool;\n if (!edgeKey.has(key)) {\n edgeKey.add(key);\n edges.push({\n source: src,\n target: row.target_tool,\n frequency: row.frequency,\n edgeType: 'pattern',\n });\n }\n }\n }\n } catch { /* table may not exist */ }\n\n // 2. Session co-occurrence: consecutive tool pairs within sessions\n try {\n let sql = `\n SELECT session_id, tool_name, created_at\n FROM tool_usage_events\n WHERE session_id IS NOT NULL\n `;\n const params: unknown[] = [];\n if (projectFilter) {\n sql += ' AND project_hash = ?';\n params.push(projectFilter);\n }\n sql += ' ORDER BY session_id, created_at ASC LIMIT 5000';\n\n const rows = db.prepare(sql).all(...params) as Array<{\n session_id: string;\n tool_name: string;\n created_at: string;\n }>;\n\n // Group by session and extract consecutive pairs\n const pairFreq = new Map<string, number>();\n let prevSession = '';\n let prevTool = '';\n for (const row of rows) {\n if (row.session_id === prevSession && prevTool && prevTool !== row.tool_name) {\n const key = prevTool + '->' + row.tool_name;\n pairFreq.set(key, (pairFreq.get(key) || 0) + 1);\n }\n prevSession = row.session_id;\n prevTool = row.tool_name;\n }\n\n for (const [key, freq] of pairFreq) {\n if (!edgeKey.has(key) && freq >= 2) {\n edgeKey.add(key);\n const [source, target] = key.split('->');\n edges.push({ source, target, frequency: freq, edgeType: 'session' });\n }\n }\n } catch { /* table may not exist */ }\n\n return c.json({ edges });\n});\n\n/**\n * GET /api/tools/:name/stats\n *\n * Returns detailed stats for a single tool.\n */\napiRoutes.get('/tools/:name/stats', (c) => {\n const db = getDb(c);\n const toolName = c.req.param('name');\n const projectFilter = getProjectHash(c);\n\n // Base tool info\n interface ToolRow {\n id: number;\n name: string;\n tool_type: string;\n scope: string;\n status: string;\n usage_count: number;\n server_name: string | null;\n description: string | null;\n last_used_at: string | null;\n discovered_at: string;\n }\n\n let tool: ToolRow | undefined;\n try {\n tool = db.prepare(\n 'SELECT id, name, tool_type, scope, status, usage_count, server_name, description, last_used_at, discovered_at FROM tool_registry WHERE name = ? ORDER BY usage_count DESC LIMIT 1'\n ).get(toolName) as ToolRow | undefined;\n } catch { /* table may not exist */ }\n\n if (!tool) {\n return c.json({ error: 'Tool not found' }, 404);\n }\n\n // Success rate from recent events\n let successRate: number | null = null;\n let totalEvents = 0;\n try {\n let sql = 'SELECT success FROM tool_usage_events WHERE tool_name = ?';\n const params: unknown[] = [toolName];\n if (projectFilter) {\n sql += ' AND project_hash = ?';\n params.push(projectFilter);\n }\n sql += ' ORDER BY created_at DESC LIMIT 50';\n const events = db.prepare(sql).all(...params) as Array<{ success: number }>;\n totalEvents = events.length;\n if (totalEvents > 0) {\n const successes = events.filter(e => e.success === 1).length;\n successRate = successes / totalEvents;\n }\n } catch { /* table may not exist */ }\n\n // Sessions used in\n let sessionsUsedIn = 0;\n try {\n let sql = 'SELECT COUNT(DISTINCT session_id) as cnt FROM tool_usage_events WHERE tool_name = ? AND session_id IS NOT NULL';\n const params: unknown[] = [toolName];\n if (projectFilter) {\n sql += ' AND project_hash = ?';\n params.push(projectFilter);\n }\n const row = db.prepare(sql).get(...params) as { cnt: number } | undefined;\n sessionsUsedIn = row?.cnt ?? 0;\n } catch { /* table may not exist */ }\n\n // Top co-occurring tools (tools used in same sessions)\n let coOccurring: Array<{ name: string; count: number }> = [];\n try {\n let sql = `\n SELECT e2.tool_name as name, COUNT(*) as count\n FROM tool_usage_events e1\n JOIN tool_usage_events e2\n ON e1.session_id = e2.session_id AND e1.tool_name != e2.tool_name\n WHERE e1.tool_name = ? AND e1.session_id IS NOT NULL\n `;\n const params: unknown[] = [toolName];\n if (projectFilter) {\n sql += ' AND e1.project_hash = ?';\n params.push(projectFilter);\n }\n sql += ' GROUP BY e2.tool_name ORDER BY count DESC LIMIT 10';\n coOccurring = db.prepare(sql).all(...params) as Array<{ name: string; count: number }>;\n } catch { /* table may not exist */ }\n\n return c.json({\n tool: {\n id: tool.id,\n name: tool.name,\n toolType: tool.tool_type,\n scope: tool.scope,\n status: tool.status,\n usageCount: tool.usage_count,\n serverName: tool.server_name,\n description: tool.description,\n lastUsedAt: tool.last_used_at,\n discoveredAt: tool.discovered_at,\n },\n successRate,\n totalEvents,\n sessionsUsedIn,\n coOccurring,\n });\n});\n\n/**\n * GET /api/tools/sessions\n *\n * Returns recent session tool sequences for the flow strip.\n */\napiRoutes.get('/tools/sessions', (c) => {\n const db = getDb(c);\n const projectFilter = getProjectHash(c);\n const limitStr = c.req.query('limit');\n const limit = limitStr ? Math.min(parseInt(limitStr, 10) || 10, 30) : 10;\n\n interface SessionToolRow {\n session_id: string;\n tool_name: string;\n created_at: string;\n }\n\n let sessions: Array<{ sessionId: string; tools: Array<{ name: string; time: string }> }> = [];\n try {\n // Get recent sessions that have tool events\n let sessionSql = `\n SELECT DISTINCT session_id FROM tool_usage_events\n WHERE session_id IS NOT NULL\n `;\n const sessionParams: unknown[] = [];\n if (projectFilter) {\n sessionSql += ' AND project_hash = ?';\n sessionParams.push(projectFilter);\n }\n sessionSql += ' ORDER BY created_at DESC LIMIT ?';\n sessionParams.push(limit);\n\n const sessionIds = db.prepare(sessionSql).all(...sessionParams) as Array<{ session_id: string }>;\n\n if (sessionIds.length > 0) {\n const placeholders = sessionIds.map(() => '?').join(', ');\n const ids = sessionIds.map(s => s.session_id);\n\n const eventRows = db.prepare(`\n SELECT session_id, tool_name, created_at\n FROM tool_usage_events\n WHERE session_id IN (${placeholders})\n ORDER BY session_id, created_at ASC\n `).all(...ids) as SessionToolRow[];\n\n // Group by session\n const sessionMap = new Map<string, Array<{ name: string; time: string }>>();\n for (const row of eventRows) {\n if (!sessionMap.has(row.session_id)) {\n sessionMap.set(row.session_id, []);\n }\n sessionMap.get(row.session_id)!.push({\n name: row.tool_name,\n time: row.created_at,\n });\n }\n\n sessions = sessionIds\n .filter(s => sessionMap.has(s.session_id))\n .map(s => ({\n sessionId: s.session_id,\n tools: sessionMap.get(s.session_id)!,\n }));\n }\n } catch { /* table may not exist */ }\n\n return c.json({ sessions });\n});\n\n// ---------------------------------------------------------------------------\n// Helpers\n// ---------------------------------------------------------------------------\n\ninterface BfsComponent {\n nodeIds: string[];\n label: string;\n edgeCount: number;\n}\n\n/**\n * Finds connected components in the graph via BFS.\n * Shared by /api/graph/analysis and /api/graph/communities.\n */\nfunction findConnectedComponents(\n db: BetterSqlite3.Database,\n projectFilter: string | null,\n): { components: BfsComponent[]; isolatedNodes: string[]; adj: Map<string, Set<string>> } {\n // Fetch all nodes\n let nodesSql = 'SELECT id, name FROM graph_nodes';\n const nodesParams: unknown[] = [];\n if (projectFilter) {\n nodesSql += ' WHERE project_hash = ?';\n nodesParams.push(projectFilter);\n }\n const allNodes = db.prepare(nodesSql).all(...nodesParams) as Array<{ id: string; name: string }>;\n const nodeNameMap = new Map(allNodes.map(n => [n.id, n.name]));\n\n // Fetch all edges\n let edgesSql = 'SELECT source_id, target_id FROM graph_edges';\n const edgesParams: unknown[] = [];\n if (projectFilter) {\n edgesSql += ' WHERE project_hash = ?';\n edgesParams.push(projectFilter);\n }\n const allEdges = db.prepare(edgesSql).all(...edgesParams) as Array<{ source_id: string; target_id: string }>;\n\n // Build adjacency list\n const adj = new Map<string, Set<string>>();\n for (const node of allNodes) {\n adj.set(node.id, new Set());\n }\n for (const edge of allEdges) {\n if (adj.has(edge.source_id)) adj.get(edge.source_id)!.add(edge.target_id);\n if (adj.has(edge.target_id)) adj.get(edge.target_id)!.add(edge.source_id);\n }\n\n // BFS to find connected components\n const visited = new Set<string>();\n const components: BfsComponent[] = [];\n const isolatedNodes: string[] = [];\n\n for (const nodeId of adj.keys()) {\n if (visited.has(nodeId)) continue;\n\n const queue = [nodeId];\n visited.add(nodeId);\n const compNodes: string[] = [];\n\n while (queue.length > 0) {\n const current = queue.shift()!;\n compNodes.push(current);\n for (const neighbor of adj.get(current) || []) {\n if (!visited.has(neighbor)) {\n visited.add(neighbor);\n queue.push(neighbor);\n }\n }\n }\n\n // Detect isolated nodes (single node, no edges)\n if (compNodes.length === 1 && (adj.get(compNodes[0])?.size ?? 0) === 0) {\n isolatedNodes.push(compNodes[0]);\n continue;\n }\n\n // Count edges within this component\n const compSet = new Set(compNodes);\n let edgeCount = 0;\n for (const edge of allEdges) {\n if (compSet.has(edge.source_id) && compSet.has(edge.target_id)) {\n edgeCount++;\n }\n }\n\n // Label by highest-degree node\n let maxDeg = -1;\n let labelNodeId = compNodes[0];\n for (const nid of compNodes) {\n const deg = (adj.get(nid) || new Set()).size;\n if (deg > maxDeg) {\n maxDeg = deg;\n labelNodeId = nid;\n }\n }\n\n components.push({\n nodeIds: compNodes,\n label: nodeNameMap.get(labelNodeId) || labelNodeId,\n edgeCount,\n });\n }\n\n // Sort by size descending\n components.sort((a, b) => b.nodeIds.length - a.nodeIds.length);\n\n return { components, isolatedNodes, adj };\n}\n\nfunction safeParseJsonArray(json: string): string[] {\n try {\n const parsed = JSON.parse(json);\n return Array.isArray(parsed) ? parsed : [];\n } catch {\n return [];\n }\n}\n\nfunction safeParseJson(json: string): Record<string, unknown> {\n try {\n return JSON.parse(json) as Record<string, unknown>;\n } catch {\n return {};\n }\n}\n","/**\n * Admin API routes for database statistics and reset operations.\n *\n * @module web/routes/admin\n */\n\nimport { existsSync, readFileSync, statSync, unlinkSync, writeFileSync } from 'node:fs';\nimport { dirname, join } from 'node:path';\nimport { fileURLToPath } from 'node:url';\n\nimport { Hono } from 'hono';\nimport type BetterSqlite3 from 'better-sqlite3';\n\nconst __dirname = dirname(fileURLToPath(import.meta.url));\nconst LAMINARK_VERSION = (() => {\n try {\n const pkg = JSON.parse(readFileSync(join(__dirname, '..', '..', 'package.json'), 'utf-8'));\n return pkg.version || 'unknown';\n } catch { return 'unknown'; }\n})();\n\nimport { getConfigDir } from '../../shared/config.js';\nimport { analyzeObservations, executePurge, findAnalysis } from '../../graph/hygiene-analyzer.js';\nimport { loadHygieneConfig, saveHygieneConfig, resetHygieneConfig } from '../../config/hygiene-config.js';\nimport { loadTopicDetectionConfig } from '../../config/topic-detection-config.js';\nimport { loadGraphExtractionConfig } from '../../config/graph-extraction-config.js';\nimport { loadCrossAccessConfig, saveCrossAccessConfig, resetCrossAccessConfig } from '../../config/cross-access.js';\nimport { loadToolVerbosityConfig, saveToolVerbosityConfig, resetToolVerbosityConfig } from '../../config/tool-verbosity-config.js';\n\ntype AppEnv = {\n Variables: {\n db: BetterSqlite3.Database;\n defaultProject: string;\n };\n};\n\nfunction getDb(c: { get: (key: 'db') => BetterSqlite3.Database }): BetterSqlite3.Database {\n return c.get('db');\n}\n\nfunction getProjectHash(c: { get: (key: 'defaultProject') => string; req: { query: (key: string) => string | undefined } }): string | null {\n return c.req.query('project') || c.get('defaultProject') || null;\n}\n\nconst ALLOWED_TABLES = new Set([\n 'observations', 'observations_fts', 'observation_embeddings', 'staleness_flags',\n 'graph_nodes', 'graph_edges', 'sessions', 'context_stashes', 'threshold_history',\n 'shift_decisions', 'pending_notifications', 'project_metadata', '_migrations',\n 'tool_registry', 'tool_usage_events', 'research_buffer',\n]);\n\nfunction tableCount(db: BetterSqlite3.Database, table: string, where?: string, params?: unknown[]): number {\n if (!ALLOWED_TABLES.has(table)) return 0;\n try {\n const sql = where\n ? `SELECT COUNT(*) AS cnt FROM ${table} WHERE ${where}`\n : `SELECT COUNT(*) AS cnt FROM ${table}`;\n const row = db.prepare(sql).get(...(params || [])) as { cnt: number } | undefined;\n return row?.cnt ?? 0;\n } catch {\n return 0;\n }\n}\n\nexport const adminRoutes = new Hono<AppEnv>();\n\n/**\n * GET /api/admin/stats\n *\n * Returns row counts per table group, optionally scoped to a project.\n */\nadminRoutes.get('/stats', (c) => {\n const db = getDb(c);\n const project = c.req.query('project') || getProjectHash(c);\n\n const projectWhere = project ? 'project_hash = ?' : undefined;\n const projectIdWhere = project ? 'project_id = ?' : undefined;\n const projectParams = project ? [project] : undefined;\n\n const observations = tableCount(db, 'observations', projectWhere, projectParams);\n const observationsFts = tableCount(db, 'observations_fts');\n const observationEmbeddings = tableCount(db, 'observation_embeddings');\n const stalenessFlags = tableCount(db, 'staleness_flags');\n const graphNodes = tableCount(db, 'graph_nodes', projectWhere, projectParams);\n const graphEdges = tableCount(db, 'graph_edges', projectWhere, projectParams);\n const sessions = tableCount(db, 'sessions', projectWhere, projectParams);\n const contextStashes = tableCount(db, 'context_stashes', projectIdWhere, projectParams);\n const thresholdHistory = tableCount(db, 'threshold_history', projectIdWhere, projectParams);\n const shiftDecisions = tableCount(db, 'shift_decisions', projectIdWhere, projectParams);\n const pendingNotifications = tableCount(db, 'pending_notifications', projectIdWhere, projectParams);\n const projects = tableCount(db, 'project_metadata');\n\n return c.json({\n observations,\n observationsFts,\n observationEmbeddings,\n stalenessFlags,\n graphNodes,\n graphEdges,\n sessions,\n contextStashes,\n thresholdHistory,\n shiftDecisions,\n pendingNotifications,\n projects,\n scopedToProject: project || null,\n });\n});\n\n/**\n * GET /api/admin/system\n *\n * Returns server-scoped system info (not project-scoped).\n */\nadminRoutes.get('/system', (c) => {\n const db = getDb(c);\n const mem = process.memoryUsage();\n\n let dbSizeBytes = 0;\n let pageCount = 0;\n let pageSize = 4096;\n try {\n const pc = db.pragma('page_count', { simple: true }) as number;\n const ps = db.pragma('page_size', { simple: true }) as number;\n pageCount = pc;\n pageSize = ps;\n dbSizeBytes = pc * ps;\n } catch { /* ignore */ }\n\n let walSizeBytes = 0;\n try {\n const dbPath = db.name;\n if (dbPath) {\n const walPath = dbPath + '-wal';\n if (existsSync(walPath)) {\n walSizeBytes = statSync(walPath).size;\n }\n }\n } catch { /* ignore */ }\n\n return c.json({\n laminarkVersion: LAMINARK_VERSION,\n nodeVersion: process.version,\n platform: process.platform,\n arch: process.arch,\n uptimeSeconds: Math.floor(process.uptime()),\n memory: {\n rssBytes: mem.rss,\n heapUsedBytes: mem.heapUsed,\n heapTotalBytes: mem.heapTotal,\n },\n database: {\n sizeBytes: dbSizeBytes,\n walSizeBytes,\n pageCount,\n pageSize,\n },\n });\n});\n\n/**\n * POST /api/admin/reset\n *\n * Hard-deletes data by group inside a transaction.\n * Body: { type: 'observations'|'graph'|'sessions'|'all', scope: 'current'|'all', projectHash?: string }\n */\nadminRoutes.post('/reset', async (c) => {\n const db = getDb(c);\n const body = await c.req.json<{ type: string; scope: string; projectHash?: string }>();\n const { type, scope } = body;\n const project = body.projectHash || getProjectHash(c);\n\n const validTypes = ['observations', 'graph', 'sessions', 'all'];\n if (!validTypes.includes(type)) {\n return c.json({ error: `Invalid type. Must be one of: ${validTypes.join(', ')}` }, 400);\n }\n\n const scoped = scope === 'current' && project;\n const deleted: string[] = [];\n\n const exec = (sql: string) => {\n try { db.exec(sql); } catch { /* table/trigger may not exist */ }\n };\n\n const run = (sql: string, params?: unknown[]) => {\n try {\n db.prepare(sql).run(...(params || []));\n } catch {\n // Table may not exist — skip silently\n }\n };\n\n db.transaction(() => {\n if (type === 'observations' || type === 'all') {\n // Drop FTS sync triggers FIRST — they fire on every DELETE and will\n // abort the delete if the FTS index is out of sync with observations.\n exec('DROP TRIGGER IF EXISTS observations_ai');\n exec('DROP TRIGGER IF EXISTS observations_au');\n exec('DROP TRIGGER IF EXISTS observations_ad');\n\n if (scoped) {\n run('DELETE FROM observation_embeddings WHERE observation_id IN (SELECT id FROM observations WHERE project_hash = ?)', [project]);\n run('DELETE FROM staleness_flags WHERE observation_id IN (SELECT id FROM observations WHERE project_hash = ?)', [project]);\n run('DELETE FROM observations WHERE project_hash = ?', [project]);\n } else {\n run('DELETE FROM observation_embeddings');\n run('DELETE FROM staleness_flags');\n run('DELETE FROM observations');\n }\n\n // Rebuild FTS (will be empty or contain only remaining rows)\n exec(\"INSERT INTO observations_fts(observations_fts) VALUES('rebuild')\");\n\n // Recreate FTS sync triggers\n exec(`\n CREATE TRIGGER observations_ai AFTER INSERT ON observations BEGIN\n INSERT INTO observations_fts(rowid, title, content)\n VALUES (new.rowid, new.title, new.content);\n END\n `);\n exec(`\n CREATE TRIGGER observations_au AFTER UPDATE ON observations BEGIN\n INSERT INTO observations_fts(observations_fts, rowid, title, content)\n VALUES('delete', old.rowid, old.title, old.content);\n INSERT INTO observations_fts(rowid, title, content)\n VALUES (new.rowid, new.title, new.content);\n END\n `);\n exec(`\n CREATE TRIGGER observations_ad AFTER DELETE ON observations BEGIN\n INSERT INTO observations_fts(observations_fts, rowid, title, content)\n VALUES('delete', old.rowid, old.title, old.content);\n END\n `);\n\n deleted.push('observations', 'observations_fts', 'observation_embeddings', 'staleness_flags');\n }\n\n if (type === 'graph' || type === 'all') {\n if (scoped) {\n run('DELETE FROM graph_edges WHERE project_hash = ?', [project]);\n run('DELETE FROM graph_nodes WHERE project_hash = ?', [project]);\n } else {\n run('DELETE FROM graph_edges');\n run('DELETE FROM graph_nodes');\n }\n deleted.push('graph_nodes', 'graph_edges');\n }\n\n if (type === 'sessions' || type === 'all') {\n if (scoped) {\n run('DELETE FROM shift_decisions WHERE project_id = ?', [project]);\n run('DELETE FROM threshold_history WHERE project_id = ?', [project]);\n run('DELETE FROM context_stashes WHERE project_id = ?', [project]);\n run('DELETE FROM pending_notifications WHERE project_id = ?', [project]);\n run('DELETE FROM sessions WHERE project_hash = ?', [project]);\n } else {\n run('DELETE FROM shift_decisions');\n run('DELETE FROM threshold_history');\n run('DELETE FROM context_stashes');\n run('DELETE FROM pending_notifications');\n run('DELETE FROM sessions');\n }\n deleted.push('sessions', 'context_stashes', 'threshold_history', 'shift_decisions', 'pending_notifications');\n }\n\n if (type === 'all' && !scoped) {\n run('DELETE FROM project_metadata');\n run('DELETE FROM _migrations');\n deleted.push('project_metadata', '_migrations');\n }\n })();\n\n return c.json({ ok: true, deleted, scope: scoped ? 'project' : 'all' });\n});\n\n// =========================================================================\n// Hygiene analysis\n// =========================================================================\n\nadminRoutes.get('/hygiene', (c) => {\n const db = getDb(c);\n const project = getProjectHash(c);\n if (!project) return c.json({ error: 'No project context available' }, 400);\n\n const tier = (c.req.query('tier') || 'high') as 'high' | 'medium' | 'all';\n const sessionId = c.req.query('session_id');\n const limit = parseInt(c.req.query('limit') || '50', 10);\n const config = loadHygieneConfig();\n\n const minTier = tier === 'all' ? 'low' as const : tier;\n const report = analyzeObservations(db, project, { sessionId, limit, minTier, config });\n\n return c.json(report);\n});\n\nadminRoutes.post('/hygiene/purge', async (c) => {\n const db = getDb(c);\n const project = getProjectHash(c);\n if (!project) return c.json({ error: 'No project context available' }, 400);\n\n const body = await c.req.json<{ tier?: string }>();\n const tier = (body.tier || 'high') as 'high' | 'medium' | 'all';\n const config = loadHygieneConfig();\n\n const minTier = tier === 'all' ? 'low' as const : tier;\n const report = analyzeObservations(db, project, { minTier, limit: 500, config });\n const result = executePurge(db, project, report, tier);\n\n return c.json({\n ok: true,\n observationsPurged: result.observationsPurged,\n orphanNodesRemoved: result.orphanNodesRemoved,\n tier,\n });\n});\n\nadminRoutes.get('/hygiene/find', (c) => {\n const db = getDb(c);\n const project = getProjectHash(c);\n if (!project) return c.json({ error: 'No project context available' }, 400);\n\n const config = loadHygieneConfig();\n const report = findAnalysis(db, project, config);\n return c.json(report);\n});\n\n// =========================================================================\n// Configuration endpoints\n// =========================================================================\n\nadminRoutes.get('/config/hygiene', (c) => {\n return c.json(loadHygieneConfig());\n});\n\nadminRoutes.put('/config/hygiene', async (c) => {\n const body = await c.req.json();\n const configPath = join(getConfigDir(), 'hygiene.json');\n\n if (body && body.__reset === true) {\n try { if (existsSync(configPath)) unlinkSync(configPath); } catch { /* ignore */ }\n return c.json(resetHygieneConfig());\n }\n\n if (typeof body !== 'object' || body === null || Array.isArray(body)) {\n return c.json({ error: 'Request body must be a JSON object' }, 400);\n }\n\n const { __reset: _, ...data } = body;\n writeFileSync(configPath, JSON.stringify(data, null, 2), 'utf-8');\n const validated = loadHygieneConfig();\n // Re-write validated config to ensure clean file\n saveHygieneConfig(validated);\n return c.json(validated);\n});\n\nadminRoutes.get('/config/topic-detection', (c) => {\n return c.json(loadTopicDetectionConfig());\n});\n\nadminRoutes.put('/config/topic-detection', async (c) => {\n const body = await c.req.json();\n const configPath = join(getConfigDir(), 'topic-detection.json');\n\n if (body && body.__reset === true) {\n try { if (existsSync(configPath)) unlinkSync(configPath); } catch { /* ignore */ }\n return c.json(loadTopicDetectionConfig());\n }\n\n if (typeof body !== 'object' || body === null || Array.isArray(body)) {\n return c.json({ error: 'Request body must be a JSON object' }, 400);\n }\n\n const { __reset: _, ...data } = body;\n // Write raw input, then re-load (validates all fields), then overwrite with validated config\n writeFileSync(configPath, JSON.stringify(data, null, 2), 'utf-8');\n const validated = loadTopicDetectionConfig();\n writeFileSync(configPath, JSON.stringify(validated, null, 2), 'utf-8');\n return c.json(validated);\n});\n\nadminRoutes.get('/config/graph-extraction', (c) => {\n return c.json(loadGraphExtractionConfig());\n});\n\nadminRoutes.put('/config/graph-extraction', async (c) => {\n const body = await c.req.json();\n const configPath = join(getConfigDir(), 'graph-extraction.json');\n\n if (body && body.__reset === true) {\n try { if (existsSync(configPath)) unlinkSync(configPath); } catch { /* ignore */ }\n return c.json(loadGraphExtractionConfig());\n }\n\n if (typeof body !== 'object' || body === null || Array.isArray(body)) {\n return c.json({ error: 'Request body must be a JSON object' }, 400);\n }\n\n const { __reset: _, ...data } = body;\n // Write raw input, then re-load (validates all fields), then overwrite with validated config\n writeFileSync(configPath, JSON.stringify(data, null, 2), 'utf-8');\n const validated = loadGraphExtractionConfig();\n writeFileSync(configPath, JSON.stringify(validated, null, 2), 'utf-8');\n return c.json(validated);\n});\n\n// =========================================================================\n// Cross-Project Access Config\n// =========================================================================\n\nadminRoutes.get('/config/cross-access', (c) => {\n const project = c.req.query('project');\n if (!project) return c.json({ error: 'project query parameter is required' }, 400);\n return c.json(loadCrossAccessConfig(project));\n});\n\nadminRoutes.put('/config/cross-access', async (c) => {\n const project = c.req.query('project');\n if (!project) return c.json({ error: 'project query parameter is required' }, 400);\n\n const body = await c.req.json();\n\n if (body && body.__reset === true) {\n resetCrossAccessConfig(project);\n return c.json(loadCrossAccessConfig(project));\n }\n\n if (typeof body !== 'object' || body === null || Array.isArray(body)) {\n return c.json({ error: 'Request body must be a JSON object' }, 400);\n }\n\n saveCrossAccessConfig(project, { readableProjects: body.readableProjects || [] });\n return c.json(loadCrossAccessConfig(project));\n});\n\n// =========================================================================\n// Tool Response Verbosity Config\n// =========================================================================\n\nadminRoutes.get('/config/tool-verbosity', (c) => {\n return c.json(loadToolVerbosityConfig());\n});\n\nadminRoutes.put('/config/tool-verbosity', async (c) => {\n const body = await c.req.json();\n\n if (body && body.__reset === true) {\n const config = resetToolVerbosityConfig();\n saveToolVerbosityConfig(config);\n return c.json(config);\n }\n\n if (typeof body !== 'object' || body === null || Array.isArray(body)) {\n return c.json({ error: 'Request body must be a JSON object' }, 400);\n }\n\n const level = body.level;\n if (level !== 1 && level !== 2 && level !== 3) {\n return c.json({ error: 'level must be 1, 2, or 3' }, 400);\n }\n\n saveToolVerbosityConfig({ level });\n return c.json(loadToolVerbosityConfig());\n});\n\n// =========================================================================\n// Project Deletion (GUI-only)\n// =========================================================================\n\ninterface ProjectRow {\n project_hash: string;\n project_path: string;\n display_name: string | null;\n last_seen_at: string;\n}\n\n/**\n * DELETE /api/admin/projects/:hash?confirm=true\n *\n * Permanently deletes all data for the given project.\n * Requires `?confirm=true` query param as a safety guard.\n * Cannot delete the currently active (most-recently-seen) project.\n */\nadminRoutes.delete('/projects/:hash', (c) => {\n const db = getDb(c);\n const projectHash = c.req.param('hash');\n const confirm = c.req.query('confirm');\n\n if (confirm !== 'true') {\n return c.json({ error: 'Safety guard: append ?confirm=true to proceed with deletion' }, 400);\n }\n\n // Determine the active project (most recently seen)\n const activeProject = db.prepare(\n 'SELECT project_hash FROM project_metadata ORDER BY last_seen_at DESC LIMIT 1',\n ).get() as { project_hash: string } | undefined;\n\n if (activeProject && projectHash === activeProject.project_hash) {\n return c.json({ error: 'Cannot delete the currently active project. Switch to a different project first.' }, 400);\n }\n\n // Validate the project exists\n const project = db.prepare(\n 'SELECT project_hash, project_path FROM project_metadata WHERE project_hash = ?',\n ).get(projectHash) as ProjectRow | undefined;\n\n if (!project) {\n return c.json({ error: `No project found with hash '${projectHash}'` }, 404);\n }\n\n // Safe helpers that ignore missing tables\n const run = (sql: string, params: unknown[]) => {\n try { db.prepare(sql).run(...params); } catch { /* table may not exist */ }\n };\n const exec = (sql: string) => {\n try { db.exec(sql); } catch { /* trigger/table may not exist */ }\n };\n\n let obsCount = 0;\n try {\n const row = db.prepare('SELECT COUNT(*) as cnt FROM observations WHERE project_hash = ?').get(projectHash) as { cnt: number };\n obsCount = row.cnt;\n } catch { /* table may not exist */ }\n\n db.transaction(() => {\n // 1. Drop FTS triggers to avoid issues during bulk delete\n exec('DROP TRIGGER IF EXISTS observations_ai');\n exec('DROP TRIGGER IF EXISTS observations_au');\n exec('DROP TRIGGER IF EXISTS observations_ad');\n\n // 2. Embeddings (no FK cascade from observations)\n run('DELETE FROM observation_embeddings WHERE observation_id IN (SELECT id FROM observations WHERE project_hash = ?)', [projectHash]);\n\n // 3. Staleness flags\n run('DELETE FROM staleness_flags WHERE observation_id IN (SELECT id FROM observations WHERE project_hash = ?)', [projectHash]);\n\n // 4. Observations\n run('DELETE FROM observations WHERE project_hash = ?', [projectHash]);\n\n // 5. Rebuild FTS\n exec(\"INSERT INTO observations_fts(observations_fts) VALUES('rebuild')\");\n\n // 6. Recreate FTS sync triggers\n exec(`\n CREATE TRIGGER observations_ai AFTER INSERT ON observations BEGIN\n INSERT INTO observations_fts(rowid, title, content)\n VALUES (new.rowid, new.title, new.content);\n END\n `);\n exec(`\n CREATE TRIGGER observations_au AFTER UPDATE ON observations BEGIN\n INSERT INTO observations_fts(observations_fts, rowid, title, content)\n VALUES('delete', old.rowid, old.title, old.content);\n INSERT INTO observations_fts(rowid, title, content)\n VALUES (new.rowid, new.title, new.content);\n END\n `);\n exec(`\n CREATE TRIGGER observations_ad AFTER DELETE ON observations BEGIN\n INSERT INTO observations_fts(observations_fts, rowid, title, content)\n VALUES('delete', old.rowid, old.title, old.content);\n END\n `);\n\n // 7. Graph (edges have FK cascade from nodes, but delete both explicitly)\n run('DELETE FROM graph_edges WHERE project_hash = ?', [projectHash]);\n run('DELETE FROM graph_nodes WHERE project_hash = ?', [projectHash]);\n\n // 8. Sessions and legacy project_id tables\n run('DELETE FROM sessions WHERE project_hash = ?', [projectHash]);\n run('DELETE FROM shift_decisions WHERE project_id = ?', [projectHash]);\n run('DELETE FROM threshold_history WHERE project_id = ?', [projectHash]);\n run('DELETE FROM context_stashes WHERE project_id = ?', [projectHash]);\n run('DELETE FROM pending_notifications WHERE project_id = ?', [projectHash]);\n\n // 9. Research buffer\n run('DELETE FROM research_buffer WHERE project_hash = ?', [projectHash]);\n\n // 10. Tool registry (project-scoped entries only)\n run('DELETE FROM tool_registry WHERE project_hash = ?', [projectHash]);\n run('DELETE FROM tool_usage_events WHERE project_hash = ?', [projectHash]);\n\n // 11. Debug paths (path_waypoints cascade via FK)\n run('DELETE FROM debug_paths WHERE project_hash = ?', [projectHash]);\n\n // 12. Thought branches (branch_observations cascade via FK)\n run('DELETE FROM thought_branches WHERE project_hash = ?', [projectHash]);\n\n // 13. Routing state\n run('DELETE FROM routing_patterns WHERE project_hash = ?', [projectHash]);\n run('DELETE FROM routing_state WHERE project_hash = ?', [projectHash]);\n\n // 14. Project metadata last\n run('DELETE FROM project_metadata WHERE project_hash = ?', [projectHash]);\n })();\n\n return c.json({\n ok: true,\n deleted: {\n projectPath: project.project_path,\n projectHash,\n observationsRemoved: obsCount,\n },\n });\n});\n","/**\n * Hono web server for the Laminark visualization UI.\n *\n * Serves static assets from the ui/ directory and registers REST API\n * and SSE route groups. Configured with CORS for localhost development\n * and a health check endpoint.\n *\n * @module web/server\n */\n\nimport path from 'path';\nimport fs from 'fs';\nimport { Hono } from 'hono';\nimport { cors } from 'hono/cors';\nimport { serve } from '@hono/node-server';\nimport { serveStatic } from '@hono/node-server/serve-static';\nimport type BetterSqlite3 from 'better-sqlite3';\n\ntype AppEnv = {\n Variables: {\n db: BetterSqlite3.Database;\n defaultProject: string;\n };\n};\n\nimport { debug } from '../shared/debug.js';\nimport { apiRoutes } from './routes/api.js';\nimport { sseRoutes } from './routes/sse.js';\nimport { adminRoutes } from './routes/admin.js';\n\n/**\n * Creates a configured Hono app with middleware, static serving,\n * and route registration.\n *\n * @param db - better-sqlite3 Database instance for API queries\n * @param uiRoot - Absolute path to the ui/ directory for static file serving\n * @returns Configured Hono app\n */\nexport function createWebServer(db: BetterSqlite3.Database, uiRoot: string, defaultProjectHash?: string): Hono<AppEnv> {\n const app = new Hono<AppEnv>();\n\n // CORS middleware for localhost development\n app.use(\n '*',\n cors({\n origin: (origin) => {\n if (!origin) return '*';\n if (origin.startsWith('http://localhost:') || origin.startsWith('http://127.0.0.1:')) {\n return origin;\n }\n return null as unknown as string;\n },\n }),\n );\n\n // Make db and defaultProject available to all route handlers via Hono context\n app.use('*', async (c, next) => {\n c.set('db', db);\n if (defaultProjectHash) {\n c.set('defaultProject', defaultProjectHash);\n }\n await next();\n });\n\n // Health check endpoint\n app.get('/api/health', (c) => {\n return c.json({ status: 'ok', timestamp: Date.now() });\n });\n\n // Mount API and SSE routes\n app.route('/api', apiRoutes);\n app.route('/api', sseRoutes);\n app.route('/api/admin', adminRoutes);\n\n // Serve static files from ui/ directory (absolute path so CWD doesn't matter)\n app.use('/*', async (c, next) => {\n const reqPath = c.req.path === '/' ? '/index.html' : c.req.path;\n const filePath = path.join(uiRoot, reqPath);\n try {\n const data = fs.readFileSync(filePath);\n const ext = path.extname(filePath).toLowerCase();\n const mimeTypes: Record<string, string> = {\n '.html': 'text/html',\n '.js': 'application/javascript',\n '.css': 'text/css',\n '.json': 'application/json',\n '.png': 'image/png',\n '.svg': 'image/svg+xml',\n '.ico': 'image/x-icon',\n };\n return c.body(data, 200, {\n 'Content-Type': mimeTypes[ext] || 'application/octet-stream',\n });\n } catch {\n await next();\n }\n });\n\n // Fallback: serve index.html for SPA routing\n app.get('*', async (c) => {\n const indexPath = path.join(uiRoot, 'index.html');\n try {\n const data = fs.readFileSync(indexPath, 'utf-8');\n return c.html(data);\n } catch {\n return c.text('UI not found', 404);\n }\n });\n\n return app;\n}\n\n/**\n * Starts the Hono web server on the specified port.\n *\n * If the port is already in use (EADDRINUSE), skips starting — the first\n * MCP instance owns the web server and all instances share the same SQLite\n * database via WAL mode, so a single web server suffices.\n *\n * @param app - Configured Hono app from createWebServer()\n * @param port - Port number (default: 37820)\n * @returns The Node.js HTTP server instance, or null if port is already taken\n */\nexport function startWebServer(\n app: Hono<AppEnv>,\n port: number = 37820,\n): ReturnType<typeof serve> | null {\n debug('db', `Starting web server on port ${port}`);\n\n const server = serve({\n fetch: app.fetch,\n port,\n });\n\n server.on('error', (err: NodeJS.ErrnoException) => {\n if (err.code === 'EADDRINUSE') {\n server.close();\n debug('db', `Web server already running on port ${port}, skipping`);\n } else {\n debug('db', `Web server error: ${err.message}`);\n }\n });\n\n server.on('listening', () => {\n const addr = server.address();\n const actualPort = typeof addr === 'object' && addr ? addr.port : port;\n debug('db', `Web server listening on http://localhost:${actualPort}`);\n });\n\n return server;\n}\n","#!/usr/bin/env node\n\n// Laminark MCP server entry point\n// Re-export storage API for library consumers\nexport * from './storage/index.js';\n\nimport path from 'path';\nimport { fileURLToPath } from 'url';\n\nimport { openDatabase } from './storage/database.js';\nimport { getDatabaseConfig, getProjectHash } from './shared/config.js';\nimport { debug } from './shared/debug.js';\nimport type { ProjectHashRef } from './shared/types.js';\nimport { createServer, startServer } from './mcp/server.js';\nimport { registerRecall } from './mcp/tools/recall.js';\nimport { registerSaveMemory } from './mcp/tools/save-memory.js';\nimport { registerIngestKnowledge } from './mcp/tools/ingest-knowledge.js';\nimport { registerTopicContext } from './mcp/tools/topic-context.js';\nimport { registerQueryGraph } from './mcp/tools/query-graph.js';\nimport { registerGraphStats } from './mcp/tools/graph-stats.js';\nimport { registerHygiene } from './mcp/tools/hygiene.js';\nimport { registerStatus } from './mcp/tools/status.js';\nimport { StatusCache } from './mcp/status-cache.js';\nimport { registerDiscoverTools } from './mcp/tools/discover-tools.js';\nimport { registerReportTools } from './mcp/tools/report-tools.js';\nimport { registerDebugPathTools } from './mcp/tools/debug-paths.js';\nimport { registerThoughtBranchTools } from './mcp/tools/thought-branches.js';\nimport { BranchRepository } from './branches/branch-repository.js';\nimport { BranchTracker } from './branches/branch-tracker.js';\nimport { AnalysisWorker } from './analysis/worker-bridge.js';\nimport { EmbeddingStore } from './storage/embeddings.js';\nimport { ObservationRepository } from './storage/observations.js';\nimport { ResearchBufferRepository } from './storage/research-buffer.js';\nimport { TopicShiftHandler } from './hooks/topic-shift-handler.js';\nimport { TopicShiftDetector } from './intelligence/topic-detector.js';\nimport { AdaptiveThresholdManager } from './intelligence/adaptive-threshold.js';\nimport { TopicShiftDecisionLogger } from './intelligence/decision-logger.js';\nimport { loadTopicDetectionConfig, applyConfig } from './config/topic-detection-config.js';\nimport { loadGraphExtractionConfig } from './config/graph-extraction-config.js';\nimport { StashManager } from './storage/stash-manager.js';\nimport { ThresholdStore } from './storage/threshold-store.js';\nimport { NotificationStore } from './storage/notifications.js';\nimport { initGraphSchema } from './graph/schema.js';\nimport { CurationAgent } from './graph/curation-agent.js';\nimport { HaikuProcessor } from './intelligence/haiku-processor.js';\nimport { initPathSchema } from './paths/schema.js';\nimport { PathRepository } from './paths/path-repository.js';\nimport { PathTracker } from './paths/path-tracker.js';\nimport { broadcast } from './web/routes/sse.js';\nimport { createWebServer, startWebServer } from './web/server.js';\nimport { ToolRegistryRepository } from './storage/tool-registry.js';\n\nconst noGui = process.argv.includes('--no_gui');\n\nconst db = openDatabase(getDatabaseConfig());\ninitGraphSchema(db.db);\ninitPathSchema(db.db);\n\n// ---------------------------------------------------------------------------\n// Live project hash ref — auto-refreshes from project_metadata on access.\n//\n// The MCP server's process.cwd() is the plugin install path, NOT the user's\n// project directory. The correct hash is written to project_metadata by the\n// SessionStart hook (which receives input.cwd from Claude Code). This ref\n// re-queries the database at most once per CHECK_INTERVAL_MS so tool handlers\n// always use the correct, current project hash.\n// ---------------------------------------------------------------------------\n\nclass LiveProjectHashRef implements ProjectHashRef {\n private _current: string;\n private _lastChecked = 0;\n private _db: import('better-sqlite3').Database;\n private static readonly CHECK_INTERVAL_MS = 2000;\n\n constructor(sqliteDb: import('better-sqlite3').Database) {\n this._db = sqliteDb;\n // Best-effort initial value (may be stale from a previous session).\n // First tool-call access will re-query after CHECK_INTERVAL_MS (0ms elapsed\n // since _lastChecked starts at 0, so the very first .current triggers a refresh).\n this._current = this.resolve();\n }\n\n get current(): string {\n const now = Date.now();\n if (now - this._lastChecked >= LiveProjectHashRef.CHECK_INTERVAL_MS) {\n this._lastChecked = now;\n const fresh = this.resolve();\n if (fresh !== this._current) {\n debug('mcp', 'Project hash refreshed from database', { old: this._current, new: fresh });\n this._current = fresh;\n }\n }\n return this._current;\n }\n\n private resolve(): string {\n try {\n const row = this._db.prepare(\n 'SELECT project_hash FROM project_metadata ORDER BY last_seen_at DESC LIMIT 1',\n ).get() as { project_hash: string } | undefined;\n if (row?.project_hash) return row.project_hash;\n } catch {\n // Table may not exist yet on first run\n }\n return getProjectHash(process.cwd());\n }\n}\n\nconst projectHashRef: ProjectHashRef = new LiveProjectHashRef(db.db);\n\n// Tool registry (cross-project, scope-aware)\nlet toolRegistry: ToolRegistryRepository | null = null;\ntry {\n toolRegistry = new ToolRegistryRepository(db.db);\n} catch {\n debug('mcp', 'Tool registry not available (pre-migration-16)');\n}\n\n// ---------------------------------------------------------------------------\n// Worker thread and embedding store (graceful degradation)\n// ---------------------------------------------------------------------------\n\nconst embeddingStore = db.hasVectorSupport\n ? new EmbeddingStore(db.db, projectHashRef.current)\n : null;\n\nconst worker = new AnalysisWorker();\n\n// Start worker in background -- do NOT await during server startup (DQ-04)\nconst workerReady = worker.start().catch(() => {\n debug('mcp', 'Worker failed to start, keyword-only mode');\n});\n\n// Suppress unhandled rejection from workerReady (already handled above)\nvoid workerReady;\n\n// ---------------------------------------------------------------------------\n// Topic shift detection (runs in background embedding loop)\n// ---------------------------------------------------------------------------\n\nconst topicConfig = loadTopicDetectionConfig();\nconst graphConfig = loadGraphExtractionConfig();\nconst detector = new TopicShiftDetector();\nconst adaptiveManager = new AdaptiveThresholdManager({\n sensitivityMultiplier: topicConfig.sensitivityMultiplier,\n alpha: topicConfig.ewmaAlpha,\n});\napplyConfig(topicConfig, detector, adaptiveManager);\n\n// Seed adaptive threshold from history (cold start handling)\nconst thresholdStore = new ThresholdStore(db.db);\nconst historicalSeed = thresholdStore.loadHistoricalSeed(projectHashRef.current);\nif (historicalSeed) {\n adaptiveManager.seedFromHistory(historicalSeed.averageDistance, historicalSeed.averageVariance);\n applyConfig(topicConfig, detector, adaptiveManager);\n}\n\nconst stashManager = new StashManager(db.db);\nconst decisionLogger = new TopicShiftDecisionLogger(db.db);\nconst notificationStore = new NotificationStore(db.db);\nconst obsRepoForTopicDetection = new ObservationRepository(db.db, projectHashRef.current);\n\nconst topicShiftHandler = new TopicShiftHandler({\n detector,\n stashManager,\n observationStore: obsRepoForTopicDetection,\n config: topicConfig,\n decisionLogger,\n adaptiveManager,\n});\n\n// ---------------------------------------------------------------------------\n// Background embedding loop\n// ---------------------------------------------------------------------------\n\n// Sources that reflect user-directed work (for topic shift detection).\n// Exploration tools (Read/Glob/Grep/Task) are excluded to avoid false shifts.\nconst TOPIC_SHIFT_SOURCES = new Set(['hook:Write', 'hook:Edit', 'hook:Bash', 'manual']);\n\nasync function processUnembedded(): Promise<void> {\n if (!embeddingStore || !worker.isReady()) return;\n\n const ids = embeddingStore.findUnembedded(10);\n if (ids.length === 0) return;\n\n const currentHash = projectHashRef.current;\n const obsRepo = new ObservationRepository(db.db, currentHash);\n\n // At most one topic shift notification per processing cycle\n let shiftDetectedThisCycle = false;\n\n for (const id of ids) {\n const obs = obsRepo.getById(id);\n if (!obs) continue;\n\n const text = obs.title ? `${obs.title}\\n${obs.content}` : obs.content;\n const embedding = await worker.embed(text);\n\n if (embedding) {\n embeddingStore.store(id, embedding);\n obsRepo.update(id, {\n embeddingModel: worker.getEngineName(),\n embeddingVersion: '1',\n });\n\n // Broadcast new observation to SSE clients (minimal payload)\n const truncatedText = obs.content.length > 120\n ? obs.content.substring(0, 120) + '...'\n : obs.content;\n broadcast('new_observation', {\n id,\n text: truncatedText,\n sessionId: obs.sessionId ?? null,\n createdAt: obs.createdAt,\n projectHash: currentHash,\n });\n\n // Topic shift detection -- only evaluate user-directed observations\n // (Write/Edit/Bash reflect user intent; Read/Glob/Grep are exploration noise)\n // Only one shift notification per processing cycle to avoid spam.\n if (topicConfig.enabled && !shiftDetectedThisCycle && TOPIC_SHIFT_SOURCES.has(obs.source)) {\n try {\n // Build the observation with its newly generated embedding\n const obsWithEmbedding = { ...obs, embedding };\n const result = await topicShiftHandler.handleObservation(\n obsWithEmbedding,\n obs.sessionId ?? 'unknown',\n currentHash,\n );\n if (result.stashed && result.notification) {\n shiftDetectedThisCycle = true;\n notificationStore.add(currentHash, result.notification);\n debug('embed', 'Topic shift detected, notification queued', { id });\n\n // Broadcast topic shift to SSE clients\n broadcast('topic_shift', {\n id: result.notification.substring(0, 32),\n fromTopic: null,\n toTopic: null,\n timestamp: new Date().toISOString(),\n confidence: null,\n projectHash: currentHash,\n });\n }\n } catch (topicErr) {\n const msg = topicErr instanceof Error ? topicErr.message : String(topicErr);\n debug('embed', 'Topic shift detection error (non-fatal)', { error: msg });\n }\n }\n\n // Knowledge graph extraction is now handled by HaikuProcessor in the background.\n // The embedding loop only handles: embeddings, SSE broadcast, topic shift detection.\n }\n }\n}\n\n// Research buffer instance for periodic flush\nlet researchBufferForFlush: ResearchBufferRepository | null = null;\ntry {\n researchBufferForFlush = new ResearchBufferRepository(db.db, projectHashRef.current);\n} catch {\n // Table may not exist yet\n}\n\n// Background tool description embedding (enables semantic tool search)\nasync function processUnembeddedTools(): Promise<void> {\n if (!toolRegistry || !worker.isReady() || !db.hasVectorSupport) return;\n\n try {\n const unembedded = toolRegistry.findUnembeddedTools(5);\n for (const tool of unembedded) {\n const text = `${tool.name} ${tool.description}`;\n const embedding = await worker.embed(text);\n if (embedding) {\n toolRegistry.storeEmbedding(tool.id, embedding);\n }\n }\n } catch (err) {\n const msg = err instanceof Error ? err.message : String(err);\n debug('embed', 'Tool embedding error (non-fatal)', { error: msg });\n }\n}\n\n// Process unembedded observations every 5 seconds\nconst embedTimer = setInterval(() => {\n processUnembedded().catch((err) => {\n const message = err instanceof Error ? err.message : String(err);\n debug('embed', 'Background embedding error', { error: message });\n });\n processUnembeddedTools().catch((err) => {\n const message = err instanceof Error ? err.message : String(err);\n debug('embed', 'Tool embedding background error', { error: message });\n });\n // Flush old research buffer entries (older than 30 minutes)\n try {\n researchBufferForFlush?.flush(30);\n } catch {\n // Non-fatal\n }\n // Refresh status cache if data changed since last build\n statusCache.refreshIfDirty();\n}, 5000);\n\n// ---------------------------------------------------------------------------\n// MCP server setup\n// ---------------------------------------------------------------------------\n\nconst statusCache = new StatusCache(\n db.db, projectHashRef, process.cwd(), db.hasVectorSupport, () => worker.isReady(),\n);\n\nconst server = createServer();\nregisterSaveMemory(server, db.db, projectHashRef, notificationStore, worker, embeddingStore, statusCache);\nregisterIngestKnowledge(server, db.db, projectHashRef, notificationStore, statusCache);\nregisterRecall(server, db.db, projectHashRef, worker, embeddingStore, notificationStore, statusCache);\nregisterTopicContext(server, db.db, projectHashRef, notificationStore);\nregisterQueryGraph(server, db.db, projectHashRef, notificationStore);\nregisterGraphStats(server, db.db, projectHashRef, notificationStore);\nregisterHygiene(server, db.db, projectHashRef, notificationStore);\nregisterStatus(server, statusCache, projectHashRef, notificationStore);\nif (toolRegistry) {\n registerDiscoverTools(server, toolRegistry, worker, db.hasVectorSupport, notificationStore, projectHashRef);\n registerReportTools(server, toolRegistry, projectHashRef);\n}\n\n// ---------------------------------------------------------------------------\n// Background Haiku processor (classification + entity extraction + relationships)\n// ---------------------------------------------------------------------------\n\nconst pathRepo = new PathRepository(db.db, projectHashRef.current);\nconst pathTracker = new PathTracker(pathRepo);\nregisterDebugPathTools(server, pathRepo, pathTracker, notificationStore, projectHashRef);\n\n// Branch tracking (thought branch auto-detection)\nlet branchRepo: BranchRepository | null = null;\nlet branchTracker: BranchTracker | undefined;\ntry {\n branchRepo = new BranchRepository(db.db, projectHashRef.current);\n branchTracker = new BranchTracker(branchRepo, db.db, projectHashRef.current);\n const obsRepoForBranches = new ObservationRepository(db.db, projectHashRef.current);\n registerThoughtBranchTools(server, branchRepo, obsRepoForBranches, notificationStore, projectHashRef);\n} catch {\n debug('mcp', 'Branch tracking not available (pre-migration-21)');\n}\n\nconst haikuProcessor = new HaikuProcessor(db.db, projectHashRef.current, {\n intervalMs: 30_000,\n batchSize: 10,\n concurrency: 3,\n pathTracker,\n branchTracker,\n});\n\nstartServer(server).then(() => {\n haikuProcessor.start();\n}).catch((err) => {\n debug('mcp', 'Fatal: failed to start server', { error: err.message });\n clearInterval(embedTimer);\n db.close();\n process.exit(1);\n});\n\n// ---------------------------------------------------------------------------\n// Web visualization server (runs alongside MCP server)\n// ---------------------------------------------------------------------------\n\nif (!noGui) {\n const webPort = parseInt(process.env.LAMINARK_WEB_PORT || '37820', 10);\n const __filename = fileURLToPath(import.meta.url);\n const __dirname = path.dirname(__filename);\n const uiRoot = path.resolve(__dirname, '..', 'ui');\n const webApp = createWebServer(db.db, uiRoot, projectHashRef.current);\n startWebServer(webApp, webPort);\n} else {\n debug('mcp', 'Web UI disabled (--no_gui)');\n}\n\n// ---------------------------------------------------------------------------\n// Background curation agent (graph maintenance)\n// ---------------------------------------------------------------------------\n\nconst curationAgent = new CurationAgent(db.db, {\n intervalMs: 5 * 60 * 1000, // 5 minutes\n graphConfig,\n onComplete: (report) => {\n debug('db', 'Curation complete', {\n merged: report.observationsMerged,\n deduped: report.entitiesDeduplicated,\n stale: report.stalenessFlagsAdded,\n pruned: report.lowValuePruned,\n decayed: report.temporalDecayUpdated,\n decayDeleted: report.temporalDecayDeleted,\n });\n statusCache.markDirty();\n },\n});\ncurationAgent.start();\n\n// ---------------------------------------------------------------------------\n// Shutdown handlers\n// ---------------------------------------------------------------------------\n\nfunction shutdown(code: number): void {\n clearInterval(embedTimer);\n haikuProcessor.stop();\n curationAgent.stop();\n worker.shutdown().catch(() => {});\n db.close();\n process.exit(code);\n}\n\nprocess.on('SIGINT', () => shutdown(0));\nprocess.on('SIGTERM', () => shutdown(0));\nprocess.on('uncaughtException', (err) => {\n debug('mcp', 'Uncaught exception', { error: err.message });\n shutdown(1);\n});\n"],"mappings":"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"}