@vinaes/succ 1.4.0 → 1.5.42

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 (687) hide show
  1. package/README.md +64 -10
  2. package/dist/cli.js +81 -1
  3. package/dist/cli.js.map +1 -1
  4. package/dist/commands/agents-md.d.ts.map +1 -1
  5. package/dist/commands/agents-md.js +3 -2
  6. package/dist/commands/agents-md.js.map +1 -1
  7. package/dist/commands/analyze-profile.d.ts.map +1 -1
  8. package/dist/commands/analyze-profile.js +32 -8
  9. package/dist/commands/analyze-profile.js.map +1 -1
  10. package/dist/commands/analyze-recursive.d.ts.map +1 -1
  11. package/dist/commands/analyze-recursive.js +6 -2
  12. package/dist/commands/analyze-recursive.js.map +1 -1
  13. package/dist/commands/analyze-utils.d.ts.map +1 -1
  14. package/dist/commands/analyze-utils.js +17 -4
  15. package/dist/commands/analyze-utils.js.map +1 -1
  16. package/dist/commands/benchmark-quality.d.ts.map +1 -1
  17. package/dist/commands/benchmark-quality.js +11 -4
  18. package/dist/commands/benchmark-quality.js.map +1 -1
  19. package/dist/commands/benchmark-sqlite-vec.d.ts.map +1 -1
  20. package/dist/commands/benchmark-sqlite-vec.js +4 -0
  21. package/dist/commands/benchmark-sqlite-vec.js.map +1 -1
  22. package/dist/commands/benchmark.d.ts.map +1 -1
  23. package/dist/commands/benchmark.js +5 -1
  24. package/dist/commands/benchmark.js.map +1 -1
  25. package/dist/commands/codex-chat.d.ts +8 -0
  26. package/dist/commands/codex-chat.d.ts.map +1 -0
  27. package/dist/commands/codex-chat.js +161 -0
  28. package/dist/commands/codex-chat.js.map +1 -0
  29. package/dist/commands/config.d.ts.map +1 -1
  30. package/dist/commands/config.js +32 -4
  31. package/dist/commands/config.js.map +1 -1
  32. package/dist/commands/daemon.d.ts.map +1 -1
  33. package/dist/commands/daemon.js +13 -4
  34. package/dist/commands/daemon.js.map +1 -1
  35. package/dist/commands/index-code.d.ts +4 -0
  36. package/dist/commands/index-code.d.ts.map +1 -1
  37. package/dist/commands/index-code.js +1 -1
  38. package/dist/commands/index-code.js.map +1 -1
  39. package/dist/commands/init.d.ts.map +1 -1
  40. package/dist/commands/init.js +305 -203
  41. package/dist/commands/init.js.map +1 -1
  42. package/dist/commands/memories.d.ts.map +1 -1
  43. package/dist/commands/memories.js +25 -14
  44. package/dist/commands/memories.js.map +1 -1
  45. package/dist/commands/progress.d.ts.map +1 -1
  46. package/dist/commands/progress.js +3 -2
  47. package/dist/commands/progress.js.map +1 -1
  48. package/dist/commands/reindex.d.ts.map +1 -1
  49. package/dist/commands/reindex.js +54 -36
  50. package/dist/commands/reindex.js.map +1 -1
  51. package/dist/commands/retention.d.ts.map +1 -1
  52. package/dist/commands/retention.js +7 -5
  53. package/dist/commands/retention.js.map +1 -1
  54. package/dist/commands/scan-code.d.ts +76 -0
  55. package/dist/commands/scan-code.d.ts.map +1 -0
  56. package/dist/commands/scan-code.js +385 -0
  57. package/dist/commands/scan-code.js.map +1 -0
  58. package/dist/commands/score.d.ts.map +1 -1
  59. package/dist/commands/score.js +3 -2
  60. package/dist/commands/score.js.map +1 -1
  61. package/dist/commands/session.d.ts +33 -0
  62. package/dist/commands/session.d.ts.map +1 -0
  63. package/dist/commands/session.js +163 -0
  64. package/dist/commands/session.js.map +1 -0
  65. package/dist/commands/setup.d.ts.map +1 -1
  66. package/dist/commands/setup.js +254 -15
  67. package/dist/commands/setup.js.map +1 -1
  68. package/dist/commands/soul.js +3 -2
  69. package/dist/commands/soul.js.map +1 -1
  70. package/dist/commands/status.d.ts.map +1 -1
  71. package/dist/commands/status.js +14 -5
  72. package/dist/commands/status.js.map +1 -1
  73. package/dist/commands/watch.d.ts.map +1 -1
  74. package/dist/commands/watch.js +13 -4
  75. package/dist/commands/watch.js.map +1 -1
  76. package/dist/daemon/analyzer.d.ts.map +1 -1
  77. package/dist/daemon/analyzer.js +21 -5
  78. package/dist/daemon/analyzer.js.map +1 -1
  79. package/dist/daemon/client.d.ts.map +1 -1
  80. package/dist/daemon/client.js +32 -8
  81. package/dist/daemon/client.js.map +1 -1
  82. package/dist/daemon/routes/analyzer.d.ts +3 -0
  83. package/dist/daemon/routes/analyzer.d.ts.map +1 -0
  84. package/dist/daemon/routes/analyzer.js +27 -0
  85. package/dist/daemon/routes/analyzer.js.map +1 -0
  86. package/dist/daemon/routes/hooks.d.ts +14 -0
  87. package/dist/daemon/routes/hooks.d.ts.map +1 -0
  88. package/dist/daemon/routes/hooks.js +1212 -0
  89. package/dist/daemon/routes/hooks.js.map +1 -0
  90. package/dist/daemon/routes/memory.d.ts +4 -0
  91. package/dist/daemon/routes/memory.d.ts.map +1 -0
  92. package/dist/daemon/routes/memory.js +71 -0
  93. package/dist/daemon/routes/memory.js.map +1 -0
  94. package/dist/daemon/routes/reflection.d.ts +10 -0
  95. package/dist/daemon/routes/reflection.d.ts.map +1 -0
  96. package/dist/daemon/routes/reflection.js +397 -0
  97. package/dist/daemon/routes/reflection.js.map +1 -0
  98. package/dist/daemon/routes/search.d.ts +5 -0
  99. package/dist/daemon/routes/search.d.ts.map +1 -0
  100. package/dist/daemon/routes/search.js +93 -0
  101. package/dist/daemon/routes/search.js.map +1 -0
  102. package/dist/daemon/routes/sessions.d.ts +3 -0
  103. package/dist/daemon/routes/sessions.d.ts.map +1 -0
  104. package/dist/daemon/routes/sessions.js +160 -0
  105. package/dist/daemon/routes/sessions.js.map +1 -0
  106. package/dist/daemon/routes/skills.d.ts +3 -0
  107. package/dist/daemon/routes/skills.d.ts.map +1 -0
  108. package/dist/daemon/routes/skills.js +36 -0
  109. package/dist/daemon/routes/skills.js.map +1 -0
  110. package/dist/daemon/routes/status.d.ts +3 -0
  111. package/dist/daemon/routes/status.d.ts.map +1 -0
  112. package/dist/daemon/routes/status.js +47 -0
  113. package/dist/daemon/routes/status.js.map +1 -0
  114. package/dist/daemon/routes/types.d.ts +240 -0
  115. package/dist/daemon/routes/types.d.ts.map +1 -0
  116. package/dist/daemon/routes/types.js +97 -0
  117. package/dist/daemon/routes/types.js.map +1 -0
  118. package/dist/daemon/routes/versioning.d.ts +27 -0
  119. package/dist/daemon/routes/versioning.d.ts.map +1 -0
  120. package/dist/daemon/routes/versioning.js +44 -0
  121. package/dist/daemon/routes/versioning.js.map +1 -0
  122. package/dist/daemon/routes/watcher.d.ts +3 -0
  123. package/dist/daemon/routes/watcher.d.ts.map +1 -0
  124. package/dist/daemon/routes/watcher.js +28 -0
  125. package/dist/daemon/routes/watcher.js.map +1 -0
  126. package/dist/daemon/service.d.ts +5 -23
  127. package/dist/daemon/service.d.ts.map +1 -1
  128. package/dist/daemon/service.js +201 -933
  129. package/dist/daemon/service.js.map +1 -1
  130. package/dist/daemon/session-processor.d.ts +4 -8
  131. package/dist/daemon/session-processor.d.ts.map +1 -1
  132. package/dist/daemon/session-processor.js +39 -38
  133. package/dist/daemon/session-processor.js.map +1 -1
  134. package/dist/lib/ai-readiness.d.ts.map +1 -1
  135. package/dist/lib/ai-readiness.js +33 -8
  136. package/dist/lib/ai-readiness.js.map +1 -1
  137. package/dist/lib/analyze-state.d.ts.map +1 -1
  138. package/dist/lib/analyze-state.js +25 -3
  139. package/dist/lib/analyze-state.js.map +1 -1
  140. package/dist/lib/auto-memory/consolidation.d.ts +41 -0
  141. package/dist/lib/auto-memory/consolidation.d.ts.map +1 -0
  142. package/dist/lib/auto-memory/consolidation.js +151 -0
  143. package/dist/lib/auto-memory/consolidation.js.map +1 -0
  144. package/dist/lib/bpe.d.ts.map +1 -1
  145. package/dist/lib/bpe.js +9 -10
  146. package/dist/lib/bpe.js.map +1 -1
  147. package/dist/lib/brain-export.d.ts +65 -0
  148. package/dist/lib/brain-export.d.ts.map +1 -0
  149. package/dist/lib/brain-export.js +413 -0
  150. package/dist/lib/brain-export.js.map +1 -0
  151. package/dist/lib/checkpoint.d.ts.map +1 -1
  152. package/dist/lib/checkpoint.js +22 -6
  153. package/dist/lib/checkpoint.js.map +1 -1
  154. package/dist/lib/chunker.d.ts.map +1 -1
  155. package/dist/lib/chunker.js +6 -1
  156. package/dist/lib/chunker.js.map +1 -1
  157. package/dist/lib/claude-ws-transport.d.ts.map +1 -1
  158. package/dist/lib/claude-ws-transport.js +12 -4
  159. package/dist/lib/claude-ws-transport.js.map +1 -1
  160. package/dist/lib/command-safety.d.ts +64 -0
  161. package/dist/lib/command-safety.d.ts.map +1 -0
  162. package/dist/lib/command-safety.js +625 -0
  163. package/dist/lib/command-safety.js.map +1 -0
  164. package/dist/lib/compact-briefing.d.ts.map +1 -1
  165. package/dist/lib/compact-briefing.js +10 -13
  166. package/dist/lib/compact-briefing.js.map +1 -1
  167. package/dist/lib/config-defaults.d.ts.map +1 -1
  168. package/dist/lib/config-defaults.js +3 -0
  169. package/dist/lib/config-defaults.js.map +1 -1
  170. package/dist/lib/config-display.d.ts +4 -0
  171. package/dist/lib/config-display.d.ts.map +1 -1
  172. package/dist/lib/config-display.js +6 -1
  173. package/dist/lib/config-display.js.map +1 -1
  174. package/dist/lib/config-types.d.ts +156 -0
  175. package/dist/lib/config-types.d.ts.map +1 -1
  176. package/dist/lib/config-validation.d.ts.map +1 -1
  177. package/dist/lib/config-validation.js +5 -0
  178. package/dist/lib/config-validation.js.map +1 -1
  179. package/dist/lib/config.d.ts.map +1 -1
  180. package/dist/lib/config.js +92 -9
  181. package/dist/lib/config.js.map +1 -1
  182. package/dist/lib/consolidate.d.ts.map +1 -1
  183. package/dist/lib/consolidate.js +66 -47
  184. package/dist/lib/consolidate.js.map +1 -1
  185. package/dist/lib/content-sanitizer.d.ts +29 -0
  186. package/dist/lib/content-sanitizer.d.ts.map +1 -0
  187. package/dist/lib/content-sanitizer.js +72 -0
  188. package/dist/lib/content-sanitizer.js.map +1 -0
  189. package/dist/lib/cross-repo.d.ts +44 -0
  190. package/dist/lib/cross-repo.d.ts.map +1 -0
  191. package/dist/lib/cross-repo.js +108 -0
  192. package/dist/lib/cross-repo.js.map +1 -0
  193. package/dist/lib/daemon-port.d.ts +12 -0
  194. package/dist/lib/daemon-port.d.ts.map +1 -0
  195. package/dist/lib/daemon-port.js +20 -0
  196. package/dist/lib/daemon-port.js.map +1 -0
  197. package/dist/lib/db/auto-memory.d.ts +40 -0
  198. package/dist/lib/db/auto-memory.d.ts.map +1 -0
  199. package/dist/lib/db/auto-memory.js +74 -0
  200. package/dist/lib/db/auto-memory.js.map +1 -0
  201. package/dist/lib/db/bm25-indexes.d.ts.map +1 -1
  202. package/dist/lib/db/bm25-indexes.js +16 -4
  203. package/dist/lib/db/bm25-indexes.js.map +1 -1
  204. package/dist/lib/db/documents.d.ts.map +1 -1
  205. package/dist/lib/db/documents.js +4 -1
  206. package/dist/lib/db/documents.js.map +1 -1
  207. package/dist/lib/db/global-memories.d.ts +2 -10
  208. package/dist/lib/db/global-memories.d.ts.map +1 -1
  209. package/dist/lib/db/global-memories.js +13 -6
  210. package/dist/lib/db/global-memories.js.map +1 -1
  211. package/dist/lib/db/graph.d.ts +5 -1
  212. package/dist/lib/db/graph.d.ts.map +1 -1
  213. package/dist/lib/db/graph.js +38 -8
  214. package/dist/lib/db/graph.js.map +1 -1
  215. package/dist/lib/db/hybrid-search.d.ts +4 -2
  216. package/dist/lib/db/hybrid-search.d.ts.map +1 -1
  217. package/dist/lib/db/hybrid-search.js +29 -11
  218. package/dist/lib/db/hybrid-search.js.map +1 -1
  219. package/dist/lib/db/index.d.ts +6 -1
  220. package/dist/lib/db/index.d.ts.map +1 -1
  221. package/dist/lib/db/index.js +5 -1
  222. package/dist/lib/db/index.js.map +1 -1
  223. package/dist/lib/db/memories.d.ts +19 -14
  224. package/dist/lib/db/memories.d.ts.map +1 -1
  225. package/dist/lib/db/memories.js +100 -37
  226. package/dist/lib/db/memories.js.map +1 -1
  227. package/dist/lib/db/parse-helpers.d.ts +14 -0
  228. package/dist/lib/db/parse-helpers.d.ts.map +1 -0
  229. package/dist/lib/db/parse-helpers.js +59 -0
  230. package/dist/lib/db/parse-helpers.js.map +1 -0
  231. package/dist/lib/db/recall-events.d.ts +49 -0
  232. package/dist/lib/db/recall-events.d.ts.map +1 -0
  233. package/dist/lib/db/recall-events.js +196 -0
  234. package/dist/lib/db/recall-events.js.map +1 -0
  235. package/dist/lib/db/retention.d.ts +4 -3
  236. package/dist/lib/db/retention.d.ts.map +1 -1
  237. package/dist/lib/db/retention.js +12 -1
  238. package/dist/lib/db/retention.js.map +1 -1
  239. package/dist/lib/db/schema.d.ts +2 -0
  240. package/dist/lib/db/schema.d.ts.map +1 -1
  241. package/dist/lib/db/schema.js +140 -80
  242. package/dist/lib/db/schema.js.map +1 -1
  243. package/dist/lib/db/skills.d.ts.map +1 -1
  244. package/dist/lib/db/skills.js +10 -6
  245. package/dist/lib/db/skills.js.map +1 -1
  246. package/dist/lib/diff-brain.d.ts +24 -0
  247. package/dist/lib/diff-brain.d.ts.map +1 -0
  248. package/dist/lib/diff-brain.js +114 -0
  249. package/dist/lib/diff-brain.js.map +1 -0
  250. package/dist/lib/diff-parser.d.ts +74 -0
  251. package/dist/lib/diff-parser.d.ts.map +1 -0
  252. package/dist/lib/diff-parser.js +200 -0
  253. package/dist/lib/diff-parser.js.map +1 -0
  254. package/dist/lib/embedding-pool.d.ts.map +1 -1
  255. package/dist/lib/embedding-pool.js +5 -1
  256. package/dist/lib/embedding-pool.js.map +1 -1
  257. package/dist/lib/embeddings.d.ts +12 -0
  258. package/dist/lib/embeddings.d.ts.map +1 -1
  259. package/dist/lib/embeddings.js +77 -19
  260. package/dist/lib/embeddings.js.map +1 -1
  261. package/dist/lib/errors.d.ts +2 -0
  262. package/dist/lib/errors.d.ts.map +1 -1
  263. package/dist/lib/errors.js +4 -0
  264. package/dist/lib/errors.js.map +1 -1
  265. package/dist/lib/fault-logger.d.ts.map +1 -1
  266. package/dist/lib/fault-logger.js +22 -7
  267. package/dist/lib/fault-logger.js.map +1 -1
  268. package/dist/lib/git/co-change.d.ts +39 -0
  269. package/dist/lib/git/co-change.d.ts.map +1 -0
  270. package/dist/lib/git/co-change.js +139 -0
  271. package/dist/lib/git/co-change.js.map +1 -0
  272. package/dist/lib/graph/bridge-edges.d.ts +93 -0
  273. package/dist/lib/graph/bridge-edges.d.ts.map +1 -0
  274. package/dist/lib/graph/bridge-edges.js +276 -0
  275. package/dist/lib/graph/bridge-edges.js.map +1 -0
  276. package/dist/lib/graph/centrality.d.ts +11 -0
  277. package/dist/lib/graph/centrality.d.ts.map +1 -1
  278. package/dist/lib/graph/centrality.js +51 -3
  279. package/dist/lib/graph/centrality.js.map +1 -1
  280. package/dist/lib/graph/cleanup.d.ts.map +1 -1
  281. package/dist/lib/graph/cleanup.js +2 -1
  282. package/dist/lib/graph/cleanup.js.map +1 -1
  283. package/dist/lib/graph/community-detection.d.ts +17 -2
  284. package/dist/lib/graph/community-detection.d.ts.map +1 -1
  285. package/dist/lib/graph/community-detection.js +147 -48
  286. package/dist/lib/graph/community-detection.js.map +1 -1
  287. package/dist/lib/graph/community-summaries.d.ts +26 -0
  288. package/dist/lib/graph/community-summaries.d.ts.map +1 -0
  289. package/dist/lib/graph/community-summaries.js +130 -0
  290. package/dist/lib/graph/community-summaries.js.map +1 -0
  291. package/dist/lib/graph/contextual-proximity.d.ts.map +1 -1
  292. package/dist/lib/graph/contextual-proximity.js +11 -4
  293. package/dist/lib/graph/contextual-proximity.js.map +1 -1
  294. package/dist/lib/graph/graphology-bridge.d.ts +101 -0
  295. package/dist/lib/graph/graphology-bridge.d.ts.map +1 -0
  296. package/dist/lib/graph/graphology-bridge.js +488 -0
  297. package/dist/lib/graph/graphology-bridge.js.map +1 -0
  298. package/dist/lib/graph/llm-relations.d.ts.map +1 -1
  299. package/dist/lib/graph/llm-relations.js +27 -5
  300. package/dist/lib/graph/llm-relations.js.map +1 -1
  301. package/dist/lib/graph-export.d.ts.map +1 -1
  302. package/dist/lib/graph-export.js +2 -2
  303. package/dist/lib/graph-export.js.map +1 -1
  304. package/dist/lib/graph-scheduler.d.ts +0 -5
  305. package/dist/lib/graph-scheduler.d.ts.map +1 -1
  306. package/dist/lib/graph-scheduler.js +5 -1
  307. package/dist/lib/graph-scheduler.js.map +1 -1
  308. package/dist/lib/guardrails.d.ts +50 -0
  309. package/dist/lib/guardrails.d.ts.map +1 -0
  310. package/dist/lib/guardrails.js +502 -0
  311. package/dist/lib/guardrails.js.map +1 -0
  312. package/dist/lib/hook-rules.d.ts +1 -1
  313. package/dist/lib/hook-rules.d.ts.map +1 -1
  314. package/dist/lib/hook-rules.js +8 -2
  315. package/dist/lib/hook-rules.js.map +1 -1
  316. package/dist/lib/ifc/file-labels.d.ts +35 -0
  317. package/dist/lib/ifc/file-labels.d.ts.map +1 -0
  318. package/dist/lib/ifc/file-labels.js +208 -0
  319. package/dist/lib/ifc/file-labels.js.map +1 -0
  320. package/dist/lib/ifc/label.d.ts +38 -0
  321. package/dist/lib/ifc/label.d.ts.map +1 -0
  322. package/dist/lib/ifc/label.js +80 -0
  323. package/dist/lib/ifc/label.js.map +1 -0
  324. package/dist/lib/ifc/session-ifc.d.ts +92 -0
  325. package/dist/lib/ifc/session-ifc.d.ts.map +1 -0
  326. package/dist/lib/ifc/session-ifc.js +222 -0
  327. package/dist/lib/ifc/session-ifc.js.map +1 -0
  328. package/dist/lib/indexer.js +2 -2
  329. package/dist/lib/indexer.js.map +1 -1
  330. package/dist/lib/injection-detector.d.ts +83 -0
  331. package/dist/lib/injection-detector.d.ts.map +1 -0
  332. package/dist/lib/injection-detector.js +586 -0
  333. package/dist/lib/injection-detector.js.map +1 -0
  334. package/dist/lib/injection-semantic.d.ts +31 -0
  335. package/dist/lib/injection-semantic.d.ts.map +1 -0
  336. package/dist/lib/injection-semantic.js +230 -0
  337. package/dist/lib/injection-semantic.js.map +1 -0
  338. package/dist/lib/llm.d.ts.map +1 -1
  339. package/dist/lib/llm.js +19 -4
  340. package/dist/lib/llm.js.map +1 -1
  341. package/dist/lib/lock.d.ts.map +1 -1
  342. package/dist/lib/lock.js +24 -3
  343. package/dist/lib/lock.js.map +1 -1
  344. package/dist/lib/md-fetch.d.ts.map +1 -1
  345. package/dist/lib/md-fetch.js +9 -2
  346. package/dist/lib/md-fetch.js.map +1 -1
  347. package/dist/lib/observability.d.ts +75 -0
  348. package/dist/lib/observability.d.ts.map +1 -0
  349. package/dist/lib/observability.js +201 -0
  350. package/dist/lib/observability.js.map +1 -0
  351. package/dist/lib/ort-session.d.ts +26 -0
  352. package/dist/lib/ort-session.d.ts.map +1 -1
  353. package/dist/lib/ort-session.js +107 -3
  354. package/dist/lib/ort-session.js.map +1 -1
  355. package/dist/lib/prd/codebase-context.d.ts.map +1 -1
  356. package/dist/lib/prd/codebase-context.js +9 -2
  357. package/dist/lib/prd/codebase-context.js.map +1 -1
  358. package/dist/lib/prd/context.d.ts.map +1 -1
  359. package/dist/lib/prd/context.js +11 -3
  360. package/dist/lib/prd/context.js.map +1 -1
  361. package/dist/lib/prd/export.js +1 -1
  362. package/dist/lib/prd/export.js.map +1 -1
  363. package/dist/lib/prd/generate.d.ts.map +1 -1
  364. package/dist/lib/prd/generate.js +17 -4
  365. package/dist/lib/prd/generate.js.map +1 -1
  366. package/dist/lib/prd/parse.d.ts.map +1 -1
  367. package/dist/lib/prd/parse.js +6 -1
  368. package/dist/lib/prd/parse.js.map +1 -1
  369. package/dist/lib/prd/runner.d.ts +1 -2
  370. package/dist/lib/prd/runner.d.ts.map +1 -1
  371. package/dist/lib/prd/runner.js +43 -32
  372. package/dist/lib/prd/runner.js.map +1 -1
  373. package/dist/lib/prd/worktree.d.ts +1 -2
  374. package/dist/lib/prd/worktree.d.ts.map +1 -1
  375. package/dist/lib/prd/worktree.js +62 -70
  376. package/dist/lib/prd/worktree.js.map +1 -1
  377. package/dist/lib/precompute-context.d.ts.map +1 -1
  378. package/dist/lib/precompute-context.js +15 -34
  379. package/dist/lib/precompute-context.js.map +1 -1
  380. package/dist/lib/pricing.d.ts.map +1 -1
  381. package/dist/lib/pricing.js +5 -1
  382. package/dist/lib/pricing.js.map +1 -1
  383. package/dist/lib/process-registry.js +3 -3
  384. package/dist/lib/process-registry.js.map +1 -1
  385. package/dist/lib/public-api.d.ts +41 -1
  386. package/dist/lib/public-api.d.ts.map +1 -1
  387. package/dist/lib/public-api.js +38 -0
  388. package/dist/lib/public-api.js.map +1 -1
  389. package/dist/lib/quality.d.ts.map +1 -1
  390. package/dist/lib/quality.js +15 -6
  391. package/dist/lib/quality.js.map +1 -1
  392. package/dist/lib/query-expansion.d.ts +32 -0
  393. package/dist/lib/query-expansion.d.ts.map +1 -1
  394. package/dist/lib/query-expansion.js +62 -1
  395. package/dist/lib/query-expansion.js.map +1 -1
  396. package/dist/lib/reference-embeddings.d.ts.map +1 -1
  397. package/dist/lib/reference-embeddings.js +17 -4
  398. package/dist/lib/reference-embeddings.js.map +1 -1
  399. package/dist/lib/reflection-synthesizer.d.ts.map +1 -1
  400. package/dist/lib/reflection-synthesizer.js +7 -1
  401. package/dist/lib/reflection-synthesizer.js.map +1 -1
  402. package/dist/lib/reranker.d.ts +41 -0
  403. package/dist/lib/reranker.d.ts.map +1 -0
  404. package/dist/lib/reranker.js +294 -0
  405. package/dist/lib/reranker.js.map +1 -0
  406. package/dist/lib/retrieval-feedback.d.ts +100 -0
  407. package/dist/lib/retrieval-feedback.d.ts.map +1 -0
  408. package/dist/lib/retrieval-feedback.js +174 -0
  409. package/dist/lib/retrieval-feedback.js.map +1 -0
  410. package/dist/lib/review/context-pack.d.ts +58 -0
  411. package/dist/lib/review/context-pack.d.ts.map +1 -0
  412. package/dist/lib/review/context-pack.js +300 -0
  413. package/dist/lib/review/context-pack.js.map +1 -0
  414. package/dist/lib/search/hierarchical-summaries.d.ts +65 -0
  415. package/dist/lib/search/hierarchical-summaries.d.ts.map +1 -0
  416. package/dist/lib/search/hierarchical-summaries.js +423 -0
  417. package/dist/lib/search/hierarchical-summaries.js.map +1 -0
  418. package/dist/lib/search/hyde.d.ts +27 -0
  419. package/dist/lib/search/hyde.d.ts.map +1 -0
  420. package/dist/lib/search/hyde.js +141 -0
  421. package/dist/lib/search/hyde.js.map +1 -0
  422. package/dist/lib/search/late-chunking.d.ts +53 -0
  423. package/dist/lib/search/late-chunking.d.ts.map +1 -0
  424. package/dist/lib/search/late-chunking.js +230 -0
  425. package/dist/lib/search/late-chunking.js.map +1 -0
  426. package/dist/lib/search/ppr-retrieval.d.ts +49 -0
  427. package/dist/lib/search/ppr-retrieval.d.ts.map +1 -0
  428. package/dist/lib/search/ppr-retrieval.js +135 -0
  429. package/dist/lib/search/ppr-retrieval.js.map +1 -0
  430. package/dist/lib/search/repo-map.d.ts +43 -0
  431. package/dist/lib/search/repo-map.d.ts.map +1 -0
  432. package/dist/lib/search/repo-map.js +165 -0
  433. package/dist/lib/search/repo-map.js.map +1 -0
  434. package/dist/lib/session-analyzer.d.ts +90 -0
  435. package/dist/lib/session-analyzer.d.ts.map +1 -0
  436. package/dist/lib/session-analyzer.js +467 -0
  437. package/dist/lib/session-analyzer.js.map +1 -0
  438. package/dist/lib/session-observations.d.ts.map +1 -1
  439. package/dist/lib/session-observations.js +13 -3
  440. package/dist/lib/session-observations.js.map +1 -1
  441. package/dist/lib/session-summary.d.ts.map +1 -1
  442. package/dist/lib/session-summary.js +57 -50
  443. package/dist/lib/session-summary.js.map +1 -1
  444. package/dist/lib/session-surgeon.d.ts +53 -0
  445. package/dist/lib/session-surgeon.d.ts.map +1 -0
  446. package/dist/lib/session-surgeon.js +501 -0
  447. package/dist/lib/session-surgeon.js.map +1 -0
  448. package/dist/lib/similarity-utils.d.ts +26 -0
  449. package/dist/lib/similarity-utils.d.ts.map +1 -0
  450. package/dist/lib/similarity-utils.js +66 -0
  451. package/dist/lib/similarity-utils.js.map +1 -0
  452. package/dist/lib/skills.d.ts.map +1 -1
  453. package/dist/lib/skills.js +11 -11
  454. package/dist/lib/skills.js.map +1 -1
  455. package/dist/lib/storage/backends/interface.d.ts +13 -3
  456. package/dist/lib/storage/backends/interface.d.ts.map +1 -1
  457. package/dist/lib/storage/backends/postgresql.d.ts +52 -3
  458. package/dist/lib/storage/backends/postgresql.d.ts.map +1 -1
  459. package/dist/lib/storage/backends/postgresql.js +694 -49
  460. package/dist/lib/storage/backends/postgresql.js.map +1 -1
  461. package/dist/lib/storage/benchmark.js +2 -2
  462. package/dist/lib/storage/benchmark.js.map +1 -1
  463. package/dist/lib/storage/dispatcher/base.d.ts +114 -0
  464. package/dist/lib/storage/dispatcher/base.d.ts.map +1 -0
  465. package/dist/lib/storage/dispatcher/base.js +160 -0
  466. package/dist/lib/storage/dispatcher/base.js.map +1 -0
  467. package/dist/lib/storage/dispatcher/documents.d.ts +25 -0
  468. package/dist/lib/storage/dispatcher/documents.d.ts.map +1 -0
  469. package/dist/lib/storage/dispatcher/documents.js +194 -0
  470. package/dist/lib/storage/dispatcher/documents.js.map +1 -0
  471. package/dist/lib/storage/dispatcher/embeddings.d.ts +34 -0
  472. package/dist/lib/storage/dispatcher/embeddings.d.ts.map +1 -0
  473. package/dist/lib/storage/dispatcher/embeddings.js +144 -0
  474. package/dist/lib/storage/dispatcher/embeddings.js.map +1 -0
  475. package/dist/lib/storage/dispatcher/export-import.d.ts +139 -0
  476. package/dist/lib/storage/dispatcher/export-import.d.ts.map +1 -0
  477. package/dist/lib/storage/dispatcher/export-import.js +191 -0
  478. package/dist/lib/storage/dispatcher/export-import.js.map +1 -0
  479. package/dist/lib/storage/dispatcher/file-hashes.d.ts +13 -0
  480. package/dist/lib/storage/dispatcher/file-hashes.d.ts.map +1 -0
  481. package/dist/lib/storage/dispatcher/file-hashes.js +36 -0
  482. package/dist/lib/storage/dispatcher/file-hashes.js.map +1 -0
  483. package/dist/lib/storage/dispatcher/global-memories.d.ts +28 -0
  484. package/dist/lib/storage/dispatcher/global-memories.d.ts.map +1 -0
  485. package/dist/lib/storage/dispatcher/global-memories.js +151 -0
  486. package/dist/lib/storage/dispatcher/global-memories.js.map +1 -0
  487. package/dist/lib/storage/dispatcher/graph.d.ts +32 -0
  488. package/dist/lib/storage/dispatcher/graph.d.ts.map +1 -0
  489. package/dist/lib/storage/dispatcher/graph.js +146 -0
  490. package/dist/lib/storage/dispatcher/graph.js.map +1 -0
  491. package/dist/lib/storage/dispatcher/index.d.ts +34 -0
  492. package/dist/lib/storage/dispatcher/index.d.ts.map +1 -0
  493. package/dist/lib/storage/dispatcher/index.js +139 -0
  494. package/dist/lib/storage/dispatcher/index.js.map +1 -0
  495. package/dist/lib/storage/dispatcher/memories.d.ts +65 -0
  496. package/dist/lib/storage/dispatcher/memories.d.ts.map +1 -0
  497. package/dist/lib/storage/dispatcher/memories.js +466 -0
  498. package/dist/lib/storage/dispatcher/memories.js.map +1 -0
  499. package/dist/lib/storage/dispatcher/mixin-helper.d.ts +6 -0
  500. package/dist/lib/storage/dispatcher/mixin-helper.d.ts.map +1 -0
  501. package/dist/lib/storage/dispatcher/mixin-helper.js +10 -0
  502. package/dist/lib/storage/dispatcher/mixin-helper.js.map +1 -0
  503. package/dist/lib/storage/dispatcher/retention.d.ts +20 -0
  504. package/dist/lib/storage/dispatcher/retention.d.ts.map +1 -0
  505. package/dist/lib/storage/dispatcher/retention.js +123 -0
  506. package/dist/lib/storage/dispatcher/retention.js.map +1 -0
  507. package/dist/lib/storage/dispatcher/search.d.ts +34 -0
  508. package/dist/lib/storage/dispatcher/search.d.ts.map +1 -0
  509. package/dist/lib/storage/dispatcher/search.js +222 -0
  510. package/dist/lib/storage/dispatcher/search.js.map +1 -0
  511. package/dist/lib/storage/dispatcher/skills.d.ts +53 -0
  512. package/dist/lib/storage/dispatcher/skills.d.ts.map +1 -0
  513. package/dist/lib/storage/dispatcher/skills.js +98 -0
  514. package/dist/lib/storage/dispatcher/skills.js.map +1 -0
  515. package/dist/lib/storage/dispatcher/token-stats.d.ts +23 -0
  516. package/dist/lib/storage/dispatcher/token-stats.d.ts.map +1 -0
  517. package/dist/lib/storage/dispatcher/token-stats.js +92 -0
  518. package/dist/lib/storage/dispatcher/token-stats.js.map +1 -0
  519. package/dist/lib/storage/dispatcher/web-search.d.ts +10 -0
  520. package/dist/lib/storage/dispatcher/web-search.d.ts.map +1 -0
  521. package/dist/lib/storage/dispatcher/web-search.js +39 -0
  522. package/dist/lib/storage/dispatcher/web-search.js.map +1 -0
  523. package/dist/lib/storage/dispatcher-export.d.ts.map +1 -1
  524. package/dist/lib/storage/dispatcher-export.js +48 -39
  525. package/dist/lib/storage/dispatcher-export.js.map +1 -1
  526. package/dist/lib/storage/dispatcher.d.ts +1 -468
  527. package/dist/lib/storage/dispatcher.d.ts.map +1 -1
  528. package/dist/lib/storage/dispatcher.js +1 -1931
  529. package/dist/lib/storage/dispatcher.js.map +1 -1
  530. package/dist/lib/storage/index.d.ts +20 -5
  531. package/dist/lib/storage/index.d.ts.map +1 -1
  532. package/dist/lib/storage/index.js +36 -7
  533. package/dist/lib/storage/index.js.map +1 -1
  534. package/dist/lib/storage/migration/export-import.d.ts.map +1 -1
  535. package/dist/lib/storage/migration/export-import.js +9 -2
  536. package/dist/lib/storage/migration/export-import.js.map +1 -1
  537. package/dist/lib/storage/types.d.ts +152 -10
  538. package/dist/lib/storage/types.d.ts.map +1 -1
  539. package/dist/lib/storage/types.js +13 -0
  540. package/dist/lib/storage/types.js.map +1 -1
  541. package/dist/lib/storage/vector/interface.d.ts +4 -0
  542. package/dist/lib/storage/vector/interface.d.ts.map +1 -1
  543. package/dist/lib/storage/vector/qdrant.d.ts +13 -2
  544. package/dist/lib/storage/vector/qdrant.d.ts.map +1 -1
  545. package/dist/lib/storage/vector/qdrant.js +147 -61
  546. package/dist/lib/storage/vector/qdrant.js.map +1 -1
  547. package/dist/lib/token-budget.d.ts.map +1 -1
  548. package/dist/lib/token-budget.js +9 -2
  549. package/dist/lib/token-budget.js.map +1 -1
  550. package/dist/lib/transcript-utils.d.ts +60 -0
  551. package/dist/lib/transcript-utils.d.ts.map +1 -0
  552. package/dist/lib/transcript-utils.js +69 -0
  553. package/dist/lib/transcript-utils.js.map +1 -0
  554. package/dist/lib/tree-sitter/extractor.d.ts +1 -1
  555. package/dist/lib/tree-sitter/extractor.d.ts.map +1 -1
  556. package/dist/lib/tree-sitter/extractor.js +34 -9
  557. package/dist/lib/tree-sitter/extractor.js.map +1 -1
  558. package/dist/lib/tree-sitter/parser.d.ts.map +1 -1
  559. package/dist/lib/tree-sitter/parser.js +45 -11
  560. package/dist/lib/tree-sitter/parser.js.map +1 -1
  561. package/dist/lib/tree-sitter/public.d.ts +12 -0
  562. package/dist/lib/tree-sitter/public.d.ts.map +1 -1
  563. package/dist/lib/tree-sitter/public.js +33 -1
  564. package/dist/lib/tree-sitter/public.js.map +1 -1
  565. package/dist/lib/tree-sitter/queries.d.ts.map +1 -1
  566. package/dist/lib/tree-sitter/queries.js +8 -0
  567. package/dist/lib/tree-sitter/queries.js.map +1 -1
  568. package/dist/lib/version-check.d.ts +29 -0
  569. package/dist/lib/version-check.d.ts.map +1 -0
  570. package/dist/lib/version-check.js +187 -0
  571. package/dist/lib/version-check.js.map +1 -0
  572. package/dist/lib/working-memory-pipeline.d.ts.map +1 -1
  573. package/dist/lib/working-memory-pipeline.js +12 -3
  574. package/dist/lib/working-memory-pipeline.js.map +1 -1
  575. package/dist/lib/worktree-detect.d.ts +43 -0
  576. package/dist/lib/worktree-detect.d.ts.map +1 -0
  577. package/dist/lib/worktree-detect.js +154 -0
  578. package/dist/lib/worktree-detect.js.map +1 -0
  579. package/dist/lsp/client.d.ts +96 -0
  580. package/dist/lsp/client.d.ts.map +1 -0
  581. package/dist/lsp/client.js +435 -0
  582. package/dist/lsp/client.js.map +1 -0
  583. package/dist/lsp/installer.d.ts +39 -0
  584. package/dist/lsp/installer.d.ts.map +1 -0
  585. package/dist/lsp/installer.js +275 -0
  586. package/dist/lsp/installer.js.map +1 -0
  587. package/dist/lsp/manager.d.ts +62 -0
  588. package/dist/lsp/manager.d.ts.map +1 -0
  589. package/dist/lsp/manager.js +234 -0
  590. package/dist/lsp/manager.js.map +1 -0
  591. package/dist/lsp/servers.d.ts +52 -0
  592. package/dist/lsp/servers.d.ts.map +1 -0
  593. package/dist/lsp/servers.js +162 -0
  594. package/dist/lsp/servers.js.map +1 -0
  595. package/dist/mcp/helpers.d.ts.map +1 -1
  596. package/dist/mcp/helpers.js +8 -2
  597. package/dist/mcp/helpers.js.map +1 -1
  598. package/dist/mcp/profile.js +1 -1
  599. package/dist/mcp/server.d.ts +3 -2
  600. package/dist/mcp/server.d.ts.map +1 -1
  601. package/dist/mcp/server.js +19 -7
  602. package/dist/mcp/server.js.map +1 -1
  603. package/dist/mcp/tools/config.d.ts.map +1 -1
  604. package/dist/mcp/tools/config.js +28 -118
  605. package/dist/mcp/tools/config.js.map +1 -1
  606. package/dist/mcp/tools/dead-end.d.ts.map +1 -1
  607. package/dist/mcp/tools/dead-end.js +4 -3
  608. package/dist/mcp/tools/dead-end.js.map +1 -1
  609. package/dist/mcp/tools/debug.d.ts.map +1 -1
  610. package/dist/mcp/tools/debug.js +27 -112
  611. package/dist/mcp/tools/debug.js.map +1 -1
  612. package/dist/mcp/tools/graph.d.ts.map +1 -1
  613. package/dist/mcp/tools/graph.js +164 -176
  614. package/dist/mcp/tools/graph.js.map +1 -1
  615. package/dist/mcp/tools/indexing.d.ts +1 -1
  616. package/dist/mcp/tools/indexing.d.ts.map +1 -1
  617. package/dist/mcp/tools/indexing.js +63 -164
  618. package/dist/mcp/tools/indexing.js.map +1 -1
  619. package/dist/mcp/tools/memory/forget.d.ts +3 -0
  620. package/dist/mcp/tools/memory/forget.d.ts.map +1 -0
  621. package/dist/mcp/tools/memory/forget.js +175 -0
  622. package/dist/mcp/tools/memory/forget.js.map +1 -0
  623. package/dist/mcp/tools/memory/memory-helpers.d.ts +45 -0
  624. package/dist/mcp/tools/memory/memory-helpers.d.ts.map +1 -0
  625. package/dist/mcp/tools/memory/memory-helpers.js +291 -0
  626. package/dist/mcp/tools/memory/memory-helpers.js.map +1 -0
  627. package/dist/mcp/tools/memory/recall.d.ts +3 -0
  628. package/dist/mcp/tools/memory/recall.d.ts.map +1 -0
  629. package/dist/mcp/tools/memory/recall.js +495 -0
  630. package/dist/mcp/tools/memory/recall.js.map +1 -0
  631. package/dist/mcp/tools/memory/remember.d.ts +3 -0
  632. package/dist/mcp/tools/memory/remember.d.ts.map +1 -0
  633. package/dist/mcp/tools/memory/remember.js +256 -0
  634. package/dist/mcp/tools/memory/remember.js.map +1 -0
  635. package/dist/mcp/tools/memory/temporal-query.d.ts +8 -0
  636. package/dist/mcp/tools/memory/temporal-query.d.ts.map +1 -0
  637. package/dist/mcp/tools/memory/temporal-query.js +68 -0
  638. package/dist/mcp/tools/memory/temporal-query.js.map +1 -0
  639. package/dist/mcp/tools/memory.d.ts +0 -11
  640. package/dist/mcp/tools/memory.d.ts.map +1 -1
  641. package/dist/mcp/tools/memory.js +6 -1228
  642. package/dist/mcp/tools/memory.js.map +1 -1
  643. package/dist/mcp/tools/prd.d.ts.map +1 -1
  644. package/dist/mcp/tools/prd.js +19 -70
  645. package/dist/mcp/tools/prd.js.map +1 -1
  646. package/dist/mcp/tools/review.d.ts +8 -0
  647. package/dist/mcp/tools/review.d.ts.map +1 -0
  648. package/dist/mcp/tools/review.js +133 -0
  649. package/dist/mcp/tools/review.js.map +1 -0
  650. package/dist/mcp/tools/search.d.ts.map +1 -1
  651. package/dist/mcp/tools/search.js +79 -8
  652. package/dist/mcp/tools/search.js.map +1 -1
  653. package/dist/mcp/tools/status.d.ts.map +1 -1
  654. package/dist/mcp/tools/status.js +50 -75
  655. package/dist/mcp/tools/status.js.map +1 -1
  656. package/dist/mcp/tools/web-fetch.d.ts.map +1 -1
  657. package/dist/mcp/tools/web-fetch.js +5 -1
  658. package/dist/mcp/tools/web-fetch.js.map +1 -1
  659. package/dist/mcp/tools/web-search.d.ts.map +1 -1
  660. package/dist/mcp/tools/web-search.js +25 -103
  661. package/dist/mcp/tools/web-search.js.map +1 -1
  662. package/dist/prompts/guardrails.d.ts +14 -0
  663. package/dist/prompts/guardrails.d.ts.map +1 -0
  664. package/dist/prompts/guardrails.js +115 -0
  665. package/dist/prompts/guardrails.js.map +1 -0
  666. package/dist/prompts/index.d.ts +2 -1
  667. package/dist/prompts/index.d.ts.map +1 -1
  668. package/dist/prompts/index.js +3 -1
  669. package/dist/prompts/index.js.map +1 -1
  670. package/dist/prompts/prd.d.ts +0 -2
  671. package/dist/prompts/prd.d.ts.map +1 -1
  672. package/dist/prompts/prd.js +0 -2
  673. package/dist/prompts/prd.js.map +1 -1
  674. package/hooks/core/__tests__/adapter.test.cjs +340 -0
  675. package/hooks/core/adapter.cjs +463 -0
  676. package/hooks/core/config.cjs +83 -0
  677. package/hooks/core/daemon-boot.cjs +140 -0
  678. package/hooks/core/log.cjs +41 -0
  679. package/hooks/core/worktree.cjs +119 -0
  680. package/hooks/succ-post-tool.cjs +198 -134
  681. package/hooks/succ-pre-compact.cjs +262 -0
  682. package/hooks/succ-pre-tool.cjs +526 -182
  683. package/hooks/succ-session-end.cjs +40 -64
  684. package/hooks/succ-session-start.cjs +528 -427
  685. package/hooks/succ-stop-reflection.cjs +36 -62
  686. package/hooks/succ-user-prompt.cjs +137 -180
  687. package/package.json +17 -6
