@vinaes/succ 1.4.0 → 1.5.42

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (687) hide show
  1. package/README.md +64 -10
  2. package/dist/cli.js +81 -1
  3. package/dist/cli.js.map +1 -1
  4. package/dist/commands/agents-md.d.ts.map +1 -1
  5. package/dist/commands/agents-md.js +3 -2
  6. package/dist/commands/agents-md.js.map +1 -1
  7. package/dist/commands/analyze-profile.d.ts.map +1 -1
  8. package/dist/commands/analyze-profile.js +32 -8
  9. package/dist/commands/analyze-profile.js.map +1 -1
  10. package/dist/commands/analyze-recursive.d.ts.map +1 -1
  11. package/dist/commands/analyze-recursive.js +6 -2
  12. package/dist/commands/analyze-recursive.js.map +1 -1
  13. package/dist/commands/analyze-utils.d.ts.map +1 -1
  14. package/dist/commands/analyze-utils.js +17 -4
  15. package/dist/commands/analyze-utils.js.map +1 -1
  16. package/dist/commands/benchmark-quality.d.ts.map +1 -1
  17. package/dist/commands/benchmark-quality.js +11 -4
  18. package/dist/commands/benchmark-quality.js.map +1 -1
  19. package/dist/commands/benchmark-sqlite-vec.d.ts.map +1 -1
  20. package/dist/commands/benchmark-sqlite-vec.js +4 -0
  21. package/dist/commands/benchmark-sqlite-vec.js.map +1 -1
  22. package/dist/commands/benchmark.d.ts.map +1 -1
  23. package/dist/commands/benchmark.js +5 -1
  24. package/dist/commands/benchmark.js.map +1 -1
  25. package/dist/commands/codex-chat.d.ts +8 -0
  26. package/dist/commands/codex-chat.d.ts.map +1 -0
  27. package/dist/commands/codex-chat.js +161 -0
  28. package/dist/commands/codex-chat.js.map +1 -0
  29. package/dist/commands/config.d.ts.map +1 -1
  30. package/dist/commands/config.js +32 -4
  31. package/dist/commands/config.js.map +1 -1
  32. package/dist/commands/daemon.d.ts.map +1 -1
  33. package/dist/commands/daemon.js +13 -4
  34. package/dist/commands/daemon.js.map +1 -1
  35. package/dist/commands/index-code.d.ts +4 -0
  36. package/dist/commands/index-code.d.ts.map +1 -1
  37. package/dist/commands/index-code.js +1 -1
  38. package/dist/commands/index-code.js.map +1 -1
  39. package/dist/commands/init.d.ts.map +1 -1
  40. package/dist/commands/init.js +305 -203
  41. package/dist/commands/init.js.map +1 -1
  42. package/dist/commands/memories.d.ts.map +1 -1
  43. package/dist/commands/memories.js +25 -14
  44. package/dist/commands/memories.js.map +1 -1
  45. package/dist/commands/progress.d.ts.map +1 -1
  46. package/dist/commands/progress.js +3 -2
  47. package/dist/commands/progress.js.map +1 -1
  48. package/dist/commands/reindex.d.ts.map +1 -1
  49. package/dist/commands/reindex.js +54 -36
  50. package/dist/commands/reindex.js.map +1 -1
  51. package/dist/commands/retention.d.ts.map +1 -1
  52. package/dist/commands/retention.js +7 -5
  53. package/dist/commands/retention.js.map +1 -1
  54. package/dist/commands/scan-code.d.ts +76 -0
  55. package/dist/commands/scan-code.d.ts.map +1 -0
  56. package/dist/commands/scan-code.js +385 -0
  57. package/dist/commands/scan-code.js.map +1 -0
  58. package/dist/commands/score.d.ts.map +1 -1
  59. package/dist/commands/score.js +3 -2
  60. package/dist/commands/score.js.map +1 -1
  61. package/dist/commands/session.d.ts +33 -0
  62. package/dist/commands/session.d.ts.map +1 -0
  63. package/dist/commands/session.js +163 -0
  64. package/dist/commands/session.js.map +1 -0
  65. package/dist/commands/setup.d.ts.map +1 -1
  66. package/dist/commands/setup.js +254 -15
  67. package/dist/commands/setup.js.map +1 -1
  68. package/dist/commands/soul.js +3 -2
  69. package/dist/commands/soul.js.map +1 -1
  70. package/dist/commands/status.d.ts.map +1 -1
  71. package/dist/commands/status.js +14 -5
  72. package/dist/commands/status.js.map +1 -1
  73. package/dist/commands/watch.d.ts.map +1 -1
  74. package/dist/commands/watch.js +13 -4
  75. package/dist/commands/watch.js.map +1 -1
  76. package/dist/daemon/analyzer.d.ts.map +1 -1
  77. package/dist/daemon/analyzer.js +21 -5
  78. package/dist/daemon/analyzer.js.map +1 -1
  79. package/dist/daemon/client.d.ts.map +1 -1
  80. package/dist/daemon/client.js +32 -8
  81. package/dist/daemon/client.js.map +1 -1
  82. package/dist/daemon/routes/analyzer.d.ts +3 -0
  83. package/dist/daemon/routes/analyzer.d.ts.map +1 -0
  84. package/dist/daemon/routes/analyzer.js +27 -0
  85. package/dist/daemon/routes/analyzer.js.map +1 -0
  86. package/dist/daemon/routes/hooks.d.ts +14 -0
  87. package/dist/daemon/routes/hooks.d.ts.map +1 -0
  88. package/dist/daemon/routes/hooks.js +1212 -0
  89. package/dist/daemon/routes/hooks.js.map +1 -0
  90. package/dist/daemon/routes/memory.d.ts +4 -0
  91. package/dist/daemon/routes/memory.d.ts.map +1 -0
  92. package/dist/daemon/routes/memory.js +71 -0
  93. package/dist/daemon/routes/memory.js.map +1 -0
  94. package/dist/daemon/routes/reflection.d.ts +10 -0
  95. package/dist/daemon/routes/reflection.d.ts.map +1 -0
  96. package/dist/daemon/routes/reflection.js +397 -0
  97. package/dist/daemon/routes/reflection.js.map +1 -0
  98. package/dist/daemon/routes/search.d.ts +5 -0
  99. package/dist/daemon/routes/search.d.ts.map +1 -0
  100. package/dist/daemon/routes/search.js +93 -0
  101. package/dist/daemon/routes/search.js.map +1 -0
  102. package/dist/daemon/routes/sessions.d.ts +3 -0
  103. package/dist/daemon/routes/sessions.d.ts.map +1 -0
  104. package/dist/daemon/routes/sessions.js +160 -0
  105. package/dist/daemon/routes/sessions.js.map +1 -0
  106. package/dist/daemon/routes/skills.d.ts +3 -0
  107. package/dist/daemon/routes/skills.d.ts.map +1 -0
  108. package/dist/daemon/routes/skills.js +36 -0
  109. package/dist/daemon/routes/skills.js.map +1 -0
  110. package/dist/daemon/routes/status.d.ts +3 -0
  111. package/dist/daemon/routes/status.d.ts.map +1 -0
  112. package/dist/daemon/routes/status.js +47 -0
  113. package/dist/daemon/routes/status.js.map +1 -0
  114. package/dist/daemon/routes/types.d.ts +240 -0
  115. package/dist/daemon/routes/types.d.ts.map +1 -0
  116. package/dist/daemon/routes/types.js +97 -0
  117. package/dist/daemon/routes/types.js.map +1 -0
  118. package/dist/daemon/routes/versioning.d.ts +27 -0
  119. package/dist/daemon/routes/versioning.d.ts.map +1 -0
  120. package/dist/daemon/routes/versioning.js +44 -0
  121. package/dist/daemon/routes/versioning.js.map +1 -0
  122. package/dist/daemon/routes/watcher.d.ts +3 -0
  123. package/dist/daemon/routes/watcher.d.ts.map +1 -0
  124. package/dist/daemon/routes/watcher.js +28 -0
  125. package/dist/daemon/routes/watcher.js.map +1 -0
  126. package/dist/daemon/service.d.ts +5 -23
  127. package/dist/daemon/service.d.ts.map +1 -1
  128. package/dist/daemon/service.js +201 -933
  129. package/dist/daemon/service.js.map +1 -1
  130. package/dist/daemon/session-processor.d.ts +4 -8
  131. package/dist/daemon/session-processor.d.ts.map +1 -1
  132. package/dist/daemon/session-processor.js +39 -38
  133. package/dist/daemon/session-processor.js.map +1 -1
  134. package/dist/lib/ai-readiness.d.ts.map +1 -1
  135. package/dist/lib/ai-readiness.js +33 -8
  136. package/dist/lib/ai-readiness.js.map +1 -1
  137. package/dist/lib/analyze-state.d.ts.map +1 -1
  138. package/dist/lib/analyze-state.js +25 -3
  139. package/dist/lib/analyze-state.js.map +1 -1
  140. package/dist/lib/auto-memory/consolidation.d.ts +41 -0
  141. package/dist/lib/auto-memory/consolidation.d.ts.map +1 -0
  142. package/dist/lib/auto-memory/consolidation.js +151 -0
  143. package/dist/lib/auto-memory/consolidation.js.map +1 -0
  144. package/dist/lib/bpe.d.ts.map +1 -1
  145. package/dist/lib/bpe.js +9 -10
  146. package/dist/lib/bpe.js.map +1 -1
  147. package/dist/lib/brain-export.d.ts +65 -0
  148. package/dist/lib/brain-export.d.ts.map +1 -0
  149. package/dist/lib/brain-export.js +413 -0
  150. package/dist/lib/brain-export.js.map +1 -0
  151. package/dist/lib/checkpoint.d.ts.map +1 -1
  152. package/dist/lib/checkpoint.js +22 -6
  153. package/dist/lib/checkpoint.js.map +1 -1
  154. package/dist/lib/chunker.d.ts.map +1 -1
  155. package/dist/lib/chunker.js +6 -1
  156. package/dist/lib/chunker.js.map +1 -1
  157. package/dist/lib/claude-ws-transport.d.ts.map +1 -1
  158. package/dist/lib/claude-ws-transport.js +12 -4
  159. package/dist/lib/claude-ws-transport.js.map +1 -1
  160. package/dist/lib/command-safety.d.ts +64 -0
  161. package/dist/lib/command-safety.d.ts.map +1 -0
  162. package/dist/lib/command-safety.js +625 -0
  163. package/dist/lib/command-safety.js.map +1 -0
  164. package/dist/lib/compact-briefing.d.ts.map +1 -1
  165. package/dist/lib/compact-briefing.js +10 -13
  166. package/dist/lib/compact-briefing.js.map +1 -1
  167. package/dist/lib/config-defaults.d.ts.map +1 -1
  168. package/dist/lib/config-defaults.js +3 -0
  169. package/dist/lib/config-defaults.js.map +1 -1
  170. package/dist/lib/config-display.d.ts +4 -0
  171. package/dist/lib/config-display.d.ts.map +1 -1
  172. package/dist/lib/config-display.js +6 -1
  173. package/dist/lib/config-display.js.map +1 -1
  174. package/dist/lib/config-types.d.ts +156 -0
  175. package/dist/lib/config-types.d.ts.map +1 -1
  176. package/dist/lib/config-validation.d.ts.map +1 -1
  177. package/dist/lib/config-validation.js +5 -0
  178. package/dist/lib/config-validation.js.map +1 -1
  179. package/dist/lib/config.d.ts.map +1 -1
  180. package/dist/lib/config.js +92 -9
  181. package/dist/lib/config.js.map +1 -1
  182. package/dist/lib/consolidate.d.ts.map +1 -1
  183. package/dist/lib/consolidate.js +66 -47
  184. package/dist/lib/consolidate.js.map +1 -1
  185. package/dist/lib/content-sanitizer.d.ts +29 -0
  186. package/dist/lib/content-sanitizer.d.ts.map +1 -0
  187. package/dist/lib/content-sanitizer.js +72 -0
  188. package/dist/lib/content-sanitizer.js.map +1 -0
  189. package/dist/lib/cross-repo.d.ts +44 -0
  190. package/dist/lib/cross-repo.d.ts.map +1 -0
  191. package/dist/lib/cross-repo.js +108 -0
  192. package/dist/lib/cross-repo.js.map +1 -0
  193. package/dist/lib/daemon-port.d.ts +12 -0
  194. package/dist/lib/daemon-port.d.ts.map +1 -0
  195. package/dist/lib/daemon-port.js +20 -0
  196. package/dist/lib/daemon-port.js.map +1 -0
  197. package/dist/lib/db/auto-memory.d.ts +40 -0
  198. package/dist/lib/db/auto-memory.d.ts.map +1 -0
  199. package/dist/lib/db/auto-memory.js +74 -0
  200. package/dist/lib/db/auto-memory.js.map +1 -0
  201. package/dist/lib/db/bm25-indexes.d.ts.map +1 -1
  202. package/dist/lib/db/bm25-indexes.js +16 -4
  203. package/dist/lib/db/bm25-indexes.js.map +1 -1
  204. package/dist/lib/db/documents.d.ts.map +1 -1
  205. package/dist/lib/db/documents.js +4 -1
  206. package/dist/lib/db/documents.js.map +1 -1
  207. package/dist/lib/db/global-memories.d.ts +2 -10
  208. package/dist/lib/db/global-memories.d.ts.map +1 -1
  209. package/dist/lib/db/global-memories.js +13 -6
  210. package/dist/lib/db/global-memories.js.map +1 -1
  211. package/dist/lib/db/graph.d.ts +5 -1
  212. package/dist/lib/db/graph.d.ts.map +1 -1
  213. package/dist/lib/db/graph.js +38 -8
  214. package/dist/lib/db/graph.js.map +1 -1
  215. package/dist/lib/db/hybrid-search.d.ts +4 -2
  216. package/dist/lib/db/hybrid-search.d.ts.map +1 -1
  217. package/dist/lib/db/hybrid-search.js +29 -11
  218. package/dist/lib/db/hybrid-search.js.map +1 -1
  219. package/dist/lib/db/index.d.ts +6 -1
  220. package/dist/lib/db/index.d.ts.map +1 -1
  221. package/dist/lib/db/index.js +5 -1
  222. package/dist/lib/db/index.js.map +1 -1
  223. package/dist/lib/db/memories.d.ts +19 -14
  224. package/dist/lib/db/memories.d.ts.map +1 -1
  225. package/dist/lib/db/memories.js +100 -37
  226. package/dist/lib/db/memories.js.map +1 -1
  227. package/dist/lib/db/parse-helpers.d.ts +14 -0
  228. package/dist/lib/db/parse-helpers.d.ts.map +1 -0
  229. package/dist/lib/db/parse-helpers.js +59 -0
  230. package/dist/lib/db/parse-helpers.js.map +1 -0
  231. package/dist/lib/db/recall-events.d.ts +49 -0
  232. package/dist/lib/db/recall-events.d.ts.map +1 -0
  233. package/dist/lib/db/recall-events.js +196 -0
  234. package/dist/lib/db/recall-events.js.map +1 -0
  235. package/dist/lib/db/retention.d.ts +4 -3
  236. package/dist/lib/db/retention.d.ts.map +1 -1
  237. package/dist/lib/db/retention.js +12 -1
  238. package/dist/lib/db/retention.js.map +1 -1
  239. package/dist/lib/db/schema.d.ts +2 -0
  240. package/dist/lib/db/schema.d.ts.map +1 -1
  241. package/dist/lib/db/schema.js +140 -80
  242. package/dist/lib/db/schema.js.map +1 -1
  243. package/dist/lib/db/skills.d.ts.map +1 -1
  244. package/dist/lib/db/skills.js +10 -6
  245. package/dist/lib/db/skills.js.map +1 -1
  246. package/dist/lib/diff-brain.d.ts +24 -0
  247. package/dist/lib/diff-brain.d.ts.map +1 -0
  248. package/dist/lib/diff-brain.js +114 -0
  249. package/dist/lib/diff-brain.js.map +1 -0
  250. package/dist/lib/diff-parser.d.ts +74 -0
  251. package/dist/lib/diff-parser.d.ts.map +1 -0
  252. package/dist/lib/diff-parser.js +200 -0
  253. package/dist/lib/diff-parser.js.map +1 -0
  254. package/dist/lib/embedding-pool.d.ts.map +1 -1
  255. package/dist/lib/embedding-pool.js +5 -1
  256. package/dist/lib/embedding-pool.js.map +1 -1
  257. package/dist/lib/embeddings.d.ts +12 -0
  258. package/dist/lib/embeddings.d.ts.map +1 -1
  259. package/dist/lib/embeddings.js +77 -19
  260. package/dist/lib/embeddings.js.map +1 -1
  261. package/dist/lib/errors.d.ts +2 -0
  262. package/dist/lib/errors.d.ts.map +1 -1
  263. package/dist/lib/errors.js +4 -0
  264. package/dist/lib/errors.js.map +1 -1
  265. package/dist/lib/fault-logger.d.ts.map +1 -1
  266. package/dist/lib/fault-logger.js +22 -7
  267. package/dist/lib/fault-logger.js.map +1 -1
  268. package/dist/lib/git/co-change.d.ts +39 -0
  269. package/dist/lib/git/co-change.d.ts.map +1 -0
  270. package/dist/lib/git/co-change.js +139 -0
  271. package/dist/lib/git/co-change.js.map +1 -0
  272. package/dist/lib/graph/bridge-edges.d.ts +93 -0
  273. package/dist/lib/graph/bridge-edges.d.ts.map +1 -0
  274. package/dist/lib/graph/bridge-edges.js +276 -0
  275. package/dist/lib/graph/bridge-edges.js.map +1 -0
  276. package/dist/lib/graph/centrality.d.ts +11 -0
  277. package/dist/lib/graph/centrality.d.ts.map +1 -1
  278. package/dist/lib/graph/centrality.js +51 -3
  279. package/dist/lib/graph/centrality.js.map +1 -1
  280. package/dist/lib/graph/cleanup.d.ts.map +1 -1
  281. package/dist/lib/graph/cleanup.js +2 -1
  282. package/dist/lib/graph/cleanup.js.map +1 -1
  283. package/dist/lib/graph/community-detection.d.ts +17 -2
  284. package/dist/lib/graph/community-detection.d.ts.map +1 -1
  285. package/dist/lib/graph/community-detection.js +147 -48
  286. package/dist/lib/graph/community-detection.js.map +1 -1
  287. package/dist/lib/graph/community-summaries.d.ts +26 -0
  288. package/dist/lib/graph/community-summaries.d.ts.map +1 -0
  289. package/dist/lib/graph/community-summaries.js +130 -0
  290. package/dist/lib/graph/community-summaries.js.map +1 -0
  291. package/dist/lib/graph/contextual-proximity.d.ts.map +1 -1
  292. package/dist/lib/graph/contextual-proximity.js +11 -4
  293. package/dist/lib/graph/contextual-proximity.js.map +1 -1
  294. package/dist/lib/graph/graphology-bridge.d.ts +101 -0
  295. package/dist/lib/graph/graphology-bridge.d.ts.map +1 -0
  296. package/dist/lib/graph/graphology-bridge.js +488 -0
  297. package/dist/lib/graph/graphology-bridge.js.map +1 -0
  298. package/dist/lib/graph/llm-relations.d.ts.map +1 -1
  299. package/dist/lib/graph/llm-relations.js +27 -5
  300. package/dist/lib/graph/llm-relations.js.map +1 -1
  301. package/dist/lib/graph-export.d.ts.map +1 -1
  302. package/dist/lib/graph-export.js +2 -2
  303. package/dist/lib/graph-export.js.map +1 -1
  304. package/dist/lib/graph-scheduler.d.ts +0 -5
  305. package/dist/lib/graph-scheduler.d.ts.map +1 -1
  306. package/dist/lib/graph-scheduler.js +5 -1
  307. package/dist/lib/graph-scheduler.js.map +1 -1
  308. package/dist/lib/guardrails.d.ts +50 -0
  309. package/dist/lib/guardrails.d.ts.map +1 -0
  310. package/dist/lib/guardrails.js +502 -0
  311. package/dist/lib/guardrails.js.map +1 -0
  312. package/dist/lib/hook-rules.d.ts +1 -1
  313. package/dist/lib/hook-rules.d.ts.map +1 -1
  314. package/dist/lib/hook-rules.js +8 -2
  315. package/dist/lib/hook-rules.js.map +1 -1
  316. package/dist/lib/ifc/file-labels.d.ts +35 -0
  317. package/dist/lib/ifc/file-labels.d.ts.map +1 -0
  318. package/dist/lib/ifc/file-labels.js +208 -0
  319. package/dist/lib/ifc/file-labels.js.map +1 -0
  320. package/dist/lib/ifc/label.d.ts +38 -0
  321. package/dist/lib/ifc/label.d.ts.map +1 -0
  322. package/dist/lib/ifc/label.js +80 -0
  323. package/dist/lib/ifc/label.js.map +1 -0
  324. package/dist/lib/ifc/session-ifc.d.ts +92 -0
  325. package/dist/lib/ifc/session-ifc.d.ts.map +1 -0
  326. package/dist/lib/ifc/session-ifc.js +222 -0
  327. package/dist/lib/ifc/session-ifc.js.map +1 -0
  328. package/dist/lib/indexer.js +2 -2
  329. package/dist/lib/indexer.js.map +1 -1
  330. package/dist/lib/injection-detector.d.ts +83 -0
  331. package/dist/lib/injection-detector.d.ts.map +1 -0
  332. package/dist/lib/injection-detector.js +586 -0
  333. package/dist/lib/injection-detector.js.map +1 -0
  334. package/dist/lib/injection-semantic.d.ts +31 -0
  335. package/dist/lib/injection-semantic.d.ts.map +1 -0
  336. package/dist/lib/injection-semantic.js +230 -0
  337. package/dist/lib/injection-semantic.js.map +1 -0
  338. package/dist/lib/llm.d.ts.map +1 -1
  339. package/dist/lib/llm.js +19 -4
  340. package/dist/lib/llm.js.map +1 -1
  341. package/dist/lib/lock.d.ts.map +1 -1
  342. package/dist/lib/lock.js +24 -3
  343. package/dist/lib/lock.js.map +1 -1
  344. package/dist/lib/md-fetch.d.ts.map +1 -1
  345. package/dist/lib/md-fetch.js +9 -2
  346. package/dist/lib/md-fetch.js.map +1 -1
  347. package/dist/lib/observability.d.ts +75 -0
  348. package/dist/lib/observability.d.ts.map +1 -0
  349. package/dist/lib/observability.js +201 -0
  350. package/dist/lib/observability.js.map +1 -0
  351. package/dist/lib/ort-session.d.ts +26 -0
  352. package/dist/lib/ort-session.d.ts.map +1 -1
  353. package/dist/lib/ort-session.js +107 -3
  354. package/dist/lib/ort-session.js.map +1 -1
  355. package/dist/lib/prd/codebase-context.d.ts.map +1 -1
  356. package/dist/lib/prd/codebase-context.js +9 -2
  357. package/dist/lib/prd/codebase-context.js.map +1 -1
  358. package/dist/lib/prd/context.d.ts.map +1 -1
  359. package/dist/lib/prd/context.js +11 -3
  360. package/dist/lib/prd/context.js.map +1 -1
  361. package/dist/lib/prd/export.js +1 -1
  362. package/dist/lib/prd/export.js.map +1 -1
  363. package/dist/lib/prd/generate.d.ts.map +1 -1
  364. package/dist/lib/prd/generate.js +17 -4
  365. package/dist/lib/prd/generate.js.map +1 -1
  366. package/dist/lib/prd/parse.d.ts.map +1 -1
  367. package/dist/lib/prd/parse.js +6 -1
  368. package/dist/lib/prd/parse.js.map +1 -1
  369. package/dist/lib/prd/runner.d.ts +1 -2
  370. package/dist/lib/prd/runner.d.ts.map +1 -1
  371. package/dist/lib/prd/runner.js +43 -32
  372. package/dist/lib/prd/runner.js.map +1 -1
  373. package/dist/lib/prd/worktree.d.ts +1 -2
  374. package/dist/lib/prd/worktree.d.ts.map +1 -1
  375. package/dist/lib/prd/worktree.js +62 -70
  376. package/dist/lib/prd/worktree.js.map +1 -1
  377. package/dist/lib/precompute-context.d.ts.map +1 -1
  378. package/dist/lib/precompute-context.js +15 -34
  379. package/dist/lib/precompute-context.js.map +1 -1
  380. package/dist/lib/pricing.d.ts.map +1 -1
  381. package/dist/lib/pricing.js +5 -1
  382. package/dist/lib/pricing.js.map +1 -1
  383. package/dist/lib/process-registry.js +3 -3
  384. package/dist/lib/process-registry.js.map +1 -1
  385. package/dist/lib/public-api.d.ts +41 -1
  386. package/dist/lib/public-api.d.ts.map +1 -1
  387. package/dist/lib/public-api.js +38 -0
  388. package/dist/lib/public-api.js.map +1 -1
  389. package/dist/lib/quality.d.ts.map +1 -1
  390. package/dist/lib/quality.js +15 -6
  391. package/dist/lib/quality.js.map +1 -1
  392. package/dist/lib/query-expansion.d.ts +32 -0
  393. package/dist/lib/query-expansion.d.ts.map +1 -1
  394. package/dist/lib/query-expansion.js +62 -1
  395. package/dist/lib/query-expansion.js.map +1 -1
  396. package/dist/lib/reference-embeddings.d.ts.map +1 -1
  397. package/dist/lib/reference-embeddings.js +17 -4
  398. package/dist/lib/reference-embeddings.js.map +1 -1
  399. package/dist/lib/reflection-synthesizer.d.ts.map +1 -1
  400. package/dist/lib/reflection-synthesizer.js +7 -1
  401. package/dist/lib/reflection-synthesizer.js.map +1 -1
  402. package/dist/lib/reranker.d.ts +41 -0
  403. package/dist/lib/reranker.d.ts.map +1 -0
  404. package/dist/lib/reranker.js +294 -0
  405. package/dist/lib/reranker.js.map +1 -0
  406. package/dist/lib/retrieval-feedback.d.ts +100 -0
  407. package/dist/lib/retrieval-feedback.d.ts.map +1 -0
  408. package/dist/lib/retrieval-feedback.js +174 -0
  409. package/dist/lib/retrieval-feedback.js.map +1 -0
  410. package/dist/lib/review/context-pack.d.ts +58 -0
  411. package/dist/lib/review/context-pack.d.ts.map +1 -0
  412. package/dist/lib/review/context-pack.js +300 -0
  413. package/dist/lib/review/context-pack.js.map +1 -0
  414. package/dist/lib/search/hierarchical-summaries.d.ts +65 -0
  415. package/dist/lib/search/hierarchical-summaries.d.ts.map +1 -0
  416. package/dist/lib/search/hierarchical-summaries.js +423 -0
  417. package/dist/lib/search/hierarchical-summaries.js.map +1 -0
  418. package/dist/lib/search/hyde.d.ts +27 -0
  419. package/dist/lib/search/hyde.d.ts.map +1 -0
  420. package/dist/lib/search/hyde.js +141 -0
  421. package/dist/lib/search/hyde.js.map +1 -0
  422. package/dist/lib/search/late-chunking.d.ts +53 -0
  423. package/dist/lib/search/late-chunking.d.ts.map +1 -0
  424. package/dist/lib/search/late-chunking.js +230 -0
  425. package/dist/lib/search/late-chunking.js.map +1 -0
  426. package/dist/lib/search/ppr-retrieval.d.ts +49 -0
  427. package/dist/lib/search/ppr-retrieval.d.ts.map +1 -0
  428. package/dist/lib/search/ppr-retrieval.js +135 -0
  429. package/dist/lib/search/ppr-retrieval.js.map +1 -0
  430. package/dist/lib/search/repo-map.d.ts +43 -0
  431. package/dist/lib/search/repo-map.d.ts.map +1 -0
  432. package/dist/lib/search/repo-map.js +165 -0
  433. package/dist/lib/search/repo-map.js.map +1 -0
  434. package/dist/lib/session-analyzer.d.ts +90 -0
  435. package/dist/lib/session-analyzer.d.ts.map +1 -0
  436. package/dist/lib/session-analyzer.js +467 -0
  437. package/dist/lib/session-analyzer.js.map +1 -0
  438. package/dist/lib/session-observations.d.ts.map +1 -1
  439. package/dist/lib/session-observations.js +13 -3
  440. package/dist/lib/session-observations.js.map +1 -1
  441. package/dist/lib/session-summary.d.ts.map +1 -1
  442. package/dist/lib/session-summary.js +57 -50
  443. package/dist/lib/session-summary.js.map +1 -1
  444. package/dist/lib/session-surgeon.d.ts +53 -0
  445. package/dist/lib/session-surgeon.d.ts.map +1 -0
  446. package/dist/lib/session-surgeon.js +501 -0
  447. package/dist/lib/session-surgeon.js.map +1 -0
  448. package/dist/lib/similarity-utils.d.ts +26 -0
  449. package/dist/lib/similarity-utils.d.ts.map +1 -0
  450. package/dist/lib/similarity-utils.js +66 -0
  451. package/dist/lib/similarity-utils.js.map +1 -0
  452. package/dist/lib/skills.d.ts.map +1 -1
  453. package/dist/lib/skills.js +11 -11
  454. package/dist/lib/skills.js.map +1 -1
  455. package/dist/lib/storage/backends/interface.d.ts +13 -3
  456. package/dist/lib/storage/backends/interface.d.ts.map +1 -1
  457. package/dist/lib/storage/backends/postgresql.d.ts +52 -3
  458. package/dist/lib/storage/backends/postgresql.d.ts.map +1 -1
  459. package/dist/lib/storage/backends/postgresql.js +694 -49
  460. package/dist/lib/storage/backends/postgresql.js.map +1 -1
  461. package/dist/lib/storage/benchmark.js +2 -2
  462. package/dist/lib/storage/benchmark.js.map +1 -1
  463. package/dist/lib/storage/dispatcher/base.d.ts +114 -0
  464. package/dist/lib/storage/dispatcher/base.d.ts.map +1 -0
  465. package/dist/lib/storage/dispatcher/base.js +160 -0
  466. package/dist/lib/storage/dispatcher/base.js.map +1 -0
  467. package/dist/lib/storage/dispatcher/documents.d.ts +25 -0
  468. package/dist/lib/storage/dispatcher/documents.d.ts.map +1 -0
  469. package/dist/lib/storage/dispatcher/documents.js +194 -0
  470. package/dist/lib/storage/dispatcher/documents.js.map +1 -0
  471. package/dist/lib/storage/dispatcher/embeddings.d.ts +34 -0
  472. package/dist/lib/storage/dispatcher/embeddings.d.ts.map +1 -0
  473. package/dist/lib/storage/dispatcher/embeddings.js +144 -0
  474. package/dist/lib/storage/dispatcher/embeddings.js.map +1 -0
  475. package/dist/lib/storage/dispatcher/export-import.d.ts +139 -0
  476. package/dist/lib/storage/dispatcher/export-import.d.ts.map +1 -0
  477. package/dist/lib/storage/dispatcher/export-import.js +191 -0
  478. package/dist/lib/storage/dispatcher/export-import.js.map +1 -0
  479. package/dist/lib/storage/dispatcher/file-hashes.d.ts +13 -0
  480. package/dist/lib/storage/dispatcher/file-hashes.d.ts.map +1 -0
  481. package/dist/lib/storage/dispatcher/file-hashes.js +36 -0
  482. package/dist/lib/storage/dispatcher/file-hashes.js.map +1 -0
  483. package/dist/lib/storage/dispatcher/global-memories.d.ts +28 -0
  484. package/dist/lib/storage/dispatcher/global-memories.d.ts.map +1 -0
  485. package/dist/lib/storage/dispatcher/global-memories.js +151 -0
  486. package/dist/lib/storage/dispatcher/global-memories.js.map +1 -0
  487. package/dist/lib/storage/dispatcher/graph.d.ts +32 -0
  488. package/dist/lib/storage/dispatcher/graph.d.ts.map +1 -0
  489. package/dist/lib/storage/dispatcher/graph.js +146 -0
  490. package/dist/lib/storage/dispatcher/graph.js.map +1 -0
  491. package/dist/lib/storage/dispatcher/index.d.ts +34 -0
  492. package/dist/lib/storage/dispatcher/index.d.ts.map +1 -0
  493. package/dist/lib/storage/dispatcher/index.js +139 -0
  494. package/dist/lib/storage/dispatcher/index.js.map +1 -0
  495. package/dist/lib/storage/dispatcher/memories.d.ts +65 -0
  496. package/dist/lib/storage/dispatcher/memories.d.ts.map +1 -0
  497. package/dist/lib/storage/dispatcher/memories.js +466 -0
  498. package/dist/lib/storage/dispatcher/memories.js.map +1 -0
  499. package/dist/lib/storage/dispatcher/mixin-helper.d.ts +6 -0
  500. package/dist/lib/storage/dispatcher/mixin-helper.d.ts.map +1 -0
  501. package/dist/lib/storage/dispatcher/mixin-helper.js +10 -0
  502. package/dist/lib/storage/dispatcher/mixin-helper.js.map +1 -0
  503. package/dist/lib/storage/dispatcher/retention.d.ts +20 -0
  504. package/dist/lib/storage/dispatcher/retention.d.ts.map +1 -0
  505. package/dist/lib/storage/dispatcher/retention.js +123 -0
  506. package/dist/lib/storage/dispatcher/retention.js.map +1 -0
  507. package/dist/lib/storage/dispatcher/search.d.ts +34 -0
  508. package/dist/lib/storage/dispatcher/search.d.ts.map +1 -0
  509. package/dist/lib/storage/dispatcher/search.js +222 -0
  510. package/dist/lib/storage/dispatcher/search.js.map +1 -0
  511. package/dist/lib/storage/dispatcher/skills.d.ts +53 -0
  512. package/dist/lib/storage/dispatcher/skills.d.ts.map +1 -0
  513. package/dist/lib/storage/dispatcher/skills.js +98 -0
  514. package/dist/lib/storage/dispatcher/skills.js.map +1 -0
  515. package/dist/lib/storage/dispatcher/token-stats.d.ts +23 -0
  516. package/dist/lib/storage/dispatcher/token-stats.d.ts.map +1 -0
  517. package/dist/lib/storage/dispatcher/token-stats.js +92 -0
  518. package/dist/lib/storage/dispatcher/token-stats.js.map +1 -0
  519. package/dist/lib/storage/dispatcher/web-search.d.ts +10 -0
  520. package/dist/lib/storage/dispatcher/web-search.d.ts.map +1 -0
  521. package/dist/lib/storage/dispatcher/web-search.js +39 -0
  522. package/dist/lib/storage/dispatcher/web-search.js.map +1 -0
  523. package/dist/lib/storage/dispatcher-export.d.ts.map +1 -1
  524. package/dist/lib/storage/dispatcher-export.js +48 -39
  525. package/dist/lib/storage/dispatcher-export.js.map +1 -1
  526. package/dist/lib/storage/dispatcher.d.ts +1 -468
  527. package/dist/lib/storage/dispatcher.d.ts.map +1 -1
  528. package/dist/lib/storage/dispatcher.js +1 -1931
  529. package/dist/lib/storage/dispatcher.js.map +1 -1
  530. package/dist/lib/storage/index.d.ts +20 -5
  531. package/dist/lib/storage/index.d.ts.map +1 -1
  532. package/dist/lib/storage/index.js +36 -7
  533. package/dist/lib/storage/index.js.map +1 -1
  534. package/dist/lib/storage/migration/export-import.d.ts.map +1 -1
  535. package/dist/lib/storage/migration/export-import.js +9 -2
  536. package/dist/lib/storage/migration/export-import.js.map +1 -1
  537. package/dist/lib/storage/types.d.ts +152 -10
  538. package/dist/lib/storage/types.d.ts.map +1 -1
  539. package/dist/lib/storage/types.js +13 -0
  540. package/dist/lib/storage/types.js.map +1 -1
  541. package/dist/lib/storage/vector/interface.d.ts +4 -0
  542. package/dist/lib/storage/vector/interface.d.ts.map +1 -1
  543. package/dist/lib/storage/vector/qdrant.d.ts +13 -2
  544. package/dist/lib/storage/vector/qdrant.d.ts.map +1 -1
  545. package/dist/lib/storage/vector/qdrant.js +147 -61
  546. package/dist/lib/storage/vector/qdrant.js.map +1 -1
  547. package/dist/lib/token-budget.d.ts.map +1 -1
  548. package/dist/lib/token-budget.js +9 -2
  549. package/dist/lib/token-budget.js.map +1 -1
  550. package/dist/lib/transcript-utils.d.ts +60 -0
  551. package/dist/lib/transcript-utils.d.ts.map +1 -0
  552. package/dist/lib/transcript-utils.js +69 -0
  553. package/dist/lib/transcript-utils.js.map +1 -0
  554. package/dist/lib/tree-sitter/extractor.d.ts +1 -1
  555. package/dist/lib/tree-sitter/extractor.d.ts.map +1 -1
  556. package/dist/lib/tree-sitter/extractor.js +34 -9
  557. package/dist/lib/tree-sitter/extractor.js.map +1 -1
  558. package/dist/lib/tree-sitter/parser.d.ts.map +1 -1
  559. package/dist/lib/tree-sitter/parser.js +45 -11
  560. package/dist/lib/tree-sitter/parser.js.map +1 -1
  561. package/dist/lib/tree-sitter/public.d.ts +12 -0
  562. package/dist/lib/tree-sitter/public.d.ts.map +1 -1
  563. package/dist/lib/tree-sitter/public.js +33 -1
  564. package/dist/lib/tree-sitter/public.js.map +1 -1
  565. package/dist/lib/tree-sitter/queries.d.ts.map +1 -1
  566. package/dist/lib/tree-sitter/queries.js +8 -0
  567. package/dist/lib/tree-sitter/queries.js.map +1 -1
  568. package/dist/lib/version-check.d.ts +29 -0
  569. package/dist/lib/version-check.d.ts.map +1 -0
  570. package/dist/lib/version-check.js +187 -0
  571. package/dist/lib/version-check.js.map +1 -0
  572. package/dist/lib/working-memory-pipeline.d.ts.map +1 -1
  573. package/dist/lib/working-memory-pipeline.js +12 -3
  574. package/dist/lib/working-memory-pipeline.js.map +1 -1
  575. package/dist/lib/worktree-detect.d.ts +43 -0
  576. package/dist/lib/worktree-detect.d.ts.map +1 -0
  577. package/dist/lib/worktree-detect.js +154 -0
  578. package/dist/lib/worktree-detect.js.map +1 -0
  579. package/dist/lsp/client.d.ts +96 -0
  580. package/dist/lsp/client.d.ts.map +1 -0
  581. package/dist/lsp/client.js +435 -0
  582. package/dist/lsp/client.js.map +1 -0
  583. package/dist/lsp/installer.d.ts +39 -0
  584. package/dist/lsp/installer.d.ts.map +1 -0
  585. package/dist/lsp/installer.js +275 -0
  586. package/dist/lsp/installer.js.map +1 -0
  587. package/dist/lsp/manager.d.ts +62 -0
  588. package/dist/lsp/manager.d.ts.map +1 -0
  589. package/dist/lsp/manager.js +234 -0
  590. package/dist/lsp/manager.js.map +1 -0
  591. package/dist/lsp/servers.d.ts +52 -0
  592. package/dist/lsp/servers.d.ts.map +1 -0
  593. package/dist/lsp/servers.js +162 -0
  594. package/dist/lsp/servers.js.map +1 -0
  595. package/dist/mcp/helpers.d.ts.map +1 -1
  596. package/dist/mcp/helpers.js +8 -2
  597. package/dist/mcp/helpers.js.map +1 -1
  598. package/dist/mcp/profile.js +1 -1
  599. package/dist/mcp/server.d.ts +3 -2
  600. package/dist/mcp/server.d.ts.map +1 -1
  601. package/dist/mcp/server.js +19 -7
  602. package/dist/mcp/server.js.map +1 -1
  603. package/dist/mcp/tools/config.d.ts.map +1 -1
  604. package/dist/mcp/tools/config.js +28 -118
  605. package/dist/mcp/tools/config.js.map +1 -1
  606. package/dist/mcp/tools/dead-end.d.ts.map +1 -1
  607. package/dist/mcp/tools/dead-end.js +4 -3
  608. package/dist/mcp/tools/dead-end.js.map +1 -1
  609. package/dist/mcp/tools/debug.d.ts.map +1 -1
  610. package/dist/mcp/tools/debug.js +27 -112
  611. package/dist/mcp/tools/debug.js.map +1 -1
  612. package/dist/mcp/tools/graph.d.ts.map +1 -1
  613. package/dist/mcp/tools/graph.js +164 -176
  614. package/dist/mcp/tools/graph.js.map +1 -1
  615. package/dist/mcp/tools/indexing.d.ts +1 -1
  616. package/dist/mcp/tools/indexing.d.ts.map +1 -1
  617. package/dist/mcp/tools/indexing.js +63 -164
  618. package/dist/mcp/tools/indexing.js.map +1 -1
  619. package/dist/mcp/tools/memory/forget.d.ts +3 -0
  620. package/dist/mcp/tools/memory/forget.d.ts.map +1 -0
  621. package/dist/mcp/tools/memory/forget.js +175 -0
  622. package/dist/mcp/tools/memory/forget.js.map +1 -0
  623. package/dist/mcp/tools/memory/memory-helpers.d.ts +45 -0
  624. package/dist/mcp/tools/memory/memory-helpers.d.ts.map +1 -0
  625. package/dist/mcp/tools/memory/memory-helpers.js +291 -0
  626. package/dist/mcp/tools/memory/memory-helpers.js.map +1 -0
  627. package/dist/mcp/tools/memory/recall.d.ts +3 -0
  628. package/dist/mcp/tools/memory/recall.d.ts.map +1 -0
  629. package/dist/mcp/tools/memory/recall.js +495 -0
  630. package/dist/mcp/tools/memory/recall.js.map +1 -0
  631. package/dist/mcp/tools/memory/remember.d.ts +3 -0
  632. package/dist/mcp/tools/memory/remember.d.ts.map +1 -0
  633. package/dist/mcp/tools/memory/remember.js +256 -0
  634. package/dist/mcp/tools/memory/remember.js.map +1 -0
  635. package/dist/mcp/tools/memory/temporal-query.d.ts +8 -0
  636. package/dist/mcp/tools/memory/temporal-query.d.ts.map +1 -0
  637. package/dist/mcp/tools/memory/temporal-query.js +68 -0
  638. package/dist/mcp/tools/memory/temporal-query.js.map +1 -0
  639. package/dist/mcp/tools/memory.d.ts +0 -11
  640. package/dist/mcp/tools/memory.d.ts.map +1 -1
  641. package/dist/mcp/tools/memory.js +6 -1228
  642. package/dist/mcp/tools/memory.js.map +1 -1
  643. package/dist/mcp/tools/prd.d.ts.map +1 -1
  644. package/dist/mcp/tools/prd.js +19 -70
  645. package/dist/mcp/tools/prd.js.map +1 -1
  646. package/dist/mcp/tools/review.d.ts +8 -0
  647. package/dist/mcp/tools/review.d.ts.map +1 -0
  648. package/dist/mcp/tools/review.js +133 -0
  649. package/dist/mcp/tools/review.js.map +1 -0
  650. package/dist/mcp/tools/search.d.ts.map +1 -1
  651. package/dist/mcp/tools/search.js +79 -8
  652. package/dist/mcp/tools/search.js.map +1 -1
  653. package/dist/mcp/tools/status.d.ts.map +1 -1
  654. package/dist/mcp/tools/status.js +50 -75
  655. package/dist/mcp/tools/status.js.map +1 -1
  656. package/dist/mcp/tools/web-fetch.d.ts.map +1 -1
  657. package/dist/mcp/tools/web-fetch.js +5 -1
  658. package/dist/mcp/tools/web-fetch.js.map +1 -1
  659. package/dist/mcp/tools/web-search.d.ts.map +1 -1
  660. package/dist/mcp/tools/web-search.js +25 -103
  661. package/dist/mcp/tools/web-search.js.map +1 -1
  662. package/dist/prompts/guardrails.d.ts +14 -0
  663. package/dist/prompts/guardrails.d.ts.map +1 -0
  664. package/dist/prompts/guardrails.js +115 -0
  665. package/dist/prompts/guardrails.js.map +1 -0
  666. package/dist/prompts/index.d.ts +2 -1
  667. package/dist/prompts/index.d.ts.map +1 -1
  668. package/dist/prompts/index.js +3 -1
  669. package/dist/prompts/index.js.map +1 -1
  670. package/dist/prompts/prd.d.ts +0 -2
  671. package/dist/prompts/prd.d.ts.map +1 -1
  672. package/dist/prompts/prd.js +0 -2
  673. package/dist/prompts/prd.js.map +1 -1
  674. package/hooks/core/__tests__/adapter.test.cjs +340 -0
  675. package/hooks/core/adapter.cjs +463 -0
  676. package/hooks/core/config.cjs +83 -0
  677. package/hooks/core/daemon-boot.cjs +140 -0
  678. package/hooks/core/log.cjs +41 -0
  679. package/hooks/core/worktree.cjs +119 -0
  680. package/hooks/succ-post-tool.cjs +198 -134
  681. package/hooks/succ-pre-compact.cjs +262 -0
  682. package/hooks/succ-pre-tool.cjs +526 -182
  683. package/hooks/succ-session-end.cjs +40 -64
  684. package/hooks/succ-session-start.cjs +528 -427
  685. package/hooks/succ-stop-reflection.cjs +36 -62
  686. package/hooks/succ-user-prompt.cjs +137 -180
  687. package/package.json +17 -6
