@vinaes/succ 1.4.0 → 1.5.37

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 (683) hide show
  1. package/README.md +64 -10
  2. package/dist/cli.js +71 -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 +177 -935
  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 +149 -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/working-memory-pipeline.d.ts.map +1 -1
  569. package/dist/lib/working-memory-pipeline.js +12 -3
  570. package/dist/lib/working-memory-pipeline.js.map +1 -1
  571. package/dist/lib/worktree-detect.d.ts +43 -0
  572. package/dist/lib/worktree-detect.d.ts.map +1 -0
  573. package/dist/lib/worktree-detect.js +154 -0
  574. package/dist/lib/worktree-detect.js.map +1 -0
  575. package/dist/lsp/client.d.ts +96 -0
  576. package/dist/lsp/client.d.ts.map +1 -0
  577. package/dist/lsp/client.js +435 -0
  578. package/dist/lsp/client.js.map +1 -0
  579. package/dist/lsp/installer.d.ts +39 -0
  580. package/dist/lsp/installer.d.ts.map +1 -0
  581. package/dist/lsp/installer.js +275 -0
  582. package/dist/lsp/installer.js.map +1 -0
  583. package/dist/lsp/manager.d.ts +62 -0
  584. package/dist/lsp/manager.d.ts.map +1 -0
  585. package/dist/lsp/manager.js +234 -0
  586. package/dist/lsp/manager.js.map +1 -0
  587. package/dist/lsp/servers.d.ts +52 -0
  588. package/dist/lsp/servers.d.ts.map +1 -0
  589. package/dist/lsp/servers.js +162 -0
  590. package/dist/lsp/servers.js.map +1 -0
  591. package/dist/mcp/helpers.d.ts.map +1 -1
  592. package/dist/mcp/helpers.js +8 -2
  593. package/dist/mcp/helpers.js.map +1 -1
  594. package/dist/mcp/profile.js +1 -1
  595. package/dist/mcp/server.d.ts +3 -2
  596. package/dist/mcp/server.d.ts.map +1 -1
  597. package/dist/mcp/server.js +19 -7
  598. package/dist/mcp/server.js.map +1 -1
  599. package/dist/mcp/tools/config.d.ts.map +1 -1
  600. package/dist/mcp/tools/config.js +28 -118
  601. package/dist/mcp/tools/config.js.map +1 -1
  602. package/dist/mcp/tools/dead-end.d.ts.map +1 -1
  603. package/dist/mcp/tools/dead-end.js +4 -3
  604. package/dist/mcp/tools/dead-end.js.map +1 -1
  605. package/dist/mcp/tools/debug.d.ts.map +1 -1
  606. package/dist/mcp/tools/debug.js +27 -112
  607. package/dist/mcp/tools/debug.js.map +1 -1
  608. package/dist/mcp/tools/graph.d.ts.map +1 -1
  609. package/dist/mcp/tools/graph.js +164 -176
  610. package/dist/mcp/tools/graph.js.map +1 -1
  611. package/dist/mcp/tools/indexing.d.ts +1 -1
  612. package/dist/mcp/tools/indexing.d.ts.map +1 -1
  613. package/dist/mcp/tools/indexing.js +63 -164
  614. package/dist/mcp/tools/indexing.js.map +1 -1
  615. package/dist/mcp/tools/memory/forget.d.ts +3 -0
  616. package/dist/mcp/tools/memory/forget.d.ts.map +1 -0
  617. package/dist/mcp/tools/memory/forget.js +175 -0
  618. package/dist/mcp/tools/memory/forget.js.map +1 -0
  619. package/dist/mcp/tools/memory/memory-helpers.d.ts +45 -0
  620. package/dist/mcp/tools/memory/memory-helpers.d.ts.map +1 -0
  621. package/dist/mcp/tools/memory/memory-helpers.js +291 -0
  622. package/dist/mcp/tools/memory/memory-helpers.js.map +1 -0
  623. package/dist/mcp/tools/memory/recall.d.ts +3 -0
  624. package/dist/mcp/tools/memory/recall.d.ts.map +1 -0
  625. package/dist/mcp/tools/memory/recall.js +495 -0
  626. package/dist/mcp/tools/memory/recall.js.map +1 -0
  627. package/dist/mcp/tools/memory/remember.d.ts +3 -0
  628. package/dist/mcp/tools/memory/remember.d.ts.map +1 -0
  629. package/dist/mcp/tools/memory/remember.js +256 -0
  630. package/dist/mcp/tools/memory/remember.js.map +1 -0
  631. package/dist/mcp/tools/memory/temporal-query.d.ts +8 -0
  632. package/dist/mcp/tools/memory/temporal-query.d.ts.map +1 -0
  633. package/dist/mcp/tools/memory/temporal-query.js +68 -0
  634. package/dist/mcp/tools/memory/temporal-query.js.map +1 -0
  635. package/dist/mcp/tools/memory.d.ts +0 -11
  636. package/dist/mcp/tools/memory.d.ts.map +1 -1
  637. package/dist/mcp/tools/memory.js +6 -1228
  638. package/dist/mcp/tools/memory.js.map +1 -1
  639. package/dist/mcp/tools/prd.d.ts.map +1 -1
  640. package/dist/mcp/tools/prd.js +19 -70
  641. package/dist/mcp/tools/prd.js.map +1 -1
  642. package/dist/mcp/tools/review.d.ts +8 -0
  643. package/dist/mcp/tools/review.d.ts.map +1 -0
  644. package/dist/mcp/tools/review.js +133 -0
  645. package/dist/mcp/tools/review.js.map +1 -0
  646. package/dist/mcp/tools/search.d.ts.map +1 -1
  647. package/dist/mcp/tools/search.js +79 -8
  648. package/dist/mcp/tools/search.js.map +1 -1
  649. package/dist/mcp/tools/status.d.ts.map +1 -1
  650. package/dist/mcp/tools/status.js +34 -75
  651. package/dist/mcp/tools/status.js.map +1 -1
  652. package/dist/mcp/tools/web-fetch.d.ts.map +1 -1
  653. package/dist/mcp/tools/web-fetch.js +5 -1
  654. package/dist/mcp/tools/web-fetch.js.map +1 -1
  655. package/dist/mcp/tools/web-search.d.ts.map +1 -1
  656. package/dist/mcp/tools/web-search.js +25 -103
  657. package/dist/mcp/tools/web-search.js.map +1 -1
  658. package/dist/prompts/guardrails.d.ts +14 -0
  659. package/dist/prompts/guardrails.d.ts.map +1 -0
  660. package/dist/prompts/guardrails.js +115 -0
  661. package/dist/prompts/guardrails.js.map +1 -0
  662. package/dist/prompts/index.d.ts +2 -1
  663. package/dist/prompts/index.d.ts.map +1 -1
  664. package/dist/prompts/index.js +3 -1
  665. package/dist/prompts/index.js.map +1 -1
  666. package/dist/prompts/prd.d.ts +0 -2
  667. package/dist/prompts/prd.d.ts.map +1 -1
  668. package/dist/prompts/prd.js +0 -2
  669. package/dist/prompts/prd.js.map +1 -1
  670. package/hooks/core/__tests__/adapter.test.cjs +340 -0
  671. package/hooks/core/adapter.cjs +463 -0
  672. package/hooks/core/config.cjs +83 -0
  673. package/hooks/core/daemon-boot.cjs +140 -0
  674. package/hooks/core/log.cjs +41 -0
  675. package/hooks/core/worktree.cjs +119 -0
  676. package/hooks/succ-post-tool.cjs +198 -134
  677. package/hooks/succ-pre-compact.cjs +262 -0
  678. package/hooks/succ-pre-tool.cjs +526 -182
  679. package/hooks/succ-session-end.cjs +40 -64
  680. package/hooks/succ-session-start.cjs +484 -430
  681. package/hooks/succ-stop-reflection.cjs +36 -62
  682. package/hooks/succ-user-prompt.cjs +137 -180
  683. 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,120 @@ 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