@@ -16,52 +16,36 @@
16
16
  import http from 'http';
17
17
  import fs from 'fs';
18
18
  import path from 'path';
19
- import { createSessionManager, createIdleWatcher } from './sessions.js';
19
+ import { createIdleWatcher, createSessionManager } from './sessions.js';
20
20
  import { logError, logWarn } from '../lib/fault-logger.js';
21
21
  import { processRegistry } from '../lib/process-registry.js';
22
- import { processSessionEnd } from './session-processor.js';
23
- import { ValidationError, NotFoundError, NetworkError } from '../lib/errors.js';
24
- import { startWatcher, stopWatcher, getWatcherStatus, indexFileOnDemand } from './watcher.js';
25
- import { startAnalyzer, stopAnalyzer, getAnalyzerStatus, triggerAnalysis } from './analyzer.js';
26
- import { getProjectRoot, getSuccDir, getIdleReflectionConfig, getIdleWatcherConfig, getConfig, getObserverConfig, } from '../lib/config.js';
27
- import { hybridSearchDocs, hybridSearchCode, hybridSearchMemories, saveMemory, closeDb, getStats, getMemoryStats, incrementMemoryAccessBatch, getRecentMemories, getPinnedMemories, setMemoryInvariant, getMemoriesByTag,
28
- // Global memory
29
- saveGlobalMemory, closeGlobalDb,
30
- // Dispatcher lifecycle
31
- initStorageDispatcher, closeStorageDispatcher, getStorageDispatcher, } from '../lib/storage/index.js';
32
- import { getEmbedding, cleanupEmbeddings } from '../lib/embeddings.js';
33
- import { scoreMemory, passesQualityThreshold, cleanupQualityScoring } from '../lib/quality.js';
34
- import { scanSensitive } from '../lib/sensitive-filter.js';
35
- import { generateCompactBriefing } from '../lib/compact-briefing.js';
36
- import { callLLM } from '../lib/llm.js';
37
- import { extractSessionSummary } from '../lib/session-summary.js';
38
- import { recordTranscriptTokens, recordExtraction, resetTranscriptCounter, loadBudgets, flushBudgets, removeBudget, } from '../lib/token-budget.js';
39
- import { appendObservations, removeObservations, cleanupStaleObservations, } from '../lib/session-observations.js';
40
- import { REFLECTION_SYSTEM, REFLECTION_PROMPT } from '../prompts/index.js';
41
- import { matchRules } from '../lib/hook-rules.js';
42
- // ============================================================================
43
- // Constants
44
- // ============================================================================
22
+ import { NotFoundError, NetworkError, ValidationError } from '../lib/errors.js';
23
+ import { startWatcher, stopWatcher } from './watcher.js';
24
+ import { startAnalyzer, stopAnalyzer } from './analyzer.js';
25
+ import { getConfig, getIdleReflectionConfig, getIdleWatcherConfig, getProjectRoot, getSuccDir, } from '../lib/config.js';
26
+ import { getStablePort } from '../lib/daemon-port.js';
27
+ import { closeDb, closeGlobalDb, closeStorageDispatcher, initStorageDispatcher, } from '../lib/storage/index.js';
28
+ import { cleanupEmbeddings } from '../lib/embeddings.js';
29
+ import { cleanupReranker } from '../lib/reranker.js';
30
+ import { cleanupQualityScoring } from '../lib/quality.js';
31
+ import { loadBudgets } from '../lib/token-budget.js';
32
+ import { getErrorMessage, } from './routes/types.js';
33
+ import { sessionRoutes } from './routes/sessions.js';
34
+ import { searchRoutes, resetSearchRoutesState } from './routes/search.js';
35
+ import { memoryRoutes, resetMemoryRoutesState } from './routes/memory.js';
36
+ import { clearBriefingCache, initReflectionMaintenance, performReflection, preGenerateBriefing, reflectionRoutes, resetReflectionRoutesState, } from './routes/reflection.js';
37
+ import { statusRoutes } from './routes/status.js';
38
+ import { watcherRoutes } from './routes/watcher.js';
39
+ import { analyzerRoutes } from './routes/analyzer.js';
40
+ import { skillRoutes } from './routes/skills.js';
41
+ import { hookRoutes } from './routes/hooks.js';
42
+ import { addVersionedRoutes, getApiVersionInfo } from './routes/versioning.js';
45
43
  const DEFAULT_PORT_RANGE_START = 37842;