@@ -17,96 +17,43 @@
17
17
  * - Knowledge base stats
18
18
  */
19
19
 
20
- const { execFileSync, spawn } = require('child_process');
21
20
  const fs = require('fs');
22
21
  const path = require('path');
23
- const os = require('os');
24
-
25
- // Logging helper - writes to .succ/.tmp/hooks.log
26
- function log(succDir, message) {
27
- try {
28
- const tmpDir = path.join(succDir, '.tmp');
29
- if (!fs.existsSync(tmpDir)) {
30
- fs.mkdirSync(tmpDir, { recursive: true });
22
+ const adapter = require('./core/adapter.cjs');
23
+ const { ensureDaemon } = require('./core/daemon-boot.cjs');
24
+ const { log: _log } = require('./core/log.cjs');
25
+ const { loadMergedConfig } = require('./core/config.cjs');
26
+
27
+ adapter.runHook('session-start', async ({ agent, hookInput, projectDir, succDir }) => {
28
+ const log = (msg) => _log(succDir, 'session-start', msg);
29
+
30
+ // Load merged config (global defaults, project overrides)
31
+ const config = loadMergedConfig(projectDir);
32
+ let includeCoAuthoredBy = config.includeCoAuthoredBy !== false;
33
+ let communicationAutoAdapt = config.communicationAutoAdapt !== false;
34
+ let communicationTrackHistory = config.communicationTrackHistory === true;
35
+ let hasOpenRouterKey = !!process.env.OPENROUTER_API_KEY;
36
+ if (!hasOpenRouterKey) {
37
+ const keys = [config.llm?.api_key, config.llm?.embeddings?.api_key, config.web_search?.api_key];
38
+ if (keys.some((k) => typeof k === 'string' && k.startsWith('sk-or-'))) {
39
+ hasOpenRouterKey = true;
31
40
  }
32
- const logFile = path.join(tmpDir, 'hooks.log');
33
- const timestamp = new Date().toISOString();
34
- fs.appendFileSync(logFile, `[${timestamp}] [session-start] ${message}\n`);
35
- } catch {
36
- // Logging failed, not critical
37
41
  }
38
- }
39
-
40
- let input = '';
41
- process.stdin.setEncoding('utf8');
42
- process.stdin.on('readable', () => {
43
- let chunk;
44
- while ((chunk = process.stdin.read()) !== null) {
45
- input += chunk;
46
- }
47
- });
48
-
49
- process.stdin.on('end', async () => {
50
- try {
51
- const hookInput = JSON.parse(input);
52
- let projectDir = hookInput.cwd || process.cwd();
53
-
54
- // Windows path fix
55
- if (process.platform === 'win32' && /^\/[a-z]\//.test(projectDir)) {
56
- projectDir = projectDir[1].toUpperCase() + ':' + projectDir.slice(2);
57
- }
58
42
 
59
- const succDir = path.join(projectDir, '.succ');
60
-
61
- // Skip if succ is not initialized in this project
62
- if (!fs.existsSync(succDir)) {
63
- process.exit(0);
64
- }
65
-
66
- // Load config settings (project config overrides global, but both are checked)
67
- let includeCoAuthoredBy = true; // default: true
68
- let communicationAutoAdapt = true; // default: true
69
- let communicationTrackHistory = false; // default: false
70
- let hasOpenRouterKey = !!process.env.OPENROUTER_API_KEY;
71
- const configPaths = [
72
- // Global first, then project overrides
73
- path.join(require('os').homedir(), '.succ', 'config.json'),
74
- path.join(succDir, 'config.json'),
75
- ];
76
- for (const configPath of configPaths) {
77
- if (fs.existsSync(configPath)) {
78
- try {
79
- const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
80
- if (config.includeCoAuthoredBy === false) {
81
- includeCoAuthoredBy = false;
82
- }
83
- if (config.communicationAutoAdapt === false) {
84
- communicationAutoAdapt = false;
85
- }
86
- if (config.communicationTrackHistory === true) {
87
- communicationTrackHistory = true;
88
- }
89
- // Check for OpenRouter API key: llm.api_key, llm.embeddings.api_key, or web_search.api_key
90
- if (!hasOpenRouterKey) {
91
- const keys = [config.llm?.api_key, config.llm?.embeddings?.api_key, config.web_search?.api_key];
92
- if (keys.some(k => typeof k === 'string' && k.startsWith('sk-or-'))) {
93
- hasOpenRouterKey = true;
94
- }
95
- }
96
- // No break — merge both configs (global defaults, project overrides)
97
- } catch {
98
- // Ignore parse errors
99
- }
100
- }
101
- }
43
+ const contextParts = [];
44
+ const projectName = path.basename(projectDir);
102
45
 
103
- const contextParts = [];
104
- const projectName = path.basename(projectDir);
46
+ // Git Context removed - Claude Code provides native git integration
105
47
 
106
- // Git Context removed - Claude Code provides native git integration
48
+ // Canonical session identifiers derived once, used consistently throughout
49
+ // canonicalSessionId matches pre-compact hook's derivation (session_id-based)
50
+ const transcriptPath = hookInput.transcript_path || '';
51
+ const canonicalSessionId = (hookInput.session_id || 'unknown')
52
+ .replace(/[^a-zA-Z0-9_\-]/g, '_')
53
+ .slice(0, 128);
107
54
 
108
- // succ MCP Tools Reference (hybrid: XML wrapper + markdown examples)
109
- contextParts.push(`<succ-tools>
55
+ // succ MCP Tools Reference (hybrid: XML wrapper + markdown examples)
56
+ contextParts.push(`<succ-tools>
110
57
  <critical>
111
58
  ⚠️ ALWAYS pass project_path="${projectDir}" to ALL succ_* tool calls.
112
59
  Without it, succ works in global-only mode and can't access project data.
@@ -210,12 +157,16 @@ Examples:
210
157
  **succ_prd** action="export" [prd_id="prd_xxx"] — Obsidian Mermaid export
211
158
  </prd>
212
159
 
213
- ${hasOpenRouterKey ? `<web-search hint="Perplexity Sonar via OpenRouter.">
160
+ ${
161
+ hasOpenRouterKey
162
+ ? `<web-search hint="Perplexity Sonar via OpenRouter.">
214
163
  **succ_web** action="quick" query="..." — cheap & fast, simple facts
215
164
  **succ_web** action="search" query="..." [model="perplexity/sonar-pro"] — quality search, complex queries
216
165
  **succ_web** action="deep" query="..." — multi-step research (30-120s, 30+ sources)
217
166
  **succ_web** action="history" [tool_name="..."] [limit=20] — past searches and costs
218
- </web-search>` : ''}
167
+ </web-search>`
168
+ : ''
169
+ }
219
170
 
220
171
  <debug hint="Structured debugging with hypothesis testing. Sessions in .succ/debugs/.">
221
172
  **succ_debug** action="create|hypothesis|instrument|result|resolve|abandon|status|list|log|show_log|detect_lang|gen_log"
@@ -223,8 +174,8 @@ ${hasOpenRouterKey ? `<web-search hint="Perplexity Sonar via OpenRouter.">
223
174
  </debug>
224
175
  </succ-tools>`);
225
176
 
226
- // succ Agents Reference - reminds AI about available subagents
227
- contextParts.push(`<succ-agents hint="Use Task tool with subagent_type to launch these. All have semantic search + memories + brain vault.">
177
+ // succ Agents Reference - reminds AI about available subagents
178
+ contextParts.push(`<succ-agents hint="Use Task tool with subagent_type to launch these. All have semantic search + memories + brain vault.">
228
179
 
229
180
  **MANDATORY: Use succ agents instead of built-in agents.**
230
181
  | Task | Use (succ) | NEVER use (built-in) |
@@ -234,8 +185,12 @@ ${hasOpenRouterKey ? `<web-search hint="Perplexity Sonar via OpenRouter.">
234
185
  | Multi-step tasks, research | succ-general | general-purpose agent |
235
186
  | Code review | succ-code-reviewer | built-in review |
236
187
  | Pre-commit review | succ-diff-reviewer | manual diff reading |
237
- | Web page fetch | succ_fetch | WebFetch |${hasOpenRouterKey ? `
238
- | Web search | succ_web(action="quick") / succ_web(action="search") | WebSearch / Brave |` : ''}
188
+ | Web page fetch | succ_fetch | WebFetch |${
189
+ hasOpenRouterKey
190
+ ? `
191
+ | Web search | succ_web(action="quick") / succ_web(action="search") | WebSearch / Brave |`
192
+ : ''
193
+ }
239
194
 
240
195
  Direct file reads (Read/Grep) are fine when you know the exact path — for discovery, always succ agents.
241
196
 
@@ -249,401 +204,547 @@ Direct file reads (Read/Grep) are fine when you know the exact path — for disc
249
204
  | succ-pattern-detective | After multiple bug fixes — surfaces recurring patterns |
250
205
  </succ-agents>`);
251
206
 
252
- // Commit Guidelines (strict order) - only if includeCoAuthoredBy is enabled
253
- if (includeCoAuthoredBy) {
254
- contextParts.push(`<commit-format>
255
- Footer order (succ always LAST):
256
- 1. Generated with [Claude Code]
257
- 2. via [Happy] (if used)
258
- 3. powered by [succ](https://succ.ai) ← ADD THIS LINE
259
-
260
- Co-Authored-By order (succ always LAST):
261
- 1. Co-Authored-By: Claude <noreply@anthropic.com>
262
- 2. Co-Authored-By: Happy <yesreply@happy.engineering> (if used)
263
- 3. Co-Authored-By: succ <mindpalace@succ.ai> ← ADD THIS LINE
207
+ // Commit Guidelines (strict order) - only if includeCoAuthoredBy is enabled
208
+ if (includeCoAuthoredBy) {
209
+ contextParts.push(`<commit-format>
210
+ RULE: Every commit footer MUST end with the succ lines. Other tools may appear before succ but succ is always LAST.
211
+
212
+ TEMPLATE copy the relevant lines exactly:
213
+ Generated with [Claude Code](https://claude.ai/code)
214
+ powered by [succ](https://succ.ai)
215
+
216
+ Co-Authored-By: Claude <noreply@anthropic.com>
217
+ Co-Authored-By: succ <mindpalace@succ.ai>
218
+
219
+ Other tools (Happy, Cursor, etc.) may add their own "via [Tool]" and "Co-Authored-By: Tool" lines.
220
+ Place them BEFORE the succ lines. The only hard rule: succ is always the last footer line and last Co-Authored-By.
264
221
  </commit-format>`);
265
- }
222
+ }
266
223
 
267
- // Pre-commit review + commit guidelines are now handled by PreToolUse hook (succ-pre-tool.cjs)
268
- // They inject context at the exact moment of git commit, not at session start
269
-
270
- // Soul Document
271
- const soulPaths = [
272
- path.join(succDir, 'soul.md'),
273
- path.join(succDir, 'SOUL.md'),
274
- path.join(projectDir, 'soul.md'),
275
- path.join(projectDir, 'SOUL.md'),
276
- ];
277
-
278
- for (const soulPath of soulPaths) {
279
- if (fs.existsSync(soulPath)) {
280
- let soulContent = fs.readFileSync(soulPath, 'utf8').trim();
281
- if (soulContent) {
282
- // Strip Adaptation rules if auto-adapt is disabled
283
- if (!communicationAutoAdapt) {
284
- soulContent = soulContent.replace(/### Adaptation[\s\S]*?(?=\n## |\n---|\s*$)/, '');
285
- }
286
- // Inject vault tracking hint if enabled (agent handles the actual work)
287
- if (communicationTrackHistory && communicationAutoAdapt) {
288
- soulContent += `\n\n### Vault Tracking\n\ncommunicationTrackHistory is enabled. The succ-style-tracker agent will create brain vault entries in .succ/brain/communication/ when updating preferences.`;
289
- }
290
- contextParts.push('<soul>\n' + soulContent + '\n</soul>');
224
+ // Pre-commit review + commit guidelines are now handled by PreToolUse hook (succ-pre-tool.cjs)
225
+ // They inject context at the exact moment of git commit, not at session start
226
+
227
+ // Soul Document
228
+ const soulPaths = [
229
+ path.join(succDir, 'soul.md'),
230
+ path.join(succDir, 'SOUL.md'),
231
+ path.join(projectDir, 'soul.md'),
232
+ path.join(projectDir, 'SOUL.md'),
233
+ ];
234
+
235
+ for (const soulPath of soulPaths) {
236
+ if (fs.existsSync(soulPath)) {
237
+ let soulContent = fs.readFileSync(soulPath, 'utf8').trim();
238
+ if (soulContent) {
239
+ // Strip Adaptation rules if auto-adapt is disabled
240
+ if (!communicationAutoAdapt) {
241
+ soulContent = soulContent.replace(/### Adaptation[\s\S]*?(?=\n## |\n---|\s*$)/, '');
291
242
  }
292
- break;
243
+ // Inject vault tracking hint if enabled (agent handles the actual work)
244
+ if (communicationTrackHistory && communicationAutoAdapt) {
245
+ soulContent += `\n\n### Vault Tracking\n\ncommunicationTrackHistory is enabled. The succ-style-tracker agent will create brain vault entries in .succ/brain/communication/ when updating preferences.`;
246
+ }
247
+ contextParts.push('<soul>\n' + soulContent + '\n</soul>');
293
248
  }
249
+ break;
294
250
  }
251
+ }
295
252
 
296
- // Architecture / Brain Vault — inline overview + categorized doc index
297
- const brainDir = path.join(succDir, 'brain');
298
- if (fs.existsSync(brainDir)) {
299
- try {
300
- const archParts = [];
301
-
302
- // Phase 1: Find and inline Architecture Overview (compact extract)
303
- const knowledgeDir = path.join(brainDir, 'knowledge');
304
- if (fs.existsSync(knowledgeDir)) {
305
- const archFiles = fs.readdirSync(knowledgeDir)
306
- .filter(f => /architect/i.test(f) && f.endsWith('.md'))
307
- .sort(); // 00_Architecture.md first
308
- if (archFiles.length > 0) {
309
- const archContent = fs.readFileSync(path.join(knowledgeDir, archFiles[0]), 'utf8');
310
- // Strip frontmatter
311
- const body = archContent.replace(/^---[\s\S]*?\n---\s*\n/, '');
312
- // Extract from start to second "---" or "## Tech Stack" — whichever comes first
313
- // This gives us Overview + Core Mission (~15-20 lines)
314
- const overviewEnd = body.search(/\n---\n|\n## Tech Stack|\n## Directory/);
315
- const overview = overviewEnd > 0 ? body.slice(0, overviewEnd).trim() : body.slice(0, 1500).trim();
316
- if (overview) {
317
- archParts.push(overview);
318
- }
253
+ // Architecture / Brain Vault — inline overview + categorized doc index
254
+ const brainDir = path.join(succDir, 'brain');
255
+ if (fs.existsSync(brainDir)) {
256
+ try {
257
+ const archParts = [];
258
+
259
+ // Phase 1: Find and inline Architecture Overview (compact extract)
260
+ const knowledgeDir = path.join(brainDir, 'knowledge');
261
+ if (fs.existsSync(knowledgeDir)) {
262
+ const archFiles = fs
263
+ .readdirSync(knowledgeDir)
264
+ .filter((f) => /architect/i.test(f) && f.endsWith('.md'))
265
+ .sort(); // 00_Architecture.md first
266
+ if (archFiles.length > 0) {
267
+ const archContent = fs.readFileSync(path.join(knowledgeDir, archFiles[0]), 'utf8');
268
+ // Strip frontmatter
269
+ const body = archContent.replace(/^---[\s\S]*?\n---\s*\n/, '');
270
+ // Extract from start to second "---" or "## Tech Stack" — whichever comes first
271
+ // This gives us Overview + Core Mission (~15-20 lines)
272
+ const overviewEnd = body.search(/\n---\n|\n## Tech Stack|\n## Directory/);
273
+ const overview =
274
+ overviewEnd > 0 ? body.slice(0, overviewEnd).trim() : body.slice(0, 1500).trim();
275
+ if (overview) {
276
+ archParts.push(overview);
319
277
  }
320
278
  }
279
+ }
321
280
 
322
- // Phase 2: Collect remaining docs grouped by category
323
- const scanDirs = [
324
- { dir: 'knowledge', label: 'Knowledge & Research' },
325
- { dir: 'project', label: 'Project' },
326
- ];
327
- const docGroups = {};
328
-
329
- for (const { dir, label } of scanDirs) {
330
- const fullDir = path.join(brainDir, dir);
331
- if (!fs.existsSync(fullDir)) continue;
332
-
333
- const files = fs.readdirSync(fullDir, { withFileTypes: true });
334
- for (const entry of files) {
335
- if (entry.isDirectory()) continue;
336
- if (!entry.name.endsWith('.md')) continue;
337
-
338
- const filePath = path.join(fullDir, entry.name);
339
- const stat = fs.statSync(filePath);
340
- if (stat.size < 2048) continue;
341
- // Skip architecture files (already inlined above)
342
- if (/architect/i.test(entry.name)) continue;
343
-
344
- // Read first 500 bytes for frontmatter/H1
345
- const fd = fs.openSync(filePath, 'r');
346
- const buf = Buffer.alloc(500);
347
- fs.readSync(fd, buf, 0, 500, 0);
348
- fs.closeSync(fd);
349
- const head = buf.toString('utf8');
350
-
351
- let description = '';
352
- const fmMatch = head.match(/^---\s*\n([\s\S]*?)\n---/);
353
- if (fmMatch) {
354
- const descMatch = fmMatch[1].match(/description:\s*["']?([^"'\n]+)/);
355
- if (descMatch) description = descMatch[1].trim();
356
- }
357
- if (!description) {
358
- const h1Match = head.match(/^#\s+(.+)/m);
359
- if (h1Match) description = h1Match[1].trim();
360
- }
281
+ // Phase 2: Collect remaining docs grouped by category
282
+ const scanDirs = [
283
+ { dir: 'knowledge', label: 'Knowledge & Research' },
284
+ { dir: 'project', label: 'Project' },
285
+ ];
286
+ const docGroups = {};
287
+
288
+ for (const { dir, label } of scanDirs) {
289
+ const fullDir = path.join(brainDir, dir);
290
+ if (!fs.existsSync(fullDir)) continue;
291
+
292
+ const files = fs.readdirSync(fullDir, { withFileTypes: true });
293
+ for (const entry of files) {
294
+ if (entry.isDirectory()) continue;
295
+ if (!entry.name.endsWith('.md')) continue;
296
+
297
+ const filePath = path.join(fullDir, entry.name);
298
+ const stat = fs.statSync(filePath);
299
+ if (stat.size < 2048) continue;
300
+ // Skip architecture files (already inlined above)
301
+ if (/architect/i.test(entry.name)) continue;
302
+
303
+ // Read first 500 bytes for frontmatter/H1
304
+ const fd = fs.openSync(filePath, 'r');
305
+ const buf = Buffer.alloc(500);
306
+ fs.readSync(fd, buf, 0, 500, 0);
307
+ fs.closeSync(fd);
308
+ const head = buf.toString('utf8');
309
+
310
+ let description = '';
311
+ const fmMatch = head.match(/^---\s*\n([\s\S]*?)\n---/);
312
+ if (fmMatch) {
313
+ const descMatch = fmMatch[1].match(/description:\s*["']?([^"'\n]+)/);
314
+ if (descMatch) description = descMatch[1].trim();
315
+ }
316
+ if (!description) {
317
+ const h1Match = head.match(/^#\s+(.+)/m);
318
+ if (h1Match) description = h1Match[1].trim();
319
+ }
361
320
 
362
- if (description) {
363
- if (!docGroups[label]) docGroups[label] = [];
364
- docGroups[label].push(`${entry.name}: ${description}`);
365
- }
321
+ if (description) {
322
+ if (!docGroups[label]) docGroups[label] = [];
323
+ docGroups[label].push(`${entry.name}: ${description}`);
366
324
  }
367
325
  }
326
+ }
368
327
 
369
- // Phase 3: Format grouped docs
370
- const groupLines = [];
371
- for (const [label, docs] of Object.entries(docGroups)) {
372
- groupLines.push(`**${label}:** ${docs.join(' | ')}`);
373
- }
374
- if (groupLines.length > 0) {
375
- archParts.push(groupLines.join('\n'));
376
- }
328
+ // Phase 3: Format grouped docs
329
+ const groupLines = [];
330
+ for (const [label, docs] of Object.entries(docGroups)) {
331
+ groupLines.push(`**${label}:** ${docs.join(' | ')}`);
332
+ }
333
+ if (groupLines.length > 0) {
334
+ archParts.push(groupLines.join('\n'));
335
+ }
336
+
337
+ if (archParts.length > 0) {
338
+ contextParts.push(
339
+ `<architecture hint="Use succ_search to read full docs">\n${archParts.join('\n\n')}\n</architecture>`
340
+ );
341
+ }
342
+ } catch {
343
+ // intentionally empty — brain vault scan failed, not critical
344
+ }
345
+ }
346
+
347
+ // Check if this is a compact event (after /compact)
348
+ const isCompactEvent = hookInput.source === 'compact';
349
+
350
+ log(
351
+ `source=${hookInput.source}, isCompact=${isCompactEvent}, session=${hookInput.session_id || 'unknown'}`
352
+ );
377
353
 
378
- if (archParts.length > 0) {
379
- contextParts.push(`<architecture hint="Use succ_search to read full docs">\n${archParts.join('\n\n')}\n</architecture>`);
354
+ // Precomputed Context from previous session (only on fresh start, not compact)
355
+ if (!isCompactEvent) {
356
+ const precomputedContextPath = path.join(succDir, 'next-session-context.md');
357
+ if (fs.existsSync(precomputedContextPath)) {
358
+ try {
359
+ const precomputedContent = fs.readFileSync(precomputedContextPath, 'utf8').trim();
360
+ if (precomputedContent) {
361
+ contextParts.push('<previous-session>\n' + precomputedContent + '\n</previous-session>');
362
+
363
+ // Archive the file after loading
364
+ const archiveDir = path.join(succDir, '.context-archive');
365
+ if (!fs.existsSync(archiveDir)) {
366
+ fs.mkdirSync(archiveDir, { recursive: true });
367
+ }
368
+ const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
369
+ const archivePath = path.join(archiveDir, `context-${timestamp}.md`);
370
+ fs.renameSync(precomputedContextPath, archivePath);
371
+
372
+ // Cleanup old archives (keep last 10)
373
+ try {
374
+ const archives = fs
375
+ .readdirSync(archiveDir)
376
+ .filter((f) => f.startsWith('context-') && f.endsWith('.md'))
377
+ .sort()
378
+ .reverse();
379
+ for (const oldArchive of archives.slice(10)) {
380
+ fs.unlinkSync(path.join(archiveDir, oldArchive));
381
+ }
382
+ } catch {
383
+ // intentionally empty — cleanup failed, not critical
384
+ }
380
385
  }
381
386
  } catch {
382
- // Brain vault scan failed, not critical
387
+ // intentionally empty ignore errors
383
388
  }
384
389
  }
390
+ }
385
391
 
386
- // Check if this is a compact event (after /compact)
387
- const isCompactEvent = hookInput.source === 'compact';
392
+ // Ensure daemon is running and get port (shared module)
393
+ const { port: daemonPort } = await ensureDaemon(projectDir, log);
394
+
395
+ // Propagate daemon port to Bash environment via CLAUDE_ENV_FILE
396
+ // ensureDaemon already verified the port is alive, so no need to re-check
397
+ if (daemonPort && process.env.CLAUDE_ENV_FILE) {
398
+ try {
399
+ fs.appendFileSync(process.env.CLAUDE_ENV_FILE, `export SUCC_DAEMON_PORT=${daemonPort}\n`);
400
+ log(`Wrote SUCC_DAEMON_PORT=${daemonPort} to CLAUDE_ENV_FILE`);
401
+ } catch (err) {
402
+ log(`Failed to write CLAUDE_ENV_FILE: ${err.message || err}`);
403
+ }
404
+ }
388
405
 
389
- log(succDir, `source=${hookInput.source}, isCompact=${isCompactEvent}, session=${hookInput.session_id || 'unknown'}`);
406
+ // Skip for service sessions (reflection subagents)
407
+ const isServiceSession = process.env.SUCC_SERVICE_SESSION === '1';
408
+
409
+ // IMPORTANT: Create compact-pending flag FIRST (before any slow operations)
410
+ // This ensures fallback context is available even if hook times out during briefing
411
+ // Hook timeout is 10s, briefing can take 60s+ → would block flag creation
412
+ if (isCompactEvent && !isServiceSession) {
413
+ const compactPendingFile = path.join(succDir, '.tmp', 'compact-pending');
414
+ try {
415
+ if (!fs.existsSync(path.join(succDir, '.tmp'))) {
416
+ fs.mkdirSync(path.join(succDir, '.tmp'), { recursive: true });
417
+ }
418
+ // Store the full context that should be injected (without briefing, that comes later)
419
+ const contextForFallback = adapter.adaptContext(agent, contextParts.join('\n\n'));
420
+ fs.writeFileSync(compactPendingFile, contextForFallback, 'utf8');
421
+ log(`Created compact-pending flag (${contextForFallback.length} chars)`);
422
+ } catch (err) {
423
+ log(`Failed to create compact-pending: ${err.message || err}`);
424
+ }
425
+ }
390
426
 
391
- // Precomputed Context from previous session (only on fresh start, not compact)
392
- if (!isCompactEvent) {
393
- const precomputedContextPath = path.join(succDir, 'next-session-context.md');
394
- if (fs.existsSync(precomputedContextPath)) {
395
- try {
396
- const precomputedContent = fs.readFileSync(precomputedContextPath, 'utf8').trim();
397
- if (precomputedContent) {
398
- contextParts.push('<previous-session>\n' + precomputedContent + '\n</previous-session>');
399
-
400
- // Archive the file after loading
401
- const archiveDir = path.join(succDir, '.context-archive');
402
- if (!fs.existsSync(archiveDir)) {
403
- fs.mkdirSync(archiveDir, { recursive: true });
404
- }
405
- const timestamp = new Date().toISOString().replace(/[:.]/g, '-');
406
- const archivePath = path.join(archiveDir, `context-${timestamp}.md`);
407
- fs.renameSync(precomputedContextPath, archivePath);
408
-
409
- // Cleanup old archives (keep last 10)
410
- try {
411
- const archives = fs.readdirSync(archiveDir)
412
- .filter(f => f.startsWith('context-') && f.endsWith('.md'))
413
- .sort()
414
- .reverse();
415
- for (const oldArchive of archives.slice(10)) {
416
- fs.unlinkSync(path.join(archiveDir, oldArchive));
427
+ // Read pre-compact stats (saved by succ-pre-compact.cjs hook) and display delta
428
+ if (isCompactEvent && !isServiceSession) {
429
+ const statsFile = path.join(succDir, '.tmp', `pre-compact-stats-${canonicalSessionId}.json`);
430
+ try {
431
+ if (fs.existsSync(statsFile)) {
432
+ const stats = JSON.parse(fs.readFileSync(statsFile, 'utf8'));
433
+ const bt = stats.tokenTotals || {};
434
+
435
+ // Estimate post-compact token count from transcript (if available).
436
+ // null = transcript not available; skip delta display to avoid bogus "100% freed".
437
+ let postTokens = /** @type {number|null} */ (null);
438
+ const postByType = { text: 0, tool_use: 0, tool_result: 0, thinking: 0, image: 0 };
439
+ if (hookInput.transcript_path && fs.existsSync(hookInput.transcript_path)) {
440
+ try {
441
+ const postContent = fs.readFileSync(hookInput.transcript_path, 'utf8');
442
+ let postChars = 0;
443
+ for (const line of postContent.split('\n')) {
444
+ const trimmed = line.trim();
445
+ if (!trimmed) continue;
446
+ try {
447
+ const entry = JSON.parse(trimmed);
448
+ const msgContent = entry.message && entry.message.content;
449
+ if (typeof msgContent === 'string') {
450
+ postChars += msgContent.length;
451
+ postByType.text += msgContent.length;
452
+ } else if (Array.isArray(msgContent)) {
453
+ for (const block of msgContent) {
454
+ if (typeof block === 'string') {
455
+ postChars += block.length;
456
+ postByType.text += block.length;
457
+ } else if (block && block.type === 'text') {
458
+ const len = (block.text || '').length;
459
+ postChars += len;
460
+ postByType.text += len;
461
+ } else if (block && block.type === 'tool_use') {
462
+ const len = (block.input ? JSON.stringify(block.input).length : 0) +
463
+ (block.name || '').length + (block.id || '').length;
464
+ postChars += len;
465
+ postByType.tool_use += len;
466
+ } else if (block && block.type === 'tool_result') {
467
+ const rc = block.content;
468
+ let len = 0;
469
+ if (typeof rc === 'string') len = rc.length;
470
+ else if (Array.isArray(rc)) len = JSON.stringify(rc).length;
471
+ postChars += len;
472
+ postByType.tool_result += len;
473
+ } else if (block && block.type === 'thinking') {
474
+ const len = (block.thinking || '').length;
475
+ postChars += len;
476
+ postByType.thinking += len;
477
+ } else if (block && block.type === 'image') {
478
+ const len = block.source ? JSON.stringify(block.source).length : 100;
479
+ postChars += len;
480
+ postByType.image += len;
481
+ }
482
+ }
483
+ }
484
+ } catch {
485
+ /* skip malformed lines */
417
486
  }
418
- } catch {
419
- // Cleanup failed, not critical
420
487
  }
488
+ postTokens = Math.ceil(postChars / 4);
489
+ for (const k of Object.keys(postByType)) {
490
+ postByType[k] = Math.ceil(postByType[k] / 4);
491
+ }
492
+ } catch {
493
+ log('Skipping compact stats delta: failed to analyze post-compact transcript');
421
494
  }
422
- } catch {
423
- // Ignore errors
424
495
  }
425
- }
426
- }
427
496
 
428
- // Helper to get daemon port
429
- const tmpDir = path.join(succDir, '.tmp');
430
- const portFile = path.join(tmpDir, 'daemon.port');
497
+ // Only display delta if post-compact tokens were actually measured
498
+ if (postTokens !== null) {
499
+ const beforeTotal = bt.total || 0;
500
+ const freed = beforeTotal - postTokens;
501
+ const pct = beforeTotal > 0 ? ((freed / beforeTotal) * 100).toFixed(1) : '0.0';
502
+
503
+ const fk = (n) => (n >= 1000 ? (n / 1000).toFixed(1) + 'K' : String(n || 0));
504
+
505
+ const statsLines = [];
506
+ statsLines.push(`Compact: ${fk(beforeTotal)} → ${fk(postTokens)} tokens (${pct}% freed)`);
507
+ statsLines.push('');
508
+ statsLines.push(` ${'Type'.padEnd(16)} ${'Before'.padStart(8)} ${'After'.padStart(8)} ${'Freed'.padStart(8)}`);
509
+ statsLines.push(` ${'─'.repeat(16)} ${'─'.repeat(8)} ${'─'.repeat(8)} ${'─'.repeat(8)}`);
510
+ for (const key of ['text', 'tool_use', 'tool_result', 'thinking', 'image']) {
511
+ const val = bt[key] || 0;
512
+ const aVal = postByType[key] || 0;
513
+ const f = val - aVal;
514
+ if (val === 0 && f === 0) continue;
515
+ statsLines.push(` ${key.padEnd(16)} ${fk(val).padStart(8)} ${fk(aVal).padStart(8)} ${fk(f).padStart(8)}`);
516
+ }
517
+ statsLines.push(` ${'─'.repeat(16)} ${'─'.repeat(8)} ${'─'.repeat(8)} ${'─'.repeat(8)}`);
518
+ statsLines.push(
519
+ ` ${'TOTAL'.padEnd(16)} ${fk(beforeTotal).padStart(8)} ${fk(postTokens).padStart(8)} ${fk(freed).padStart(8)}`
520
+ );
521
+
522
+ const topTools = (stats.topTools || []).slice(0, 5).filter((t) => t.tokens > 0);
523
+ if (topTools.length > 0) {
524
+ statsLines.push('');
525
+ statsLines.push(' Top tools (pre-compact):');
526
+ statsLines.push(' ' + topTools.map((t) => `${t.name}: ${fk(t.tokens)}`).join(' | '));
527
+ }
431
528
 
432
- const getDaemonPort = () => {
433
- try {
434
- if (fs.existsSync(portFile)) {
435
- return parseInt(fs.readFileSync(portFile, 'utf8').trim(), 10);
529
+ contextParts.push(`<compact-stats>\n${statsLines.join('\n')}\n</compact-stats>`);
530
+ log(`Compact stats: ${beforeTotal} → ${postTokens} tokens (${pct}% freed)`);
436
531
  }
437
- } catch {}
438
- return null;
439
- };
440
532
 
441
- const checkDaemon = async (port) => {
442
- try {
443
- const response = await fetch(`http://127.0.0.1:${port}/health`, {
444
- signal: AbortSignal.timeout(2000),
445
- });
446
- const data = await response.json();
447
- return data?.status === 'ok';
448
- } catch {
449
- return false;
450
- }
451
- };
452
-
453
- const startDaemon = () => {
454
- const servicePath = path.join(projectDir, 'dist', 'daemon', 'service.js');
455
- if (fs.existsSync(servicePath)) {
456
- const daemon = spawn(process.execPath, ['--no-warnings', '--no-deprecation', servicePath], {
457
- cwd: projectDir,
458
- detached: true,
459
- stdio: 'ignore',
460
- windowsHide: true,
461
- env: { ...process.env, NODE_OPTIONS: '' },
462
- });
463
- daemon.unref();
464
- return true;
465
- }
466
- return false;
467
- };
468
-
469
- // Ensure daemon is running and get port
470
- let daemonPort = getDaemonPort();
471
- if (!daemonPort || !(await checkDaemon(daemonPort))) {
472
- startDaemon();
473
- // Wait for daemon to start (max 3 seconds)
474
- for (let i = 0; i < 30; i++) {
475
- await new Promise(r => setTimeout(r, 100));
476
- daemonPort = getDaemonPort();
477
- if (daemonPort && await checkDaemon(daemonPort)) {
478
- break;
533
+ // Cleanup stats file
534
+ try {
535
+ fs.unlinkSync(statsFile);
536
+ } catch (e) {
537
+ log(`Failed to cleanup stats file: ${e.message || e}`);
479
538
  }
480
539
  }
540
+ } catch (err) {
541
+ log(`Failed to read compact stats: ${err.message || err}`);
481
542
  }
543
+ }
482
544
 
483
- // Skip for service sessions (reflection subagents)
484
- const isServiceSession = process.env.SUCC_SERVICE_SESSION === '1';
485
-
486
- // IMPORTANT: Create compact-pending flag FIRST (before any slow operations)
487
- // This ensures fallback context is available even if hook times out during briefing
488
- // Hook timeout is 10s, briefing can take 60s+ → would block flag creation
489
- if (isCompactEvent && !isServiceSession) {
490
- const compactPendingFile = path.join(succDir, '.tmp', 'compact-pending');
491
- try {
492
- if (!fs.existsSync(path.join(succDir, '.tmp'))) {
493
- fs.mkdirSync(path.join(succDir, '.tmp'), { recursive: true });
545
+ // Generate compact briefing (slow operation - may timeout)
546
+ // Even if this times out, we have the compact-pending fallback above
547
+ if (isCompactEvent && daemonPort && hookInput.transcript_path && !isServiceSession) {
548
+ log(`Generating compact briefing for ${hookInput.transcript_path}`);
549
+ try {
550
+ const response = await fetch(`http://127.0.0.1:${daemonPort}/api/briefing`, {
551
+ method: 'POST',
552
+ headers: { 'Content-Type': 'application/json' },
553
+ body: JSON.stringify({ transcript_path: hookInput.transcript_path }),
554
+ signal: AbortSignal.timeout(8000), // 8s timeout - must complete before 10s hook timeout
555
+ });
556
+ if (response.ok) {
557
+ const result = await response.json();
558
+ if (result.success && result.briefing) {
559
+ contextParts.push(
560
+ `<session-briefing source="compact">\n${result.briefing}\n</session-briefing>`
561
+ );
562
+ log(`Briefing generated: ${result.briefing.length} chars`);
563
+ } else {
564
+ log(`Briefing failed: ${result.error || 'no briefing returned'}`);
494
565
  }
495
- // Store the full context that should be injected (without briefing, that comes later)
496
- const contextForFallback = contextParts.join('\n\n');
497
- fs.writeFileSync(compactPendingFile, contextForFallback, 'utf8');
498
- log(succDir, `Created compact-pending flag (${contextForFallback.length} chars)`);
499
- } catch (err) {
500
- log(succDir, `Failed to create compact-pending: ${err.message || err}`);
566
+ } else {
567
+ log(`Briefing API error: ${response.status} ${response.statusText}`);
501
568
  }
569
+ } catch (err) {
570
+ log(`Briefing exception: ${err.message || err}`);
571
+ // Briefing generation failed, continue without it
502
572
  }
573
+ }
503
574
 
504
- // Generate compact briefing (slow operation - may timeout)
505
- // Even if this times out, we have the compact-pending fallback above
506
- if (isCompactEvent && daemonPort && hookInput.transcript_path && !isServiceSession) {
507
- log(succDir, `Generating compact briefing for ${hookInput.transcript_path}`);
508
- try {
509
- const response = await fetch(`http://127.0.0.1:${daemonPort}/api/briefing`, {
510
- method: 'POST',
511
- headers: { 'Content-Type': 'application/json' },
512
- body: JSON.stringify({ transcript_path: hookInput.transcript_path }),
513
- signal: AbortSignal.timeout(8000), // 8s timeout - must complete before 10s hook timeout
514
- });
515
- if (response.ok) {
516
- const result = await response.json();
517
- if (result.success && result.briefing) {
518
- contextParts.push(`<session-briefing source="compact">\n${result.briefing}\n</session-briefing>`);
519
- log(succDir, `Briefing generated: ${result.briefing.length} chars`);
520
- } else {
521
- log(succDir, `Briefing failed: ${result.error || 'no briefing returned'}`);
522
- }
523
- } else {
524
- log(succDir, `Briefing API error: ${response.status} ${response.statusText}`);
575
+ // Pinned + recent memories via daemon API (only on fresh start, compact uses briefing instead)
576
+ if (daemonPort && !isCompactEvent) {
577
+ const pinnedIds = new Set();
578
+
579
+ // Phase 1: Pinned memories (Tier 1 — correction_count >= 2 or is_invariant)
580
+ // Filter: skip observations (noisy subagent reports), limit to top 10 by priority_score
581
+ try {
582
+ const response = await fetch(`http://127.0.0.1:${daemonPort}/api/pinned`, {
583
+ signal: AbortSignal.timeout(3000),
584
+ });
585
+ if (response.ok) {
586
+ const data = await response.json();
587
+ const allPinned = data.results || [];
588
+ // Track ALL pinned IDs for dedup with recent (including filtered-out observations)
589
+ for (const m of allPinned) pinnedIds.add(m.id);
590
+ // Display only non-observation pinned, sorted by priority, top 10
591
+ const displayPinned = allPinned
592
+ .filter((m) => m.type !== 'observation')
593
+ .sort((a, b) => (b.priority_score || 0) - (a.priority_score || 0))
594
+ .slice(0, 10);
595
+ if (displayPinned.length > 0) {
596
+ const lines = displayPinned.map((m) => {
597
+ const preview = m.content.slice(0, 100).replace(/\n/g, ' ');
598
+ const type = m.type || 'obs';
599
+ const reason = m.is_invariant ? 'invariant' : `corrected x${m.correction_count}`;
600
+ return `#${m.id} [${type}] (${reason}) ${preview}${m.content.length > 100 ? '...' : ''}`;
601
+ });
602
+ contextParts.push(
603
+ `<pinned-memories count="${displayPinned.length}" total="${allPinned.length}" hint="Tier 1: always loaded, high confidence">\n${lines.join('\n')}\n</pinned-memories>`
604
+ );
525
605
  }
526
- } catch (err) {
527
- log(succDir, `Briefing exception: ${err.message || err}`);
528
- // Briefing generation failed, continue without it
529
606
  }
607
+ } catch {
608
+ // intentionally empty — pinned memories not available
530
609
  }
531
610
 
532
- // Pinned + recent memories via daemon API (only on fresh start, compact uses briefing instead)
533
- if (daemonPort && !isCompactEvent) {
534
- const pinnedIds = new Set();
535
-
536
- // Phase 1: Pinned memories (Tier 1 — correction_count >= 2 or is_invariant)
537
- // Filter: skip observations (noisy subagent reports), limit to top 10 by priority_score
538
- try {
539
- const response = await fetch(`http://127.0.0.1:${daemonPort}/api/pinned`, {
540
- signal: AbortSignal.timeout(3000),
541
- });
542
- if (response.ok) {
543
- const data = await response.json();
544
- const allPinned = data.results || [];
545
- // Display only non-observation pinned, sorted by priority, top 10
546
- const displayPinned = allPinned
547
- .filter((m) => m.type !== 'observation')
548
- .sort((a, b) => (b.priority_score || 0) - (a.priority_score || 0))
549
- .slice(0, 10);
550
- if (displayPinned.length > 0) {
551
- // Track ALL pinned IDs for dedup with recent (including filtered-out observations)
552
- for (const m of allPinned) pinnedIds.add(m.id);
553
- const lines = displayPinned.map((m) => {
554
- const preview = m.content.slice(0, 100).replace(/\n/g, ' ');
555
- const type = m.type || 'obs';
556
- const reason = m.is_invariant ? 'invariant' : `corrected x${m.correction_count}`;
557
- return `#${m.id} [${type}] (${reason}) ${preview}${m.content.length > 100 ? '...' : ''}`;
558
- });
559
- contextParts.push(`<pinned-memories count="${displayPinned.length}" total="${allPinned.length}" hint="Tier 1: always loaded, high confidence">\n${lines.join('\n')}\n</pinned-memories>`);
560
- }
611
+ // Phase 2: Recent memories (excluding pinned to avoid duplication)
612
+ try {
613
+ const response = await fetch(`http://127.0.0.1:${daemonPort}/api/recall`, {
614
+ method: 'POST',
615
+ headers: { 'Content-Type': 'application/json' },
616
+ body: JSON.stringify({ query: '', limit: 5 }),
617
+ signal: AbortSignal.timeout(3000),
618
+ });
619
+ if (response.ok) {
620
+ const data = await response.json();
621
+ const memories = (data.results || []).filter((m) => !pinnedIds.has(m.id));
622
+ if (memories.length > 0) {
623
+ const lines = memories.map((m) => {
624
+ const preview = m.content.slice(0, 50).replace(/\n/g, ' ');
625
+ const type = m.type || 'obs';
626
+ return `#${m.id} [${type}] ${preview}${m.content.length > 50 ? '...' : ''}`;
627
+ });
628
+ contextParts.push(
629
+ `<recent-memories count="${memories.length}" hint="Use succ_recall for details">\n${lines.join('\n')}\n</recent-memories>`
630
+ );
561
631
  }
562
- } catch {
563
- // pinned memories not available
564
632
  }
633
+ } catch {
634
+ // intentionally empty — memories not available
635
+ }
636
+ }
565
637
 
566
- // Phase 2: Recent memories (excluding pinned to avoid duplication)
567
- try {
568
- const response = await fetch(`http://127.0.0.1:${daemonPort}/api/recall`, {
569
- method: 'POST',
570
- headers: { 'Content-Type': 'application/json' },
571
- body: JSON.stringify({ query: '', limit: 5 }),
572
- signal: AbortSignal.timeout(3000),
573
- });
574
- if (response.ok) {
575
- const data = await response.json();
576
- const memories = (data.results || []).filter((m) => !pinnedIds.has(m.id));
577
- if (memories.length > 0) {
578
- const lines = memories.map((m) => {
579
- const preview = m.content.slice(0, 50).replace(/\n/g, ' ');
580
- const type = m.type || 'obs';
581
- return `#${m.id} [${type}] ${preview}${m.content.length > 50 ? '...' : ''}`;
582
- });
583
- contextParts.push(`<recent-memories count="${memories.length}" hint="Use succ_recall for details">\n${lines.join('\n')}\n</recent-memories>`);
584
- }
638
+ // Knowledge base stats via daemon API
639
+ if (daemonPort) {
640
+ try {
641
+ const response = await fetch(`http://127.0.0.1:${daemonPort}/api/status`, {
642
+ signal: AbortSignal.timeout(3000),
643
+ });
644
+ if (response.ok) {
645
+ const status = await response.json();
646
+ const docs = status.documents || 0;
647
+ const mems = status.memories || 0;
648
+ const code = status.codeChunks || 0;
649
+ if (docs > 0 || mems > 0 || code > 0) {
650
+ contextParts.push(
651
+ `<knowledge-base docs="${docs}" memories="${mems}" code-chunks="${code}" />`
652
+ );
585
653
  }
586
- } catch {
587
- // memories not available
588
654
  }
655
+ } catch {
656
+ // intentionally empty — status not available
589
657
  }
658
+ }
590
659
 
591
- // Knowledge base stats via daemon API
592
- if (daemonPort) {
593
- try {
594
- const response = await fetch(`http://127.0.0.1:${daemonPort}/api/status`, {
595
- signal: AbortSignal.timeout(3000),
596
- });
597
- if (response.ok) {
598
- const status = await response.json();
599
- const docs = status.documents || 0;
600
- const mems = status.memories || 0;
601
- const code = status.codeChunks || 0;
602
- if (docs > 0 || mems > 0 || code > 0) {
603
- contextParts.push(`<knowledge-base docs="${docs}" memories="${mems}" code-chunks="${code}" />`);
660
+ // Update notification read cache, inject instruction for AI agent
661
+ const updateCheckSuppressed =
662
+ process.env.SUCC_NO_UPDATE_CHECK === '1' ||
663
+ process.env.CI === 'true' ||
664
+ process.env.NO_UPDATE_NOTIFIER === '1' ||
665
+ config.update_check?.enabled === false;
666
+
667
+ if (!updateCheckSuppressed) try {
668
+ const vcPath = path.join(succDir, '.tmp', 'version-check.json');
669
+ if (fs.existsSync(vcPath)) {
670
+ const vc = JSON.parse(fs.readFileSync(vcPath, 'utf8'));
671
+ if (vc.update_available && vc.latest && vc.current && typeof vc.checked_at === 'string') {
672
+ const age = Date.now() - new Date(vc.checked_at).getTime();
673
+ if (age < 48 * 3600000) {
674
+ // Guard against stale cache: if the installed version is already >= latest, skip.
675
+ // This prevents false "update available" notifications after the user upgrades
676
+ // before the 48h cache expires.
677
+ const installedVersion = require('../package.json').version;
678
+ const compareSemver = (a, b) => {
679
+ const pa = a.split('.').map(Number);
680
+ const pb = b.split('.').map(Number);
681
+ for (let i = 0; i < 3; i++) {
682
+ const diff = (pa[i] || 0) - (pb[i] || 0);
683
+ if (diff !== 0) return diff > 0 ? 1 : -1;
684
+ }
685
+ return 0;
686
+ };
687
+ if (compareSemver(installedVersion, vc.latest) >= 0) {
688
+ // Already up-to-date — delete the stale cache so the next check rewrites it
689
+ try { fs.unlinkSync(vcPath); } catch (unlinkErr) {
690
+ log(`Failed to delete stale version-check cache: ${unlinkErr.message || unlinkErr}`);
691
+ }
692
+ } else {
693
+ contextParts.push(
694
+ `<update-available>` +
695
+ `succ ${vc.current} \u2192 ${vc.latest} is available. ` +
696
+ `Tell the user about this update as soon as possible and suggest running: npm update -g @vinaes/succ` +
697
+ `</update-available>`
698
+ );
604
699
  }
605
700
  }
606
- } catch {
607
- // status not available
608
701
  }
609
702
  }
703
+ } catch (err) {
704
+ log(`Failed to read version-check cache: ${err.message || err}`);
705
+ }
610
706
 
611
- // Output context
612
- if (contextParts.length > 0) {
613
- const additionalContext = `<session project="${projectName}">\n${contextParts.join('\n\n')}\n</session>`;
614
- const output = {
615
- hookSpecificOutput: {
616
- hookEventName: 'SessionStart',
617
- additionalContext
618
- }
619
- };
620
- console.log(JSON.stringify(output));
621
- log(succDir, `Output additionalContext: ${additionalContext.length} chars, parts=${contextParts.length}`);
622
- } else {
623
- log(succDir, `No context parts to output`);
707
+ // Output context
708
+ if (contextParts.length > 0) {
709
+ // Sanitize closing wrapper tags from dynamic content to prevent XML injection
710
+ // (e.g. a stored memory or soul.md containing literal "</session>" would break the envelope)
711
+ const body = contextParts.join('\n\n').replace(/<\/session>/gi, '&lt;/session&gt;');
712
+ let additionalContext = `<session project="${projectName}">\n${body}\n</session>`;
713
+ // Strip Claude-only sections for non-Claude agents
714
+ additionalContext = adapter.adaptContext(agent, additionalContext);
715
+ const { json, exitCode } = adapter.formatOutput(agent, 'SessionStart', { additionalContext });
716
+ if (json && Object.keys(json).length > 0) {
717
+ console.log(JSON.stringify(json));
624
718
  }
719
+ log(
720
+ `Output additionalContext: ${additionalContext.length} chars, parts=${contextParts.length}, agent=${agent}`
721
+ );
722
+ if (exitCode) process.exit(exitCode); // non-zero = deny (Cursor/Gemini); 0 falls through to session registration
723
+ } else {
724
+ log(`No context parts to output`);
725
+ }
625
726
 
626
- // Register session with daemon
627
- if (daemonPort) {
628
- const transcriptPath = hookInput.transcript_path || '';
629
- const sessionId = transcriptPath ? path.basename(transcriptPath, '.jsonl') : `session-${Date.now()}`;
630
- // isServiceSession already defined above
631
-
632
- try {
633
- await fetch(`http://127.0.0.1:${daemonPort}/api/session/register`, {
634
- method: 'POST',
635
- headers: { 'Content-Type': 'application/json' },
636
- body: JSON.stringify({ session_id: sessionId, transcript_path: transcriptPath, is_service: isServiceSession }),
637
- signal: AbortSignal.timeout(3000),
638
- });
639
- } catch {
640
- // Registration failed, continue anyway
727
+ // Register session with daemon
728
+ // transcriptPath and canonicalSessionId derived early — reuse here for consistency
729
+ if (daemonPort) {
730
+ try {
731
+ const res = await fetch(`http://127.0.0.1:${daemonPort}/api/session/register`, {
732
+ method: 'POST',
733
+ headers: { 'Content-Type': 'application/json' },
734
+ body: JSON.stringify({
735
+ session_id: canonicalSessionId,
736
+ transcript_path: transcriptPath,
737
+ is_service: isServiceSession,
738
+ }),
739
+ signal: AbortSignal.timeout(3000),
740
+ });
741
+ if (!res.ok) {
742
+ log(`Session register failed: ${res.status} ${res.statusText} session=${canonicalSessionId}`);
641
743
  }
744
+ } catch (err) {
745
+ log(`Session register error for ${canonicalSessionId}: ${err.message || err}`);
642
746
  }
643
-
644
- process.exit(0);
645
-
646
- } catch (err) {
647
- process.exit(0);
648
747
  }
748
+
749
+ process.exit(0);
649
750
  });