- }
862
- }
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,
880
- });
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}`);
256
+ return handler(body, searchParams);
1009
257
  }
1010
- // ============================================================================
1011
- // Daemon Lifecycle
1012
- // ============================================================================
1013
258
  export async function startDaemon() {
1014
259
  if (state?.server) {
1015
260
  return { port: state.port, pid: process.pid };
1016
261
  }
1017
- // Check if another daemon is already running (prevent duplicate processes)
1018
262
  const existingPidFile = getDaemonPidFile();
1019
263
  if (fs.existsSync(existingPidFile)) {
1020
264
  try {
1021
265
  const existingPid = parseInt(fs.readFileSync(existingPidFile, 'utf8').trim(), 10);
1022
266
  if (existingPid && existingPid !== process.pid) {
1023
- // Check if process is actually running
1024
267
  try {
1025
- process.kill(existingPid, 0); // Signal 0 = check if process exists
1026
- // Process exists, read port and return
268
+ process.kill(existingPid, 0);
1027
269
  const portFile = getDaemonPortFile();
1028
270
  if (fs.existsSync(portFile)) {
1029
271
  const port = parseInt(fs.readFileSync(portFile, 'utf8').trim(), 10);
1030
272
  log(`[daemon] Another daemon already running (pid=${existingPid}, port=${port})`);
1031
- process.exit(0); // Exit silently - another daemon is handling things
273
+ process.exit(0);
1032
274
  }
1033
275
  }
1034
- catch {
1035
- // Process doesn't exist, clean up stale files
276
+ catch (error) {
277
+ logWarn('service', 'Failed to signal existing daemon process for liveness check', {
278
+ error: error instanceof Error ? error.message : String(error),
279
+ });
1036
280
  log(`[daemon] Cleaning up stale PID file (pid=${existingPid} not running)`);
1037
281
  fs.unlinkSync(existingPidFile);
1038
282
  const portFile = getDaemonPortFile();
@@ -1051,15 +295,10 @@ export async function startDaemon() {
1051
295
  const cwd = getProjectRoot();
1052
296
  const watcherConfig = getIdleWatcherConfig();
1053
297
  getIdleReflectionConfig();
1054
- // Initialize storage dispatcher (routes to SQLite or PG based on config)
1055
298
  await initStorageDispatcher();
1056
- // Initialize session manager
1057
299
  sessionManager = createSessionManager();
1058
- // Load token budgets from previous daemon run
1059
300
  loadBudgets();
1060
- // Clean up stale observation files (>48h)
1061
- cleanupStaleObservations();
1062
- // Create HTTP server
301
+ initReflectionMaintenance();
1063
302
  const server = http.createServer((req, res) => {
1064
303
  handleRequest(req, res).catch((err) => {
1065
304
  log(`[http] Unhandled error: ${err.message}`);
@@ -1067,14 +306,25 @@ export async function startDaemon() {
1067
306
  res.end();
1068
307
  });
1069
308
  });
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++) {
309
+ // Try stable port first (config or hash-based), then fall back to scan
310
+ const config = getConfig();
311
+ const stablePort = config.daemon?.port ?? getStablePort(cwd);
312
+ const fallbackStart = config.daemon?.port_range_start ?? DEFAULT_PORT_RANGE_START;
313
+ let port = stablePort;
314
+ let portAttempts = 0;
315
+ const maxAttempts = 1 + MAX_PORT_ATTEMPTS; // 1 stable + 100 fallback
316
+ for (let i = 0; i < maxAttempts; i++) {
1074
317
  await new Promise((resolve, reject) => {
1075
318
  server.once('error', (err) => {
1076
319
  if (err.code === 'EADDRINUSE') {
1077
- port++;
320
+ portAttempts++;
321
+ if (portAttempts === 1) {
322
+ // Stable port busy — switch to fallback range
323
+ port = fallbackStart;
324
+ }
325
+ else {
326
+ port++;
327
+ }
1078
328
  resolve();
1079
329
  }
1080
330
  else {
@@ -1090,33 +340,28 @@ export async function startDaemon() {
1090
340
  }
1091
341
  }
1092
342
  if (!server.listening) {
1093
- throw new NetworkError(`Could not find available port in range ${portStart}-${portStart + MAX_PORT_ATTEMPTS}`);
343
+ throw new NetworkError(`Could not find available port (tried ${stablePort}, then ${fallbackStart}-${fallbackStart + MAX_PORT_ATTEMPTS})`);
1094
344
  }
1095
- // Save state
1096
345
  state = {
1097
346
  cwd,
1098
347
  startedAt: Date.now(),
1099
348
  port,
1100
349
  server,
1101
350
  };
1102
- // Write PID and port files
1103
351
  fs.writeFileSync(getDaemonPidFile(), String(process.pid));
1104
352
  fs.writeFileSync(getDaemonPortFile(), String(port));
1105
- // Start idle watcher with briefing pre-generation
1106
353
  idleWatcher = createIdleWatcher({
1107
354
  sessionManager,
1108
- onIdle: handleReflection,
1109
- onPreGenerateBriefing: preGenerateBriefing,
355
+ onIdle: (sessionId, session) => performReflection(createRouteContext(), sessionId, session),
356
+ onPreGenerateBriefing: (sessionId, transcriptPath) => preGenerateBriefing(createRouteContext(), sessionId, transcriptPath),
1110
357
  checkIntervalSeconds: watcherConfig.check_interval,
1111
358
  idleMinutes: watcherConfig.idle_minutes,
1112
359
  reflectionCooldownMinutes: watcherConfig.reflection_cooldown_minutes,
1113
- preGenerateIdleSeconds: 120, // Pre-generate briefing after 2 min idle
360
+ preGenerateIdleSeconds: 120,
1114
361
  log,
1115
362
  });
1116
363
  idleWatcher.start();
1117
364
  log(`[daemon] Started on port ${port} (pid=${process.pid})`);
1118
- // Auto-start watch service if configured
1119
- const config = getConfig();
1120
365
  if (config.daemon?.watch?.auto_start) {
1121
366
  const watchConfig = config.daemon.watch;
1122
367
  await startWatcher({
@@ -1126,7 +371,6 @@ export async function startDaemon() {
1126
371
  }, log);
1127
372
  log(`[daemon] Auto-started watch service`);
1128
373
  }
1129
- // Auto-start analyze service if configured
1130
374
  if (config.daemon?.analyze?.auto_start) {
1131
375
  const analyzeConfig = config.daemon.analyze;
1132
376
  startAnalyzer({
@@ -1135,7 +379,6 @@ export async function startDaemon() {
1135
379
  }, log);
1136
380
  log(`[daemon] Auto-started analyze service`);
1137
381
  }
1138
- // Setup graceful shutdown
1139
382
  setupShutdownHandlers();
1140
383
  return { port, pid: process.pid };
1141
384
  }
@@ -1143,14 +386,18 @@ function setupShutdownHandlers() {
1143
386
  const shutdown = () => shutdownDaemon();
1144
387
  process.on('SIGTERM', shutdown);
1145
388
  process.on('SIGINT', shutdown);
1146
- // SIGHUP only exists on Unix
1147
389
  if (process.platform !== 'win32') {
1148
390
  process.on('SIGHUP', shutdown);
1149
391
  }
392
+ // Prevent silent crashes — log and keep running
393
+ process.on('uncaughtException', (err) => {
394
+ log(`[daemon] UNCAUGHT EXCEPTION: ${err.message}\n${err.stack}`);
395
+ });
396
+ process.on('unhandledRejection', (reason) => {
397
+ const msg = reason instanceof Error ? `${reason.message}\n${reason.stack}` : String(reason);
398
+ log(`[daemon] UNHANDLED REJECTION: ${msg}`);
399
+ });
1150
400
  }
1151
- /**
1152
- * Check if daemon should shutdown (no sessions and no pending work)
1153
- */
1154
401
  function checkShutdown() {
1155
402
  if (sessionManager?.canShutdown()) {
1156
403
  log(`[daemon] No more sessions and no pending work, scheduling shutdown`);
@@ -1158,54 +405,52 @@ function checkShutdown() {
1158
405
  if (sessionManager?.canShutdown()) {
1159
406
  shutdownDaemon();
1160
407
  }
1161
- }, 5000); // Give 5 seconds for new sessions to connect
408
+ }, 5000);
1162
409
  }
1163
410
  }
1164
- export function shutdownDaemon() {
411
+ export async function shutdownDaemon() {
1165
412
  log('[daemon] Shutting down...');
1166
- // Stop idle watcher
1167
413
  if (idleWatcher) {
1168
414
  idleWatcher.stop();
1169
415
  idleWatcher = null;
1170
416
  }
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
417
+ const watcherPromise = stopWatcher(log).catch((err) => log(`[shutdown] Watcher stop failed: ${err}`));
1174
418
  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.)
419
+ const serverPromise = state?.server
420
+ ? new Promise((resolve) => {
421
+ state.server.close(() => resolve());
422
+ state.server = null;
423
+ })
424
+ : Promise.resolve();
1181
425
  processRegistry.killAll();
1182
- // Cleanup DB connections
1183
- closeStorageDispatcher().catch((err) => log(`[shutdown] Storage close failed: ${err}`));
426
+ // Wait for watcher and HTTP server to finish before closing storage,
427
+ // so in-flight requests don't hit a closed DB.
428
+ await Promise.allSettled([watcherPromise, serverPromise]);
429
+ await closeStorageDispatcher().catch((err) => log(`[shutdown] Storage close failed: ${err}`));
1184
430
  cleanupEmbeddings();
431
+ await cleanupReranker().catch((err) => log(`[shutdown] Reranker cleanup failed: ${err}`));
1185
432
  cleanupQualityScoring();
1186
433
  closeDb();
1187
434
  closeGlobalDb();
1188
- // Remove PID and port files
1189
435
  try {
1190
436
  fs.unlinkSync(getDaemonPidFile());
1191
437
  }
1192
438
  catch (err) {
1193
- if (err.code !== 'ENOENT')
1194
- log(`[shutdown] PID file removal failed: ${err}`);
439
+ if (!isErrnoException(err) || err.code !== 'ENOENT') {
440
+ log(`[shutdown] PID file removal failed: ${getErrorMessage(err)}`);
441
+ }
1195
442
  }
1196
443
  try {
1197
444
  fs.unlinkSync(getDaemonPortFile());
1198
445
  }
1199
446
  catch (err) {
1200
- if (err.code !== 'ENOENT')
1201
- log(`[shutdown] Port file removal failed: ${err}`);
447
+ if (!isErrnoException(err) || err.code !== 'ENOENT') {
448
+ log(`[shutdown] Port file removal failed: ${getErrorMessage(err)}`);
449
+ }
1202
450
  }
1203
451
  log('[daemon] Shutdown complete');
1204
452
  process.exit(0);
1205
453
  }
1206
- // ============================================================================
1207
- // Test Helpers (exported for unit testing)
1208
- // ============================================================================
1209
454
  /** @internal Initialize module state for testing without starting HTTP server */
1210
455
  export function _initTestState(cwd = process.cwd()) {
1211
456
  sessionManager = createSessionManager();
@@ -1216,13 +461,10 @@ export function _resetTestState() {
1216
461
  sessionManager = null;
1217
462
  idleWatcher = null;
1218
463
  state = null;
1219
- briefingCache.clear();
1220
- briefingGenerationInProgress.clear();
464
+ resetReflectionRoutesState();
465
+ resetMemoryRoutesState();
466
+ resetSearchRoutesState();
1221
467
  }
1222
- // ============================================================================
1223
- // CLI Entry Point
1224
- // ============================================================================
1225
- // If run directly, start daemon
1226
468
  if (process.argv[1]?.endsWith('service.js') || process.argv[1]?.endsWith('service.ts')) {
1227
469
  startDaemon()
1228
470
  .then(({ port, pid }) => {