46
44
  const MAX_PORT_ATTEMPTS = 100;
47
- // ============================================================================
48
- // Daemon State
49
- // ============================================================================
45
+ const MAX_BODY_SIZE = 10 * 1024 * 1024; // 10MB
50
46
  let state = null;
51
47
  let sessionManager = null;
52
48
  let idleWatcher = null;
53
- const briefingCache = new Map();
54
- const briefingGenerationInProgress = new Set();
55
- // In-flight dedup: prevents race condition when identical /api/remember requests
56
- // arrive within a short window (e.g. hook fires twice for same tool_use)
57
- const rememberInFlight = new Map();
58
- const REMEMBER_DEDUP_TTL_MS = 5000;
59
- // Hook rules cache — stores all hook-rule memories, invalidated on remember with hook-rule tag
60
- let hookRulesCache = null;
61
- const HOOK_RULES_CACHE_TTL = 60_000; // 60s
62
- // ============================================================================
63
- // File Paths
64
- // ============================================================================
65
49
  function getDaemonPidFile() {
66
50
  const succDir = getSuccDir();
67
51
  const tmpDir = path.join(succDir, '.tmp');
@@ -70,13 +54,6 @@ function getDaemonPidFile() {
70
54
  }
71
55
  return path.join(tmpDir, 'daemon.pid');
72
56
  }
73
- // ============================================================================
74
- // Progress File Management
75
- // ============================================================================
76
- /**
77
- * Get path to session progress file
78
- * Progress files accumulate idle reflection briefings for session-end processing
79
- */
80
57
  function getProgressFilePath(sessionId) {
81
58
  const succDir = getSuccDir();
82
59
  const tmpDir = path.join(succDir, '.tmp');
@@ -85,10 +62,6 @@ function getProgressFilePath(sessionId) {
85
62
  }
86
63
  return path.join(tmpDir, `session-${sessionId}-progress.md`);
87
64
  }
88
- /**
89
- * Append a briefing to the session progress file
90
- * Creates file with header if it doesn't exist
91
- */
92
65
  export function appendToProgressFile(sessionId, briefing) {
93
66
  const progressPath = getProgressFilePath(sessionId);
94
67
  const timestamp = new Date().toISOString();
@@ -106,10 +79,6 @@ export function appendToProgressFile(sessionId, briefing) {
106
79
  content += '\n\n---\n\n';
107
80
  fs.appendFileSync(progressPath, content);
108
81
  }
109
- /**
110
- * Read tail of transcript file (for fallback when no progress file)
111
- * Returns the last maxBytes of the file, starting from a complete line
112
- */
113
82
  export function readTailTranscript(transcriptPath, maxBytes = 2 * 1024 * 1024) {
114
83
  if (!fs.existsSync(transcriptPath)) {
115
84
  return '';
@@ -118,107 +87,14 @@ export function readTailTranscript(transcriptPath, maxBytes = 2 * 1024 * 1024) {
118
87
  if (stats.size <= maxBytes) {
119
88
  return fs.readFileSync(transcriptPath, 'utf8');
120
89
  }
121
- // Read only tail
122
90
  const fd = fs.openSync(transcriptPath, 'r');
123
91
  const buffer = Buffer.alloc(maxBytes);
124
92
  fs.readSync(fd, buffer, 0, maxBytes, stats.size - maxBytes);
125
93
  fs.closeSync(fd);
126
- // Find first complete line (skip partial line at start)
127
94
  const content = buffer.toString('utf8');
128
95
  const firstNewline = content.indexOf('\n');
129
96
  return firstNewline > 0 ? content.slice(firstNewline + 1) : content;
130
97
  }
131
- // ============================================================================
132
- // Briefing Pre-Generation
133
- // ============================================================================
134
- const BRIEFING_CACHE_MAX_AGE_MS = 5 * 60 * 1000; // 5 minutes
135
- const BRIEFING_MIN_TRANSCRIPT_GROWTH = 5000; // Re-generate after 5KB growth
136
- // const BRIEFING_PREGENERATE_IDLE_MS = 120 * 1000; // Pre-generate after 2 min idle
137
- /**
138
- * Pre-generate briefing for a session in background
139
- * Called when session is idle or transcript grows significantly
140
- */
141
- async function preGenerateBriefing(sessionId, transcriptPath) {
142
- // Skip if already generating
143
- if (briefingGenerationInProgress.has(sessionId)) {
144
- return;
145
- }
146
- if (!fs.existsSync(transcriptPath)) {
147
- return;
148
- }
149
- const stats = fs.statSync(transcriptPath);
150
- const currentSize = stats.size;
151
- // Check if we need to regenerate
152
- const cached = briefingCache.get(sessionId);
153
- if (cached) {
154
- const age = Date.now() - cached.generatedAt;
155
- const growth = currentSize - cached.transcriptSize;
156
- // Skip if cache is fresh and transcript hasn't grown much
157
- if (age < BRIEFING_CACHE_MAX_AGE_MS && growth < BRIEFING_MIN_TRANSCRIPT_GROWTH) {
158
- return;
159
- }
160
- }
161
- briefingGenerationInProgress.add(sessionId);
162
- log(`[briefing] Pre-generating for session ${sessionId.slice(0, 8)}...`);
163
- try {
164
- const transcriptContent = fs.readFileSync(transcriptPath, 'utf-8');
165
- const result = await generateCompactBriefing(transcriptContent);
166
- if (result.success && result.briefing) {
167
- briefingCache.set(sessionId, {
168
- briefing: result.briefing,
169
- generatedAt: Date.now(),
170
- transcriptSize: currentSize,
171
- });
172
- log(`[briefing] Pre-generated for session ${sessionId.slice(0, 8)} (${result.briefing.length} chars)`);
173
- }
174
- else {
175
- log(`[briefing] Pre-generation failed: ${result.error}`);
176
- }
177
- }
178
- catch (error) {
179
- log(`[briefing] Pre-generation error: ${error}`);
180
- }
181
- finally {
182
- briefingGenerationInProgress.delete(sessionId);
183
- }
184
- }
185
- /**
186
- * Get cached briefing or generate on-demand
187
- */
188
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
189
- async function getCachedBriefing(sessionId, transcriptPath) {
190
- const cached = briefingCache.get(sessionId);
191
- if (cached) {
192
- // Check if cache is still valid
193
- const age = Date.now() - cached.generatedAt;
194
- if (age < BRIEFING_CACHE_MAX_AGE_MS) {
195
- return { briefing: cached.briefing, cached: true };
196
- }
197
- }
198
- // Cache miss or stale - generate fresh
199
- if (!fs.existsSync(transcriptPath)) {
200
- return { cached: false };
201
- }
202
- const transcriptContent = fs.readFileSync(transcriptPath, 'utf-8');
203
- const result = await generateCompactBriefing(transcriptContent);
204
- if (result.success && result.briefing) {
205
- const stats = fs.statSync(transcriptPath);
206
- briefingCache.set(sessionId, {
207
- briefing: result.briefing,
208
- generatedAt: Date.now(),
209
- transcriptSize: stats.size,
210
- });
211
- return { briefing: result.briefing, cached: false };
212
- }
213
- return { cached: false };
214
- }
215
- /**
216
- * Clear briefing cache for a session (called when session ends)
217
- */
218
- function clearBriefingCache(sessionId) {
219
- briefingCache.delete(sessionId);
220
- briefingGenerationInProgress.delete(sessionId);
221
- }
222
98
  function getDaemonPortFile() {
223
99
  const succDir = getSuccDir();
224
100
  const tmpDir = path.join(succDir, '.tmp');
@@ -231,13 +107,9 @@ function getDaemonLogFile() {
231
107
  const succDir = getSuccDir();
232
108
  return path.join(succDir, 'daemon.log');
233
109
  }
234
- // ============================================================================
235
- // Logging
236
- // ============================================================================
237
110
  function log(message) {
238
111
  const timestamp = new Date().toISOString();
239
112
  const line = `[${timestamp}] ${message}\n`;
240
- // Write to daemon.log
241
113
  try {
242
114
  fs.appendFileSync(getDaemonLogFile(), line);
243
115
  }
@@ -246,291 +118,43 @@ function log(message) {
246
118
  error: err instanceof Error ? err.message : String(err),
247
119
  });
248
120
  }
249
- // Also write to stderr for debugging
250
121
  process.stderr.write(line);
251
122
  }
252
- // ============================================================================
253
- // Write Reflection
254
- // ============================================================================
255
- /**
256
- * Write a human-like reflection to the brain vault
257
- * Uses Claude CLI or local LLM to generate introspective text
258
- */
259
- async function writeReflection(transcript, _idleConfig) {
260
- const projectRoot = getProjectRoot();
261
- const reflectionsDir = path.join(projectRoot, '.succ', 'brain', 'reflections');
262
- // Create reflections directory if needed
263
- if (!fs.existsSync(reflectionsDir)) {
264
- fs.mkdirSync(reflectionsDir, { recursive: true });
265
- }
266
- const now = new Date();
267
- const dateStr = now.toISOString().split('T')[0];
268
- const timeStr = now.toTimeString().split(' ')[0].substring(0, 5);
269
- const timestamp = `${dateStr} ${timeStr}`;
270
- const prompt = REFLECTION_PROMPT.replace('{transcript}', transcript.substring(0, 3000));
271
- let reflectionText = null;
272
- // Use sleep agent for background reflection if enabled
273
- try {
274
- reflectionText = await callLLM(prompt, {
275
- timeout: 60000,
276
- useSleepAgent: true,
277
- systemPrompt: REFLECTION_SYSTEM,
278
- });
279
- }
280
- catch (err) {
281
- log(`[reflection] LLM call failed: ${err}`);
282
- reflectionText = null;
283
- }
284
- if (!reflectionText || reflectionText.trim().length < 50) {
285
- log(`[reflection] Reflection text too short or empty, skipping`);
286
- return;
287
- }
288
- // Write reflection file with YAML frontmatter
289
- const reflectionFile = path.join(reflectionsDir, `${timestamp}.md`);
290
- const content = `---
291
- date: ${dateStr}
292
- time: ${timeStr}
293
- trigger: idle
294
- tags:
295
- - reflection
296
- ---
297
-
298
- # Reflection ${dateStr} ${timeStr}
299
-
300
- ${reflectionText.trim()}
301
- `;
302
- fs.writeFileSync(reflectionFile, content);
303
- // Also save to memory (with dedup to prevent duplicate reflections)
304
- const embedding = await getEmbedding(reflectionText.trim());
305
- await saveMemory(reflectionText.trim(), embedding, ['reflection'], 'observation', {
306
- qualityScore: { score: 0.6, factors: { hasContext: 1 } },
307
- deduplicate: true,
308
- });
123
+ function isErrnoException(error) {
124
+ return typeof error === 'object' && error !== null && 'code' in error;
309
125
  }
310
- // LLM functions moved to shared module: src/lib/llm.ts
311
- // ============================================================================
312
- // Reflection Handler
313
- // ============================================================================
314
- async function handleReflection(sessionId, session) {
315
- // Skip reflection for service sessions (reflection subagents, analyzers, etc.)
316
- if (session.isService) {
317
- log(`[reflection] Skipping service session ${sessionId}`);
318
- return;
319
- }
320
- log(`[reflection] Starting reflection for session ${sessionId}`);
321
- const idleConfig = getIdleReflectionConfig();
322
- // Only run if we have a transcript
323
- if (!session.transcriptPath || !fs.existsSync(session.transcriptPath)) {
324
- log(`[reflection] No transcript found for session ${sessionId}`);
325
- return;
326
- }
327
- try {
328
- // ── Change detection: skip redundant work during long AFK ──
329
- let transcriptChanged = true;
330
- let memoriesChanged = true;
331
- try {
332
- const currentSize = fs.statSync(session.transcriptPath).size;
333
- if (session.lastTranscriptSize !== undefined && currentSize === session.lastTranscriptSize) {
334
- transcriptChanged = false;
335
- log(`[reflection] Transcript unchanged (${currentSize}b), skipping briefing`);
336
- }
337
- session.lastTranscriptSize = currentSize;
338
- }
339
- catch {
340
- /* transcript file gone — skip size check */
341
- }
342
- // Check memory count for consolidation skip
343
- const memStats = await getMemoryStats();
344
- const currentMemCount = memStats.total;
345
- if (session.lastMemoryCount !== undefined && currentMemCount === session.lastMemoryCount) {
346
- memoriesChanged = false;
347
- }
348
- session.lastMemoryCount = currentMemCount;
349
- // ── Mid-conversation observer: extract facts when enough new content ──
350
- const observerConfig = getObserverConfig();
351
- if (observerConfig.enabled && transcriptChanged) {
352
- try {
353
- const currentSize = session.lastTranscriptSize ?? 0;
354
- const lastObsSize = session.lastObservationSize ?? 0;
355
- const lastObsTime = session.lastObservation ?? session.registeredAt;
356
- const now = Date.now();
357
- // Read new content and track real token count via budget
358
- const newBytes = currentSize - lastObsSize;
359
- const timeThresholdMs = observerConfig.max_minutes * 60 * 1000;
360
- const enoughTime = now - lastObsTime >= timeThresholdMs;
361
- // Use byte-estimated token check first (cheap), then verify with real count
362
- const estimatedTokens = Math.ceil(newBytes / 3.5);
363
- const enoughNewContent = estimatedTokens >= observerConfig.min_tokens;
364
- if (enoughNewContent || enoughTime) {
365
- const newContent = readTailTranscript(session.transcriptPath, newBytes);
366
- // Track real tokens in budget
367
- const realTokens = recordTranscriptTokens(sessionId, newContent);
368
- log(`[observer] Triggering extraction (tokens: ~${realTokens}, time: ${Math.round((now - lastObsTime) / 60000)}min)`);
369
- if (newContent.length > 200) {
370
- const result = await extractSessionSummary(newContent, { verbose: false });
371
- recordExtraction(sessionId, result.transcriptTokens ?? 0, result.summaryTokens ?? 0, result.factsExtracted, result.factsSaved);
372
- resetTranscriptCounter(sessionId);
373
- // Persist extraction metadata to session observations (append-only)
374
- if (result.factsSaved > 0) {
375
- appendObservations(sessionId, [
376
- {
377
- content: `Extracted ${result.factsExtracted} facts, saved ${result.factsSaved}`,
378
- type: 'observation',
379
- tags: ['mid-session'],
380
- extractedAt: new Date().toISOString(),
381
- source: 'mid-session-observer',
382
- transcriptOffset: currentSize,
383
- memoryId: null,
384
- },
385
- ]);
386
- }
387
- log(`[observer] Extracted ${result.factsExtracted} facts, saved ${result.factsSaved} (skipped ${result.factsSkipped})`);
388
- }
389
- session.lastObservation = now;
390
- session.lastObservationSize = currentSize;
391
- flushBudgets();
392
- }
393
- }
394
- catch (err) {
395
- log(`[observer] Mid-session extraction failed: ${err}`);
396
- }
397
- }
398
- // ── Generate briefing (skip if transcript unchanged) ──
399
- let briefingResult = { success: false };
400
- if (transcriptChanged) {
401
- const transcriptContent = readTailTranscript(session.transcriptPath, 100 * 1024); // 100KB max
402
- briefingResult = await generateCompactBriefing(transcriptContent, {
403
- format: 'structured',
404
- include_memories: true,
405
- max_memories: 3,
406
- });
407
- if (briefingResult.success && briefingResult.briefing) {
408
- appendToProgressFile(sessionId, briefingResult.briefing);
409
- log(`[reflection] Appended briefing to progress file (${briefingResult.briefing.length} chars)`);
410
- }
411
- else {
412
- log(`[reflection] Failed to generate briefing for ${sessionId}`);
413
- }
414
- }
415
- // ── Parallel operations ──
416
- const globalConfig = getConfig();
417
- const parallelOps = [];
418
- // memory_consolidation - skip if no new memories (disabled by default, opt-in only)
419
- if (memoriesChanged && idleConfig.operations?.memory_consolidation === true) {
420
- parallelOps.push((async () => {
421
- const threshold = idleConfig.thresholds?.similarity_for_merge ?? 0.92;
422
- const limit = idleConfig.max_memories_to_process ?? 50;
423
- log(`[reflection] Running memory consolidation (threshold=${threshold}, limit=${limit})`);
424
- const { consolidate } = await import('../commands/consolidate.js');
425
- await consolidate({
426
- threshold: String(threshold),
427
- limit: String(limit),
428
- llm: true,
429
- verbose: false,
430
- });
431
- log(`[reflection] Memory consolidation complete`);
432
- })());
433
- }
434
- // retention_cleanup - independent (always runs if enabled)
435
- if (globalConfig.retention?.enabled && idleConfig.operations?.retention_cleanup !== false) {
436
- parallelOps.push((async () => {
437
- log(`[reflection] Running retention cleanup`);
438
- const { retention } = await import('../commands/retention.js');
439
- await retention({ apply: true, verbose: false });
440
- log(`[reflection] Retention cleanup complete`);
441
- })());
442
- }
443
- await Promise.all(parallelOps);
444
- // ── Graph cleanup: prune → enrich → orphans → communities → centrality ──
445
- if (idleConfig.operations?.graph_refinement !== false ||
446
- idleConfig.operations?.graph_enrichment !== false) {
447
- const shouldRun = memoriesChanged || session.lastLinkCount === undefined;
448
- if (shouldRun) {
449
- log(`[reflection] Running graph cleanup pipeline`);
450
- try {
451
- const { graphCleanup } = await import('../lib/graph/cleanup.js');
452
- const cleanupResult = await graphCleanup({
453
- skipEnrich: idleConfig.operations?.graph_enrichment === false,
454
- onProgress: (step, detail) => log(`[reflection] [${step}] ${detail}`),
455
- });
456
- log(`[reflection] Cleanup: pruned ${cleanupResult.pruned}, enriched ${cleanupResult.enriched}, orphans ${cleanupResult.orphansConnected}, communities ${cleanupResult.communitiesDetected}, centrality ${cleanupResult.centralityUpdated}`);
457
- // Proximity links from co-occurrence (not part of cleanup pipeline)
458
- try {
459
- const { createProximityLinks } = await import('../lib/graph/contextual-proximity.js');
460
- const r = await createProximityLinks({ minCooccurrence: 2 });
461
- log(`[reflection] Created ${r.created} proximity links`);
462
- }
463
- catch (err) {
464
- log(`[reflection] Proximity failed: ${err}`);
465
- }
466
- // Synthesize patterns from community clusters (uses cleanup's community result)
467
- if (cleanupResult.communityResult &&
468
- cleanupResult.communityResult.communities.length > 0) {
469
- try {
470
- const { synthesizeFromCommunities } = await import('../lib/reflection-synthesizer.js');
471
- const synthResult = await synthesizeFromCommunities(cleanupResult.communityResult, {
472
- log,
473
- });
474
- const hasSynthActivity = synthResult.patternsCreated > 0 ||
475
- synthResult.duplicatesSkipped > 0 ||
476
- synthResult.reinforced > 0;
477
- if (hasSynthActivity) {
478
- log(`[reflection] Synthesized ${synthResult.patternsCreated} patterns from ${synthResult.clustersProcessed} clusters` +
479
- (synthResult.reinforced > 0
480
- ? `, reinforced ${synthResult.reinforced} existing`
481
- : '') +
482
- (synthResult.duplicatesSkipped > 0
483
- ? `, skipped ${synthResult.duplicatesSkipped} duplicates`
484
- : '') +
485
- (synthResult.observationsMarked > 0
486
- ? `, marked ${synthResult.observationsMarked} as reflected`
487
- : ''));
488
- }
489
- }
490
- catch (err) {
491
- log(`[reflection] Synthesis failed: ${err}`);
492
- }
493
- }
494
- session.lastLinkCount = (session.lastLinkCount ?? 0) + cleanupResult.orphansConnected;
495
- }
496
- catch (err) {
497
- log(`[reflection] Graph cleanup failed: ${err}`);
498
- }
499
- }
500
- else {
501
- log(`[reflection] Skipping graph cleanup (no changes)`);
502
- }
503
- }
504
- // ── Write reflection (runs last, may use LLM) ──
505
- if (idleConfig.operations?.write_reflection !== false) {
506
- log(`[reflection] Writing reflection for ${sessionId}`);
507
- try {
508
- const progressPath = getProgressFilePath(sessionId);
509
- const briefingContent = fs.existsSync(progressPath)
510
- ? fs.readFileSync(progressPath, 'utf-8')
511
- : briefingResult.briefing || '';
512
- if (briefingContent.length >= 100) {
513
- await writeReflection(briefingContent, idleConfig);
514
- log(`[reflection] Reflection written`);
515
- }
516
- }
517
- catch (err) {
518
- log(`[reflection] Write reflection error: ${err}`);
519
- }
520
- }
521
- log(`[reflection] Completed reflection for session ${sessionId}`);
522
- }
523
- catch (err) {
524
- log(`[reflection] Error for session ${sessionId}: ${err}`);
525
- }
126
+ function createRouteContext() {
127
+ return {
128
+ state,
129
+ sessionManager,
130
+ log,
131
+ checkShutdown,
132
+ clearBriefingCache,
133
+ appendToProgressFile,
134
+ readTailTranscript,
135
+ getProgressFilePath,
136
+ };
137
+ }
138
+ function buildRoutes(ctx) {
139
+ const baseRoutes = {
140
+ ...statusRoutes(ctx),
141
+ ...sessionRoutes(ctx),
142
+ ...searchRoutes(ctx),
143
+ ...memoryRoutes(ctx),
144
+ ...reflectionRoutes(ctx),
145
+ ...watcherRoutes(ctx),
146
+ ...analyzerRoutes(ctx),
147
+ ...skillRoutes(ctx),
148
+ ...hookRoutes(ctx),
149
+ // API version info endpoint
150
+ 'GET /api/version': async () => getApiVersionInfo(),
151
+ };
152
+ // Add /v1/api/* aliases for all /api/* routes
153
+ return addVersionedRoutes(baseRoutes);
526
154
  }
527
- // ============================================================================
528
- // HTTP Request Handler
529
- // ============================================================================
530
155
  async function handleRequest(req, res) {
531
156
  const reqUrl = new URL(req.url || '/', `http://localhost`);
532
157
  const method = req.method || 'GET';
533
- // CORS headers (for potential web clients)
534
158
  res.setHeader('Access-Control-Allow-Origin', '*');
535
159
  res.setHeader('Access-Control-Allow-Methods', 'GET, POST, DELETE, OPTIONS');
536
160
  res.setHeader('Access-Control-Allow-Headers', 'Content-Type');
@@ -539,500 +163,140 @@ async function handleRequest(req, res) {
539
163
  res.end();
540
164
  return;
541
165
  }
542
- // Parse JSON body for POST requests
543
166
  let body = null;
544
167
  if (method === 'POST') {
545
- body = await parseBody(req);
168
+ try {
169
+ body = await parseBody(req);
170
+ }
171
+ catch (error) {
172
+ const message = getErrorMessage(error);
173
+ if (message.includes('too large')) {
174
+ res.writeHead(413, { 'Content-Type': 'application/json' });
175
+ res.end(JSON.stringify({ error: 'Request body too large' }));
176
+ return;
177
+ }
178
+ res.writeHead(400, { 'Content-Type': 'application/json' });
179
+ res.end(JSON.stringify({ error: 'Invalid request body' }));
180
+ return;
181
+ }
546
182
  }
547
183
  try {
548
- // Route request
549
184
  const result = await routeRequest(method, reqUrl.pathname, reqUrl.searchParams, body);
550
185
  res.writeHead(200, { 'Content-Type': 'application/json' });
551
186
  res.end(JSON.stringify(result));
552
187
  }
553
188
  catch (err) {
554
- log(`[http] Error: ${err.message}`);
189
+ const message = getErrorMessage(err);
190
+ log(`[http] Error: ${message}`);
191
+ if (err instanceof ValidationError) {
192
+ res.writeHead(400, { 'Content-Type': 'application/json' });
193
+ res.end(JSON.stringify({ error: message }));
194
+ return;
195
+ }
196
+ if (err instanceof NotFoundError) {
197
+ res.writeHead(404, { 'Content-Type': 'application/json' });
198
+ res.end(JSON.stringify({ error: message }));
199
+ return;
200
+ }
555
201
  res.writeHead(500, { 'Content-Type': 'application/json' });
556
- res.end(JSON.stringify({ error: err.message }));
202
+ res.end(JSON.stringify({ error: 'Internal server error' }));
557
203
  }
558
204
  }
559
205
  export async function parseBody(req) {
560
206
  return new Promise((resolve, reject) => {
561
207
  let data = '';
562
- req.on('data', (chunk) => (data += chunk));
208
+ let size = 0;
209
+ let settled = false;
210
+ const settle = (fn) => {
211
+ if (settled)
212
+ return;
213
+ settled = true;
214
+ fn();
215
+ };
216
+ req.on('data', (chunk) => {
217
+ if (settled)
218
+ return;
219
+ const chunkSize = Buffer.isBuffer(chunk) ? chunk.length : Buffer.byteLength(String(chunk));
220
+ size += chunkSize;
221
+ if (size > MAX_BODY_SIZE) {
222
+ req.destroy();
223
+ settle(() => reject(new Error('Request body too large')));
224
+ return;
225
+ }
226
+ data += chunk;
227
+ });
563
228
  req.on('end', () => {
229
+ if (settled)
230
+ return;
564
231
  try {
565
- resolve(data ? JSON.parse(data) : {});
232
+ const parsed = data ? JSON.parse(data) : {};
233
+ if (!parsed || typeof parsed !== 'object' || Array.isArray(parsed)) {
234
+ settle(() => reject(new Error('Invalid request body')));
235
+ return;
236
+ }
237
+ settle(() => resolve(parsed));
566
238
  }
567
- catch (err) {
568
- logWarn('daemon', 'Invalid JSON in HTTP request body', {
569
- error: err instanceof Error ? err.message : String(err),
570
- });
571
- resolve({});
239
+ catch {
240
+ settle(() => reject(new Error('Invalid request body')));
572
241
  }
573
242
  });
574
- req.on('error', reject);
243
+ req.on('error', (error) => {
244
+ settle(() => reject(error));
245
+ });
575
246
  });
576
247
  }
577
248
  /** @internal Exported for testing */
578
249
  export async function routeRequest(method, pathname, searchParams, body) {
579
- // Health check
580
- if (pathname === '/health') {
581
- return {
582
- status: 'ok',
583
- pid: process.pid,
584
- uptime: Date.now() - (state?.startedAt || Date.now()),
585
- activeSessions: sessionManager?.count() || 0,
586
- cwd: state?.cwd || process.cwd(),
587
- };
250
+ const routes = buildRoutes(createRouteContext());
251
+ const key = `${method} ${pathname}`;
252
+ const handler = routes[key];
253
+ if (!handler) {
254
+ throw new NotFoundError(`Unknown endpoint: ${method} ${pathname}`);
588
255
  }
589
- // Session endpoints
590
- if (pathname === '/api/session/register' && method === 'POST') {
591
- const { session_id, transcript_path, is_service = false } = body;
592
- if (!session_id) {
593
- throw new ValidationError('session_id required');
594
- }
595
- const session = sessionManager.register(session_id, transcript_path || '', is_service);
596
- log(`[session] Registered: ${session_id}${is_service ? ' (service)' : ''}`);
597
- return { success: true, session };
598
- }
599
- if (pathname === '/api/session/unregister' && method === 'POST') {
600
- const { session_id, transcript_path, run_reflection } = body;
601
- if (!session_id) {
602
- throw new ValidationError('session_id required');
603
- }
604
- const session = sessionManager.get(session_id);
605
- const transcriptFile = transcript_path || session?.transcriptPath || '';
606
- // Flush session counters to learning_deltas before unregister
607
- try {
608
- const d = await getStorageDispatcher();
609
- await d.flushSessionCounters('daemon-session');
610
- }
611
- catch (err) {
612
- log(`[session] Failed to flush session counters: ${err}`);
613
- }
614
- // Unregister the session immediately (don't block on processing)
615
- const removed = sessionManager.unregister(session_id);
616
- clearBriefingCache(session_id); // Clean up any cached briefing
617
- removeBudget(session_id); // Clean up token budget
618
- removeObservations(session_id); // Clean up observation JSONL
619
- flushBudgets();
620
- log(`[session] Unregistered: ${session_id} (removed=${removed})`);
621
- // Process session asynchronously (summarize transcript, extract learnings, save to memory)
622
- if (run_reflection && transcriptFile) {
623
- sessionManager.incrementPendingWork();
624
- log(`[session] Queuing async processing for ${session_id}`);
625
- // Fire-and-forget async processing
626
- (async () => {
627
- try {
628
- const result = await processSessionEnd(transcriptFile, session_id, log);
629
- log(`[session] Processing complete for ${session_id}: summary=${result.summary.length}chars, learnings=${result.learnings.length}, saved=${result.saved}`);
630
- }
631
- catch (err) {
632
- log(`[session] Processing failed for ${session_id}: ${err}`);
633
- }
634
- finally {
635
- sessionManager.decrementPendingWork();
636
- // Check shutdown after work completes
637
- checkShutdown();
638
- }
639
- })();
640
- }
641
- else {
642
- // No processing needed, check shutdown immediately
643
- checkShutdown();
644
- }
645
- return { success: removed, remaining_sessions: sessionManager.count() };
646
- }
647
- if (pathname === '/api/session/activity' && method === 'POST') {
648
- const { session_id, type, transcript_path, is_service = false } = body;
649
- if (!session_id || !type) {
650
- throw new ValidationError('session_id and type required');
651
- }
652
- let session = sessionManager.activity(session_id, type);
653
- if (!session) {
654
- // Auto-register if session not found (with transcript_path if provided)
655
- sessionManager.register(session_id, transcript_path || '', is_service);
656
- session = sessionManager.activity(session_id, type);
657
- log(`[session] Auto-registered and activity: ${session_id} (${type})${is_service ? ' (service)' : ''}`);
658
- }
659
- else if (transcript_path && !session.transcriptPath) {
660
- // Update transcript path if not set
661
- session.transcriptPath = transcript_path;
662
- log(`[session] Activity: ${session_id} (${type}) + updated transcript`);
663
- }
664
- else {
665
- log(`[session] Activity: ${session_id} (${type})`);
666
- }
667
- return { success: true };
668
- }
669
- if (pathname === '/api/sessions' && method === 'GET') {
670
- const includeService = searchParams.get('includeService') === 'true';
671
- const sessions = {};
672
- for (const [id, session] of sessionManager.getAll(includeService)) {
673
- sessions[id] = session;
674
- }
675
- return { sessions, count: sessionManager.count(includeService) };
676
- }
677
- // Search endpoints
678
- if (pathname === '/api/search' && method === 'POST') {
679
- const { query, limit = 5, threshold = 0.3 } = body;
680
- if (!query) {
681
- throw new ValidationError('query required');
682
- }
683
- const queryEmbedding = await getEmbedding(query);
684
- const results = await hybridSearchDocs(query, queryEmbedding, limit, threshold);
685
- // Track access for returned memories
686
- const accesses = results
687
- .filter((r) => r.memory_id)
688
- .map((r) => ({ memoryId: r.memory_id, weight: 0.5 }));
689
- if (accesses.length > 0) {
690
- await incrementMemoryAccessBatch(accesses);
691
- }
692
- return { results };
693
- }
694
- if (pathname === '/api/search-code' && method === 'POST') {
695
- const { query, limit = 5, threshold = 0.3 } = body;
696
- if (!query) {
697
- throw new ValidationError('query required');
698
- }
699
- const queryEmbedding = await getEmbedding(query);
700
- const results = await hybridSearchCode(query, queryEmbedding, limit, threshold);
701
- return { results };
702
- }
703
- if (pathname === '/api/recall' && method === 'POST') {
704
- const { query, limit = 5 } = body;
705
- // Empty query returns recent memories
706
- if (!query) {
707
- const memories = await getRecentMemories(limit);
708
- return { results: memories };
709
- }
710
- // Generate embedding for semantic search
711
- const queryEmbedding = await getEmbedding(query);
712
- const results = await hybridSearchMemories(query, queryEmbedding, limit, 0.3);
713
- // Track access for returned memories
714
- const accesses = results
715
- .filter((r) => r.id)
716
- .map((r) => ({ memoryId: r.id, weight: 1.0 }));
717
- if (accesses.length > 0) {
718
- await incrementMemoryAccessBatch(accesses);
719
- }
720
- return { results };
721
- }
722
- if (pathname === '/api/pinned' && method === 'GET') {
723
- const pinned = await getPinnedMemories();
724
- return { results: pinned };
725
- }
726
- if (pathname === '/api/pinned/cleanup' && method === 'POST') {
727
- // Remove false invariant flags from observation-type memories
728
- const pinned = await getPinnedMemories();
729
- let cleaned = 0;
730
- for (const mem of pinned) {
731
- if (mem.type === 'observation' && mem.is_invariant) {
732
- await setMemoryInvariant(mem.id, false);
733
- cleaned++;
734
- }
735
- }
736
- return { cleaned, total: pinned.length };
737
- }
738
- if (pathname === '/api/recall-by-tag' && method === 'POST') {
739
- const { tag, limit = 5 } = body;
740
- if (!tag) {
741
- throw new ValidationError('tag required');
742
- }
743
- const results = await getMemoriesByTag(tag, limit);
744
- return { results };
745
- }
746
- if (pathname === '/api/hook-rules' && method === 'POST') {
747
- const { tool_name, tool_input: rawInput } = body;
748
- if (!tool_name) {
749
- throw new ValidationError('tool_name required');
750
- }
751
- const tool_input = rawInput && typeof rawInput === 'object' && !Array.isArray(rawInput)
752
- ? rawInput
753
- : {};
754
- // Check cache
755
- const now = Date.now();
756
- if (!hookRulesCache || now - hookRulesCache.timestamp > HOOK_RULES_CACHE_TTL) {
757
- const memories = await getMemoriesByTag('hook-rule', 50);
758
- hookRulesCache = { memories, timestamp: now };
759
- }
760
- const rules = matchRules(hookRulesCache.memories, tool_name, tool_input);
761
- return { rules };
762
- }
763
- if (pathname === '/api/remember' && method === 'POST') {
764
- const { content, tags = [], type = 'observation', source, global = false, valid_from, valid_until, } = body;
765
- if (!content) {
766
- throw new ValidationError('content required');
767
- }
768
- // In-flight dedup: if an identical request is already being processed, wait for it
769
- // Prevents race condition when hooks fire twice for the same tool_use
770
- const contentHash = content.slice(0, 200) + '|' + (tags || []).join(',');
771
- const existing = rememberInFlight.get(contentHash);
772
- if (existing) {
773
- const result = await existing;
774
- return { success: false, id: result.id, isDuplicate: true, reason: 'in-flight dedup' };
775
- }
776
- const processRemember = async () => {
777
- // Check for sensitive content
778
- const config = getConfig();
779
- let finalContent = content;
780
- if (config.sensitive_filter_enabled !== false) {
781
- const scanResult = scanSensitive(content);
782
- if (scanResult.hasSensitive) {
783
- if (config.sensitive_auto_redact) {
784
- finalContent = scanResult.redactedText;
785
- }
786
- else {
787
- throw new ValidationError('Content contains sensitive information');
788
- }
789
- }
790
- }
791
- // Get embedding
792
- const embedding = await getEmbedding(finalContent);
793
- // Score quality
794
- const qualityResult = await scoreMemory(finalContent);
795
- if (!passesQualityThreshold(qualityResult)) {
796
- return { success: false, reason: 'Below quality threshold', score: qualityResult.score };
797
- }
798
- // Save to appropriate DB
799
- let result;
800
- if (global) {
801
- result = await saveGlobalMemory(finalContent, embedding, tags, type);
802
- }
803
- else {
804
- result = await saveMemory(finalContent, embedding, tags, source ?? type, {
805
- qualityScore: { score: qualityResult.score, factors: qualityResult.factors },
806
- validFrom: valid_from,
807
- validUntil: valid_until,
808
- });
809
- }
810
- // Invalidate hook-rules cache if this memory is a hook rule
811
- if (Array.isArray(tags) && tags.includes('hook-rule')) {
812
- hookRulesCache = null;
813
- }
814
- return { success: !result.isDuplicate, id: result.id, isDuplicate: result.isDuplicate };
815
- };
816
- const promise = processRemember();
817
- rememberInFlight.set(contentHash, promise);
818
- setTimeout(() => rememberInFlight.delete(contentHash), REMEMBER_DEDUP_TTL_MS);
819
- try {
820
- return await promise;
821
- }
822
- finally {
823
- rememberInFlight.delete(contentHash);
824
- }
825
- }
826
- // Reflection endpoint
827
- if (pathname === '/api/reflect' && method === 'POST') {
828
- const { session_id } = body;
829
- const watcherConfig = getIdleWatcherConfig();
830
- if (session_id) {
831
- const session = sessionManager.get(session_id);
832
- if (!session) {
833
- throw new NotFoundError('Session not found');
834
- }
835
- await handleReflection(session_id, session);
836
- sessionManager.markReflection(session_id);
837
- return { success: true, session_id };
838
- }
839
- else {
840
- // Run for all idle sessions
841
- const idleSessions = sessionManager.getIdleSessions(watcherConfig.idle_minutes);
842
- for (const { sessionId, session } of idleSessions) {
843
- await handleReflection(sessionId, session);
844
- sessionManager.markReflection(sessionId);
845
- }
846
- return { success: true, sessions_processed: idleSessions.length };
847
- }
848
- }
849
- // Compact briefing endpoint (for /compact hook)
850
- // Supports pre-generated cache for instant responses
851
- if (pathname === '/api/briefing' && method === 'POST') {
852
- const { transcript, transcript_path, session_id, format, include_learnings, include_memories, max_memories, use_cache, } = body;
853
- // Try cached briefing first if session_id provided and use_cache not explicitly false
854
- if (session_id && use_cache !== false) {
855
- const cached = briefingCache.get(session_id);
856
- if (cached) {
857
- const age = Date.now() - cached.generatedAt;
858
- if (age < BRIEFING_CACHE_MAX_AGE_MS) {
859
- log(`[briefing] Serving cached briefing for ${session_id.slice(0, 8)} (age: ${Math.round(age / 1000)}s)`);
860
- return { success: true, briefing: cached.briefing, cached: true };
861
- }
256
+ return handler(body, searchParams);
257
+ }
258
+ let updateCheckTimer = null;
259
+ function scheduleUpdateCheck(logFn) {
260
+ // Run once at startup (after short delay to not block init)
261
+ const runCheck = () => {
262
+ import('../lib/version-check.js')
263
+ .then(({ checkForUpdate }) => checkForUpdate())
264
+ .then((result) => {
265
+ if (result?.update_available) {
266
+ logFn(`[update-check] Update available: ${result.current} ${result.latest}`);
862
267
  }
863
- }
864
- // Either transcript content or path to transcript file
865
- let transcriptContent;
866
- if (transcript) {
867
- transcriptContent = transcript;
868
- }
869
- else if (transcript_path && fs.existsSync(transcript_path)) {
870
- transcriptContent = fs.readFileSync(transcript_path, 'utf-8');
871
- }
872
- else {
873
- throw new ValidationError('transcript or transcript_path required');
874
- }
875
- const result = await generateCompactBriefing(transcriptContent, {
876
- format,
877
- include_learnings,
878
- include_memories,
879
- max_memories,
268
+ })
269
+ .catch((err) => {
270
+ logFn(`[update-check] Failed: ${err instanceof Error ? err.message : String(err)}`);
880
271
  });
881
- // Cache the result if session_id provided
882
- if (session_id && result.success && result.briefing && transcript_path) {
883
- const stats = fs.existsSync(transcript_path) ? fs.statSync(transcript_path) : null;
884
- briefingCache.set(session_id, {
885
- briefing: result.briefing,
886
- generatedAt: Date.now(),
887
- transcriptSize: stats?.size || 0,
888
- });
889
- }
890
- return { ...result, cached: false };
891
- }
892
- // Status endpoints
893
- if (pathname === '/api/status' && method === 'GET') {
894
- const stats = await getStats();
895
- const memStats = await getMemoryStats();
896
- const watchStatus = getWatcherStatus();
897
- const analyzeStatus = getAnalyzerStatus();
898
- return {
899
- daemon: {
900
- pid: process.pid,
901
- uptime: Date.now() - (state?.startedAt || Date.now()),
902
- sessions: sessionManager.count(),
903
- },
904
- index: stats,
905
- memories: memStats,
906
- services: {
907
- watch: watchStatus,
908
- analyze: analyzeStatus,
909
- },
910
- };
911
- }
912
- // Watch service endpoints
913
- if (pathname === '/api/watch/start' && method === 'POST') {
914
- const { patterns, includeCode } = body;
915
- const watchState = await startWatcher({ patterns, includeCode }, log);
916
- return {
917
- success: true,
918
- active: watchState.active,
919
- patterns: watchState.patterns,
920
- includeCode: watchState.includeCode,
921
- };
922
- }
923
- if (pathname === '/api/watch/stop' && method === 'POST') {
924
- await stopWatcher(log);
925
- return { success: true };
926
- }
927
- if (pathname === '/api/watch/status' && method === 'GET') {
928
- return getWatcherStatus();
929
- }
930
- if (pathname === '/api/watch/index' && method === 'POST') {
931
- const { file } = body;
932
- if (!file) {
933
- throw new ValidationError('file required');
934
- }
935
- await indexFileOnDemand(file, log);
936
- return { success: true, file };
937
- }
938
- // Analyze service endpoints
939
- if (pathname === '/api/analyze/start' && method === 'POST') {
940
- const { intervalMinutes, mode } = body;
941
- const analyzeState = startAnalyzer({ intervalMinutes, mode }, log);
942
- return {
943
- success: true,
944
- active: analyzeState.active,
945
- runsCompleted: analyzeState.runsCompleted,
946
- };
947
- }
948
- if (pathname === '/api/analyze/stop' && method === 'POST') {
949
- stopAnalyzer(log);
950
- return { success: true };
951
- }
952
- if (pathname === '/api/analyze/status' && method === 'GET') {
953
- return getAnalyzerStatus();
954
- }
955
- if (pathname === '/api/analyze' && method === 'POST') {
956
- const { mode = 'claude' } = body;
957
- await triggerAnalysis(mode, log);
958
- return { success: true };
959
- }
960
- // Skills endpoints
961
- if (pathname === '/api/skills/suggest' && method === 'POST') {
962
- const { prompt, limit = 2 } = body;
963
- if (!prompt) {
964
- throw new ValidationError('prompt required');
965
- }
966
- const { suggestSkills, getSkillsConfig } = await import('../lib/skills.js');
967
- const config = getSkillsConfig();
968
- if (!config.enabled || !config.auto_suggest?.enabled) {
969
- return { success: true, skills: [], disabled: true };
970
- }
971
- const suggestions = await suggestSkills(prompt, config);
972
- return {
973
- success: true,
974
- skills: suggestions.slice(0, limit),
975
- };
976
- }
977
- if (pathname === '/api/skills/index' && method === 'POST') {
978
- const { indexLocalSkills } = await import('../lib/skills.js');
979
- const cwd = state?.cwd || process.cwd();
980
- const count = indexLocalSkills(cwd);
981
- return { success: true, indexed: count };
982
- }
983
- if (pathname === '/api/skills/track' && method === 'POST') {
984
- const { skill_name } = body;
985
- if (!skill_name) {
986
- throw new ValidationError('skill_name required');
987
- }
988
- const { trackSkillUsage } = await import('../lib/skills.js');
989
- trackSkillUsage(skill_name);
990
- return { success: true };
991
- }
992
- // Skyll status endpoint
993
- if (pathname === '/api/skills/skyll' && method === 'GET') {
994
- const { getSkyllStatus } = await import('../lib/skyll-client.js');
995
- return getSkyllStatus();
996
- }
997
- // Services endpoint (list all services status)
998
- if (pathname === '/api/services' && method === 'GET') {
999
- return {
1000
- watch: getWatcherStatus(),
1001
- analyze: getAnalyzerStatus(),
1002
- idle: {
1003
- enabled: true,
1004
- sessions: sessionManager.count(),
1005
- },
1006
- };
1007
- }
1008
- throw new NotFoundError(`Unknown endpoint: ${method} ${pathname}`);
272
+ };
273
+ setTimeout(runCheck, 5000); // 5s after startup
274
+ // Re-check every 12 hours (cache TTL is 24h, so this keeps it warm)
275
+ updateCheckTimer = setInterval(runCheck, 12 * 3600_000);
276
+ updateCheckTimer.unref(); // Don't prevent process exit
1009
277
  }
1010
- // ============================================================================
1011
- // Daemon Lifecycle
1012
- // ============================================================================
1013
278
  export async function startDaemon() {
1014
279
  if (state?.server) {
1015
280
  return { port: state.port, pid: process.pid };
1016
281
  }
1017
- // Check if another daemon is already running (prevent duplicate processes)
1018
282
  const existingPidFile = getDaemonPidFile();
1019
283
  if (fs.existsSync(existingPidFile)) {
1020
284
  try {
1021
285
  const existingPid = parseInt(fs.readFileSync(existingPidFile, 'utf8').trim(), 10);
1022
286
  if (existingPid && existingPid !== process.pid) {
1023
- // Check if process is actually running
1024
287
  try {
1025
- process.kill(existingPid, 0); // Signal 0 = check if process exists
1026
- // Process exists, read port and return
288
+ process.kill(existingPid, 0);
1027
289
  const portFile = getDaemonPortFile();
1028
290
  if (fs.existsSync(portFile)) {
1029
291
  const port = parseInt(fs.readFileSync(portFile, 'utf8').trim(), 10);
1030
292
  log(`[daemon] Another daemon already running (pid=${existingPid}, port=${port})`);
1031
- process.exit(0); // Exit silently - another daemon is handling things
293
+ process.exit(0);
1032
294
  }
1033
295
  }
1034
- catch {
1035
- // Process doesn't exist, clean up stale files
296
+ catch (error) {
297
+ logWarn('service', 'Failed to signal existing daemon process for liveness check', {
298
+ error: error instanceof Error ? error.message : String(error),
299
+ });
1036
300
  log(`[daemon] Cleaning up stale PID file (pid=${existingPid} not running)`);
1037
301
  fs.unlinkSync(existingPidFile);
1038
302
  const portFile = getDaemonPortFile();
@@ -1051,15 +315,10 @@ export async function startDaemon() {
1051
315
  const cwd = getProjectRoot();
1052
316
  const watcherConfig = getIdleWatcherConfig();
1053
317
  getIdleReflectionConfig();
1054
- // Initialize storage dispatcher (routes to SQLite or PG based on config)
1055
318
  await initStorageDispatcher();
1056
- // Initialize session manager
1057
319
  sessionManager = createSessionManager();
1058
- // Load token budgets from previous daemon run
1059
320
  loadBudgets();
1060
- // Clean up stale observation files (>48h)
1061
- cleanupStaleObservations();
1062
- // Create HTTP server
321
+ initReflectionMaintenance();
1063
322
  const server = http.createServer((req, res) => {
1064
323
  handleRequest(req, res).catch((err) => {
1065
324
  log(`[http] Unhandled error: ${err.message}`);
@@ -1067,14 +326,25 @@ export async function startDaemon() {
1067
326
  res.end();
1068
327
  });
1069
328
  });
1070
- // Find available port
1071
- const portStart = DEFAULT_PORT_RANGE_START;
1072
- let port = portStart;
1073
- for (let i = 0; i < MAX_PORT_ATTEMPTS; i++) {
329
+ // Try stable port first (config or hash-based), then fall back to scan
330
+ const config = getConfig();
331
+ const stablePort = config.daemon?.port ?? getStablePort(cwd);
332
+ const fallbackStart = config.daemon?.port_range_start ?? DEFAULT_PORT_RANGE_START;
333
+ let port = stablePort;
334
+ let portAttempts = 0;
335
+ const maxAttempts = 1 + MAX_PORT_ATTEMPTS; // 1 stable + 100 fallback
336
+ for (let i = 0; i < maxAttempts; i++) {
1074
337
  await new Promise((resolve, reject) => {
1075
338
  server.once('error', (err) => {
1076
339
  if (err.code === 'EADDRINUSE') {
1077
- port++;
340
+ portAttempts++;
341
+ if (portAttempts === 1) {
342
+ // Stable port busy — switch to fallback range
343
+ port = fallbackStart;
344
+ }
345
+ else {
346
+ port++;
347
+ }
1078
348
  resolve();
1079
349
  }
1080
350
  else {
@@ -1090,33 +360,30 @@ export async function startDaemon() {
1090
360
  }
1091
361
  }
1092
362
  if (!server.listening) {
1093
- throw new NetworkError(`Could not find available port in range ${portStart}-${portStart + MAX_PORT_ATTEMPTS}`);
363
+ throw new NetworkError(`Could not find available port (tried ${stablePort}, then ${fallbackStart}-${fallbackStart + MAX_PORT_ATTEMPTS})`);
1094
364
  }
1095
- // Save state
1096
365
  state = {
1097
366
  cwd,
1098
367
  startedAt: Date.now(),
1099
368
  port,
1100
369
  server,
1101
370
  };
1102
- // Write PID and port files
1103
371
  fs.writeFileSync(getDaemonPidFile(), String(process.pid));
1104
372
  fs.writeFileSync(getDaemonPortFile(), String(port));
1105
- // Start idle watcher with briefing pre-generation
1106
373
  idleWatcher = createIdleWatcher({
1107
374
  sessionManager,
1108
- onIdle: handleReflection,
1109
- onPreGenerateBriefing: preGenerateBriefing,
375
+ onIdle: (sessionId, session) => performReflection(createRouteContext(), sessionId, session),
376
+ onPreGenerateBriefing: (sessionId, transcriptPath) => preGenerateBriefing(createRouteContext(), sessionId, transcriptPath),
1110
377
  checkIntervalSeconds: watcherConfig.check_interval,
1111
378
  idleMinutes: watcherConfig.idle_minutes,
1112
379
  reflectionCooldownMinutes: watcherConfig.reflection_cooldown_minutes,
1113
- preGenerateIdleSeconds: 120, // Pre-generate briefing after 2 min idle
380
+ preGenerateIdleSeconds: 120,
1114
381
  log,
1115
382
  });
1116
383
  idleWatcher.start();
1117
384
  log(`[daemon] Started on port ${port} (pid=${process.pid})`);
1118
- // Auto-start watch service if configured
1119
- const config = getConfig();
385
+ // Background update check keeps cache warm for MCP-only users
386
+ scheduleUpdateCheck(log);
1120
387
  if (config.daemon?.watch?.auto_start) {
1121
388
  const watchConfig = config.daemon.watch;
1122
389
  await startWatcher({
@@ -1126,7 +393,6 @@ export async function startDaemon() {
1126
393
  }, log);
1127
394
  log(`[daemon] Auto-started watch service`);
1128
395
  }
1129
- // Auto-start analyze service if configured
1130
396
  if (config.daemon?.analyze?.auto_start) {
1131
397
  const analyzeConfig = config.daemon.analyze;
1132
398
  startAnalyzer({
@@ -1135,7 +401,6 @@ export async function startDaemon() {
1135
401
  }, log);
1136
402
  log(`[daemon] Auto-started analyze service`);
1137
403
  }
1138
- // Setup graceful shutdown
1139
404
  setupShutdownHandlers();
1140
405
  return { port, pid: process.pid };
1141
406
  }
@@ -1143,14 +408,18 @@ function setupShutdownHandlers() {
1143
408
  const shutdown = () => shutdownDaemon();
1144
409
  process.on('SIGTERM', shutdown);
1145
410
  process.on('SIGINT', shutdown);
1146
- // SIGHUP only exists on Unix
1147
411
  if (process.platform !== 'win32') {
1148
412
  process.on('SIGHUP', shutdown);
1149
413
  }
414
+ // Prevent silent crashes — log and keep running
415
+ process.on('uncaughtException', (err) => {
416
+ log(`[daemon] UNCAUGHT EXCEPTION: ${err.message}\n${err.stack}`);
417
+ });
418
+ process.on('unhandledRejection', (reason) => {
419
+ const msg = reason instanceof Error ? `${reason.message}\n${reason.stack}` : String(reason);
420
+ log(`[daemon] UNHANDLED REJECTION: ${msg}`);
421
+ });
1150
422
  }
1151
- /**
1152
- * Check if daemon should shutdown (no sessions and no pending work)
1153
- */
1154
423
  function checkShutdown() {
1155
424
  if (sessionManager?.canShutdown()) {
1156
425
  log(`[daemon] No more sessions and no pending work, scheduling shutdown`);
@@ -1158,54 +427,56 @@ function checkShutdown() {
1158
427
  if (sessionManager?.canShutdown()) {
1159
428
  shutdownDaemon();
1160
429
  }
1161
- }, 5000); // Give 5 seconds for new sessions to connect
430
+ }, 5000);
1162
431
  }
1163
432
  }
1164
- export function shutdownDaemon() {
433
+ export async function shutdownDaemon() {
1165
434
  log('[daemon] Shutting down...');
1166
- // Stop idle watcher
435
+ if (updateCheckTimer) {
436
+ clearInterval(updateCheckTimer);
437
+ updateCheckTimer = null;
438
+ }
1167
439
  if (idleWatcher) {
1168
440
  idleWatcher.stop();
1169
441
  idleWatcher = null;
1170
442
  }
1171
- // Stop watch service (async, but we're shutting down so fire-and-forget)
1172
- stopWatcher(log).catch((err) => log(`[shutdown] Watcher stop failed: ${err}`));
1173
- // Stop analyze service
443
+ const watcherPromise = stopWatcher(log).catch((err) => log(`[shutdown] Watcher stop failed: ${err}`));
1174
444
  stopAnalyzer(log);
1175
- // Close HTTP server
1176
- if (state?.server) {
1177
- state.server.close();
1178
- state.server = null;
1179
- }
1180
- // Kill all spawned child processes (claude CLI, etc.)
445
+ const serverPromise = state?.server
446
+ ? new Promise((resolve) => {
447
+ state.server.close(() => resolve());
448
+ state.server = null;
449
+ })
450
+ : Promise.resolve();
1181
451
  processRegistry.killAll();
1182
- // Cleanup DB connections
1183
- closeStorageDispatcher().catch((err) => log(`[shutdown] Storage close failed: ${err}`));
452
+ // Wait for watcher and HTTP server to finish before closing storage,
453
+ // so in-flight requests don't hit a closed DB.
454
+ await Promise.allSettled([watcherPromise, serverPromise]);
455
+ await closeStorageDispatcher().catch((err) => log(`[shutdown] Storage close failed: ${err}`));
1184
456
  cleanupEmbeddings();
457
+ await cleanupReranker().catch((err) => log(`[shutdown] Reranker cleanup failed: ${err}`));
1185
458
  cleanupQualityScoring();
1186
459
  closeDb();
1187
460
  closeGlobalDb();
1188
- // Remove PID and port files
1189
461
  try {
1190
462
  fs.unlinkSync(getDaemonPidFile());
1191
463
  }
1192
464
  catch (err) {
1193
- if (err.code !== 'ENOENT')
1194
- log(`[shutdown] PID file removal failed: ${err}`);
465
+ if (!isErrnoException(err) || err.code !== 'ENOENT') {
466
+ log(`[shutdown] PID file removal failed: ${getErrorMessage(err)}`);
467
+ }
1195
468
  }
1196
469
  try {
1197
470
  fs.unlinkSync(getDaemonPortFile());
1198
471
  }
1199
472
  catch (err) {
1200
- if (err.code !== 'ENOENT')
1201
- log(`[shutdown] Port file removal failed: ${err}`);
473
+ if (!isErrnoException(err) || err.code !== 'ENOENT') {
474
+ log(`[shutdown] Port file removal failed: ${getErrorMessage(err)}`);
475
+ }
1202
476
  }
1203
477
  log('[daemon] Shutdown complete');
1204
478
  process.exit(0);
1205
479
  }
1206
- // ============================================================================
1207
- // Test Helpers (exported for unit testing)
1208
- // ============================================================================
1209
480
  /** @internal Initialize module state for testing without starting HTTP server */
1210
481
  export function _initTestState(cwd = process.cwd()) {
1211
482
  sessionManager = createSessionManager();
@@ -1216,13 +487,10 @@ export function _resetTestState() {
1216
487
  sessionManager = null;
1217
488
  idleWatcher = null;
1218
489
  state = null;
1219
- briefingCache.clear();
1220
- briefingGenerationInProgress.clear();
490
+ resetReflectionRoutesState();
491
+ resetMemoryRoutesState();
492
+ resetSearchRoutesState();
1221
493
  }
1222
- // ============================================================================
1223
- // CLI Entry Point
1224
- // ============================================================================
1225
- // If run directly, start daemon
1226
494
  if (process.argv[1]?.endsWith('service.js') || process.argv[1]?.endsWith('service.ts')) {
1227
495
  startDaemon()
1228
496
  .then(({ port, pid }) => {