nlm-memory 0.20.0 → 0.21.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (471) hide show
  1. package/README.md +19 -10
  2. package/dist/cli/digest.js +60 -1
  3. package/dist/cli/digest.js.map +1 -1
  4. package/dist/cli/init.d.ts +12 -0
  5. package/dist/cli/init.js +49 -0
  6. package/dist/cli/init.js.map +1 -0
  7. package/dist/cli/nlm.d.ts +13 -1
  8. package/dist/cli/nlm.js +872 -61
  9. package/dist/cli/nlm.js.map +1 -1
  10. package/dist/cli/scope-backfill.d.ts +34 -0
  11. package/dist/cli/scope-backfill.js +237 -0
  12. package/dist/cli/scope-backfill.js.map +1 -0
  13. package/dist/cli/scope-coverage.d.ts +44 -0
  14. package/dist/cli/scope-coverage.js +205 -0
  15. package/dist/cli/scope-coverage.js.map +1 -0
  16. package/dist/core/actions/overlay.d.ts +20 -6
  17. package/dist/core/actions/overlay.js +32 -18
  18. package/dist/core/actions/overlay.js.map +1 -1
  19. package/dist/core/adapters/claude-code.d.ts +14 -0
  20. package/dist/core/adapters/claude-code.js +36 -0
  21. package/dist/core/adapters/claude-code.js.map +1 -1
  22. package/dist/core/classifier/prompt.js +3 -3
  23. package/dist/core/dataset/build-dataset.js +1 -0
  24. package/dist/core/dataset/build-dataset.js.map +1 -1
  25. package/dist/core/digest/compose.d.ts +13 -3
  26. package/dist/core/digest/compose.js +23 -18
  27. package/dist/core/digest/compose.js.map +1 -1
  28. package/dist/core/digest/hook-injection.d.ts +43 -0
  29. package/dist/core/digest/hook-injection.js +79 -0
  30. package/dist/core/digest/hook-injection.js.map +1 -0
  31. package/dist/core/embedding/embed-backfill.d.ts +13 -2
  32. package/dist/core/embedding/embed-backfill.js +181 -43
  33. package/dist/core/embedding/embed-backfill.js.map +1 -1
  34. package/dist/core/embedding/embedding-config.d.ts +17 -0
  35. package/dist/core/embedding/embedding-config.js +12 -0
  36. package/dist/core/embedding/embedding-config.js.map +1 -0
  37. package/dist/core/embedding/pg-embed-backfill.d.ts +31 -0
  38. package/dist/core/embedding/pg-embed-backfill.js +264 -0
  39. package/dist/core/embedding/pg-embed-backfill.js.map +1 -0
  40. package/dist/core/entities/dedup-suggest.d.ts +25 -0
  41. package/dist/core/entities/dedup-suggest.js +107 -0
  42. package/dist/core/entities/dedup-suggest.js.map +1 -0
  43. package/dist/core/eval/classifier-fixture-eval.d.ts +29 -0
  44. package/dist/core/eval/classifier-fixture-eval.js +217 -0
  45. package/dist/core/eval/classifier-fixture-eval.js.map +1 -0
  46. package/dist/core/eval/extraction-scoring.d.ts +63 -0
  47. package/dist/core/eval/extraction-scoring.js +79 -0
  48. package/dist/core/eval/extraction-scoring.js.map +1 -0
  49. package/dist/core/eval/run-eval.d.ts +40 -0
  50. package/dist/core/eval/run-eval.js +34 -0
  51. package/dist/core/eval/run-eval.js.map +1 -0
  52. package/dist/core/exemplars/capture-from-session.d.ts +2 -0
  53. package/dist/core/exemplars/capture-from-session.js +7 -3
  54. package/dist/core/exemplars/capture-from-session.js.map +1 -1
  55. package/dist/core/exemplars/diff-parse.d.ts +23 -0
  56. package/dist/core/exemplars/diff-parse.js +73 -0
  57. package/dist/core/exemplars/diff-parse.js.map +1 -0
  58. package/dist/core/exemplars/embed-backfill.d.ts +36 -0
  59. package/dist/core/exemplars/embed-backfill.js +85 -0
  60. package/dist/core/exemplars/embed-backfill.js.map +1 -0
  61. package/dist/core/exemplars/embed-text.d.ts +15 -0
  62. package/dist/core/exemplars/embed-text.js +18 -0
  63. package/dist/core/exemplars/embed-text.js.map +1 -0
  64. package/dist/core/exemplars/extract-exemplar.d.ts +2 -0
  65. package/dist/core/exemplars/extract-exemplar.js +9 -66
  66. package/dist/core/exemplars/extract-exemplar.js.map +1 -1
  67. package/dist/core/exemplars/ingest-exemplar.d.ts +1 -0
  68. package/dist/core/exemplars/ingest-exemplar.js +1 -0
  69. package/dist/core/exemplars/ingest-exemplar.js.map +1 -1
  70. package/dist/core/exemplars/recall-code.js +4 -0
  71. package/dist/core/exemplars/recall-code.js.map +1 -1
  72. package/dist/core/exemplars/warm-embedder.d.ts +14 -0
  73. package/dist/core/exemplars/warm-embedder.js +18 -0
  74. package/dist/core/exemplars/warm-embedder.js.map +1 -0
  75. package/dist/core/health/corpus-state.d.ts +8 -0
  76. package/dist/core/health/corpus-state.js +11 -0
  77. package/dist/core/health/corpus-state.js.map +1 -0
  78. package/dist/core/health/embed-inflight.d.ts +9 -0
  79. package/dist/core/health/embed-inflight.js +30 -0
  80. package/dist/core/health/embed-inflight.js.map +1 -0
  81. package/dist/core/health/embedding-lane-state.d.ts +6 -0
  82. package/dist/core/health/embedding-lane-state.js +18 -0
  83. package/dist/core/health/embedding-lane-state.js.map +1 -0
  84. package/dist/core/health/warmup-state.d.ts +12 -0
  85. package/dist/core/health/warmup-state.js +12 -0
  86. package/dist/core/health/warmup-state.js.map +1 -0
  87. package/dist/core/hook/claude-settings.d.ts +1 -1
  88. package/dist/core/hook/claude-settings.js +0 -1
  89. package/dist/core/hook/claude-settings.js.map +1 -1
  90. package/dist/core/hook/gate.d.ts +13 -6
  91. package/dist/core/hook/gate.js +47 -6
  92. package/dist/core/hook/gate.js.map +1 -1
  93. package/dist/core/hook/hook-log.d.ts +5 -0
  94. package/dist/core/hook/hook-log.js.map +1 -1
  95. package/dist/core/hook/memo.d.ts +12 -0
  96. package/dist/core/hook/memo.js +41 -1
  97. package/dist/core/hook/memo.js.map +1 -1
  98. package/dist/core/hook/pointer-block.d.ts +1 -0
  99. package/dist/core/hook/pointer-block.js +23 -2
  100. package/dist/core/hook/pointer-block.js.map +1 -1
  101. package/dist/core/hook/query-extract.d.ts +2 -1
  102. package/dist/core/hook/query-extract.js +10 -1
  103. package/dist/core/hook/query-extract.js.map +1 -1
  104. package/dist/core/hook/resolve-conversation-by-query.d.ts +28 -0
  105. package/dist/core/hook/resolve-conversation-by-query.js +98 -0
  106. package/dist/core/hook/resolve-conversation-by-query.js.map +1 -0
  107. package/dist/core/hook/select.d.ts +8 -0
  108. package/dist/core/hook/select.js +9 -2
  109. package/dist/core/hook/select.js.map +1 -1
  110. package/dist/core/hook/transcript.js +16 -2
  111. package/dist/core/hook/transcript.js.map +1 -1
  112. package/dist/core/ingest/backfill-derivables.d.ts +92 -0
  113. package/dist/core/ingest/backfill-derivables.js +175 -0
  114. package/dist/core/ingest/backfill-derivables.js.map +1 -0
  115. package/dist/core/ingest/ingest-session.d.ts +5 -0
  116. package/dist/core/ingest/ingest-session.js +23 -0
  117. package/dist/core/ingest/ingest-session.js.map +1 -1
  118. package/dist/core/ingest/reclassify-oversized.d.ts +5 -0
  119. package/dist/core/ingest/reclassify-oversized.js +9 -0
  120. package/dist/core/ingest/reclassify-oversized.js.map +1 -1
  121. package/dist/core/ingest/reprocess.d.ts +96 -0
  122. package/dist/core/ingest/reprocess.js +280 -0
  123. package/dist/core/ingest/reprocess.js.map +1 -0
  124. package/dist/core/ingest/transcript-derivables.d.ts +27 -0
  125. package/dist/core/ingest/transcript-derivables.js +138 -0
  126. package/dist/core/ingest/transcript-derivables.js.map +1 -0
  127. package/dist/core/integrity/check-invariants.d.ts +6 -1
  128. package/dist/core/integrity/check-invariants.js +209 -3
  129. package/dist/core/integrity/check-invariants.js.map +1 -1
  130. package/dist/core/metrics/corpus-stats.d.ts +49 -0
  131. package/dist/core/metrics/corpus-stats.js +111 -0
  132. package/dist/core/metrics/corpus-stats.js.map +1 -0
  133. package/dist/core/metrics/re-derivation.d.ts +48 -0
  134. package/dist/core/metrics/re-derivation.js +87 -0
  135. package/dist/core/metrics/re-derivation.js.map +1 -0
  136. package/dist/core/outcome/coverage.d.ts +33 -0
  137. package/dist/core/outcome/coverage.js +46 -0
  138. package/dist/core/outcome/coverage.js.map +1 -0
  139. package/dist/core/outcome/rollup.d.ts +33 -0
  140. package/dist/core/outcome/rollup.js +113 -0
  141. package/dist/core/outcome/rollup.js.map +1 -0
  142. package/dist/core/recall/citation-log.d.ts +8 -0
  143. package/dist/core/recall/citation-log.js +17 -0
  144. package/dist/core/recall/citation-log.js.map +1 -1
  145. package/dist/core/recall/hook-recall-log.d.ts +2 -2
  146. package/dist/core/recall/hook-recall-log.js +2 -2
  147. package/dist/core/recall/query-intent.d.ts +7 -0
  148. package/dist/core/recall/query-intent.js +25 -0
  149. package/dist/core/recall/query-intent.js.map +1 -0
  150. package/dist/core/recall/query-log.d.ts +4 -0
  151. package/dist/core/recall/query-log.js +4 -0
  152. package/dist/core/recall/query-log.js.map +1 -1
  153. package/dist/core/recall/recall-service.d.ts +37 -1
  154. package/dist/core/recall/recall-service.js +106 -63
  155. package/dist/core/recall/recall-service.js.map +1 -1
  156. package/dist/core/recall/related-exemplars.d.ts +1 -0
  157. package/dist/core/recall/related-exemplars.js +4 -1
  158. package/dist/core/recall/related-exemplars.js.map +1 -1
  159. package/dist/core/recall/related-facts.js +2 -2
  160. package/dist/core/recall/related-facts.js.map +1 -1
  161. package/dist/core/recall/reranker.d.ts +2 -1
  162. package/dist/core/recall/reranker.js +3 -3
  163. package/dist/core/recall/reranker.js.map +1 -1
  164. package/dist/core/recall-facts/fact-query-log.d.ts +7 -0
  165. package/dist/core/recall-facts/fact-query-log.js +2 -0
  166. package/dist/core/recall-facts/fact-query-log.js.map +1 -1
  167. package/dist/core/recall-facts/fact-recall-service.d.ts +9 -1
  168. package/dist/core/recall-facts/fact-recall-service.js +22 -8
  169. package/dist/core/recall-facts/fact-recall-service.js.map +1 -1
  170. package/dist/core/scheduler/scan-once.d.ts +6 -6
  171. package/dist/core/scheduler/scan-once.js +15 -49
  172. package/dist/core/scheduler/scan-once.js.map +1 -1
  173. package/dist/core/scheduler/scheduler.d.ts +10 -0
  174. package/dist/core/scheduler/scheduler.js +109 -13
  175. package/dist/core/scheduler/scheduler.js.map +1 -1
  176. package/dist/core/scope/alias-map.d.ts +10 -0
  177. package/dist/core/scope/alias-map.js +41 -0
  178. package/dist/core/scope/alias-map.js.map +1 -0
  179. package/dist/core/scope/derive-scope.d.ts +8 -0
  180. package/dist/core/scope/derive-scope.js +54 -0
  181. package/dist/core/scope/derive-scope.js.map +1 -0
  182. package/dist/core/scope/scope-clause.d.ts +16 -0
  183. package/dist/core/scope/scope-clause.js +21 -0
  184. package/dist/core/scope/scope-clause.js.map +1 -0
  185. package/dist/core/scope/stamp-flag.d.ts +1 -0
  186. package/dist/core/scope/stamp-flag.js +4 -0
  187. package/dist/core/scope/stamp-flag.js.map +1 -0
  188. package/dist/core/signals/code-signal.d.ts +50 -0
  189. package/dist/core/signals/code-signal.js +54 -0
  190. package/dist/core/signals/code-signal.js.map +1 -0
  191. package/dist/core/signals/ingest-signal.d.ts +1 -1
  192. package/dist/core/signals/ingest-signal.js +3 -1
  193. package/dist/core/signals/ingest-signal.js.map +1 -1
  194. package/dist/core/signals/stamp-scope.d.ts +18 -0
  195. package/dist/core/signals/stamp-scope.js +28 -0
  196. package/dist/core/signals/stamp-scope.js.map +1 -0
  197. package/dist/core/storage/db-restore.js +3 -1
  198. package/dist/core/storage/db-restore.js.map +1 -1
  199. package/dist/core/storage/fact-batch.d.ts +9 -0
  200. package/dist/core/storage/fact-batch.js +14 -0
  201. package/dist/core/storage/fact-batch.js.map +1 -0
  202. package/dist/core/storage/pg-code-exemplar-store.d.ts +1 -0
  203. package/dist/core/storage/pg-code-exemplar-store.js +13 -2
  204. package/dist/core/storage/pg-code-exemplar-store.js.map +1 -1
  205. package/dist/core/storage/pg-embedding-config.d.ts +29 -0
  206. package/dist/core/storage/pg-embedding-config.js +53 -0
  207. package/dist/core/storage/pg-embedding-config.js.map +1 -0
  208. package/dist/core/storage/pg-entity-store.d.ts +12 -0
  209. package/dist/core/storage/pg-entity-store.js +79 -0
  210. package/dist/core/storage/pg-entity-store.js.map +1 -0
  211. package/dist/core/storage/pg-fact-ingest.d.ts +15 -0
  212. package/dist/core/storage/pg-fact-ingest.js +34 -0
  213. package/dist/core/storage/pg-fact-ingest.js.map +1 -0
  214. package/dist/core/storage/pg-fact-store.d.ts +3 -0
  215. package/dist/core/storage/pg-fact-store.js +26 -30
  216. package/dist/core/storage/pg-fact-store.js.map +1 -1
  217. package/dist/core/storage/pg-migrate.d.ts +18 -0
  218. package/dist/core/storage/pg-migrate.js +73 -0
  219. package/dist/core/storage/pg-migrate.js.map +1 -0
  220. package/dist/core/storage/pg-session-store.d.ts +11 -2
  221. package/dist/core/storage/pg-session-store.js +224 -96
  222. package/dist/core/storage/pg-session-store.js.map +1 -1
  223. package/dist/core/storage/pg-signal-store.js +4 -3
  224. package/dist/core/storage/pg-signal-store.js.map +1 -1
  225. package/dist/core/storage/pg-storage.d.ts +9 -7
  226. package/dist/core/storage/pg-storage.js +20 -50
  227. package/dist/core/storage/pg-storage.js.map +1 -1
  228. package/dist/core/storage/pg-workstream-store.d.ts +25 -0
  229. package/dist/core/storage/pg-workstream-store.js +97 -0
  230. package/dist/core/storage/pg-workstream-store.js.map +1 -0
  231. package/dist/core/storage/sqlite-code-exemplar-store.d.ts +1 -0
  232. package/dist/core/storage/sqlite-code-exemplar-store.js +15 -2
  233. package/dist/core/storage/sqlite-code-exemplar-store.js.map +1 -1
  234. package/dist/core/storage/sqlite-embedding-config.d.ts +10 -0
  235. package/dist/core/storage/sqlite-embedding-config.js +27 -0
  236. package/dist/core/storage/sqlite-embedding-config.js.map +1 -0
  237. package/dist/core/storage/sqlite-entity-store.d.ts +13 -0
  238. package/dist/core/storage/sqlite-entity-store.js +96 -0
  239. package/dist/core/storage/sqlite-entity-store.js.map +1 -0
  240. package/dist/core/storage/sqlite-fact-store.d.ts +15 -7
  241. package/dist/core/storage/sqlite-fact-store.js +81 -27
  242. package/dist/core/storage/sqlite-fact-store.js.map +1 -1
  243. package/dist/core/storage/sqlite-outcome-store.d.ts +89 -0
  244. package/dist/core/storage/sqlite-outcome-store.js +158 -0
  245. package/dist/core/storage/sqlite-outcome-store.js.map +1 -0
  246. package/dist/core/storage/sqlite-session-store.d.ts +42 -7
  247. package/dist/core/storage/sqlite-session-store.js +208 -95
  248. package/dist/core/storage/sqlite-session-store.js.map +1 -1
  249. package/dist/core/storage/sqlite-signal-store.js +4 -2
  250. package/dist/core/storage/sqlite-signal-store.js.map +1 -1
  251. package/dist/core/storage/sqlite-storage.d.ts +8 -8
  252. package/dist/core/storage/sqlite-storage.js +21 -32
  253. package/dist/core/storage/sqlite-storage.js.map +1 -1
  254. package/dist/core/storage/sqlite-workstream-store.d.ts +25 -0
  255. package/dist/core/storage/sqlite-workstream-store.js +99 -0
  256. package/dist/core/storage/sqlite-workstream-store.js.map +1 -0
  257. package/dist/core/storage/supersedence-log.js +2 -0
  258. package/dist/core/storage/supersedence-log.js.map +1 -1
  259. package/dist/core/telemetry/probe-filter.d.ts +35 -0
  260. package/dist/core/telemetry/probe-filter.js +56 -0
  261. package/dist/core/telemetry/probe-filter.js.map +1 -0
  262. package/dist/core/update-check/check.d.ts +6 -0
  263. package/dist/core/update-check/check.js +35 -0
  264. package/dist/core/update-check/check.js.map +1 -1
  265. package/dist/core/work-digest/active-spans.d.ts +8 -0
  266. package/dist/core/work-digest/active-spans.js +29 -0
  267. package/dist/core/work-digest/active-spans.js.map +1 -0
  268. package/dist/core/work-digest/attribute.d.ts +15 -0
  269. package/dist/core/work-digest/attribute.js +76 -0
  270. package/dist/core/work-digest/attribute.js.map +1 -0
  271. package/dist/core/work-digest/build-work-digest.d.ts +12 -0
  272. package/dist/core/work-digest/build-work-digest.js +69 -0
  273. package/dist/core/work-digest/build-work-digest.js.map +1 -0
  274. package/dist/core/work-digest/compose-work-digest.d.ts +3 -0
  275. package/dist/core/work-digest/compose-work-digest.js +52 -0
  276. package/dist/core/work-digest/compose-work-digest.js.map +1 -0
  277. package/dist/core/work-digest/merge-active.d.ts +10 -0
  278. package/dist/core/work-digest/merge-active.js +22 -0
  279. package/dist/core/work-digest/merge-active.js.map +1 -0
  280. package/dist/core/work-digest/read-transcript-timestamps.d.ts +7 -0
  281. package/dist/core/work-digest/read-transcript-timestamps.js +40 -0
  282. package/dist/core/work-digest/read-transcript-timestamps.js.map +1 -0
  283. package/dist/core/work-digest/topics.d.ts +18 -0
  284. package/dist/core/work-digest/topics.js +26 -0
  285. package/dist/core/work-digest/topics.js.map +1 -0
  286. package/dist/core/work-digest/types.d.ts +45 -0
  287. package/dist/core/work-digest/types.js +2 -0
  288. package/dist/core/work-digest/types.js.map +1 -0
  289. package/dist/core/workstream/bind.d.ts +26 -0
  290. package/dist/core/workstream/bind.js +42 -0
  291. package/dist/core/workstream/bind.js.map +1 -0
  292. package/dist/core/workstream/compose-recall.d.ts +2 -0
  293. package/dist/core/workstream/compose-recall.js +39 -0
  294. package/dist/core/workstream/compose-recall.js.map +1 -0
  295. package/dist/core/workstream/merge-suggest.d.ts +19 -0
  296. package/dist/core/workstream/merge-suggest.js +61 -0
  297. package/dist/core/workstream/merge-suggest.js.map +1 -0
  298. package/dist/core/workstream/model.d.ts +21 -0
  299. package/dist/core/workstream/model.js +9 -0
  300. package/dist/core/workstream/model.js.map +1 -0
  301. package/dist/core/workstream/name-match.d.ts +8 -0
  302. package/dist/core/workstream/name-match.js +18 -0
  303. package/dist/core/workstream/name-match.js.map +1 -0
  304. package/dist/core/workstream/resolve.d.ts +10 -0
  305. package/dist/core/workstream/resolve.js +21 -0
  306. package/dist/core/workstream/resolve.js.map +1 -0
  307. package/dist/core/workstream/rollup.d.ts +12 -0
  308. package/dist/core/workstream/rollup.js +17 -0
  309. package/dist/core/workstream/rollup.js.map +1 -0
  310. package/dist/core/workstream/work-topics.d.ts +10 -0
  311. package/dist/core/workstream/work-topics.js +55 -0
  312. package/dist/core/workstream/work-topics.js.map +1 -0
  313. package/dist/hook/hook-helpers.d.ts +4 -0
  314. package/dist/hook/hook-helpers.js +36 -0
  315. package/dist/hook/hook-helpers.js.map +1 -0
  316. package/dist/hook/pre-compact-hook.d.ts +23 -0
  317. package/dist/hook/pre-compact-hook.js +67 -0
  318. package/dist/hook/pre-compact-hook.js.map +1 -0
  319. package/dist/hook/prompt-recall-hook.d.ts +38 -0
  320. package/dist/hook/prompt-recall-hook.js +124 -20
  321. package/dist/hook/prompt-recall-hook.js.map +1 -1
  322. package/dist/hook/recall-gate.d.ts +29 -0
  323. package/dist/hook/recall-gate.js +50 -0
  324. package/dist/hook/recall-gate.js.map +1 -0
  325. package/dist/hook/recall-over-http.d.ts +3 -2
  326. package/dist/hook/recall-over-http.js +26 -13
  327. package/dist/hook/recall-over-http.js.map +1 -1
  328. package/dist/hook/recent-context.d.ts +25 -0
  329. package/dist/hook/recent-context.js +104 -0
  330. package/dist/hook/recent-context.js.map +1 -0
  331. package/dist/hook/score-floor.d.ts +27 -0
  332. package/dist/hook/score-floor.js +42 -0
  333. package/dist/hook/score-floor.js.map +1 -0
  334. package/dist/hook/session-end-hook.js +8 -29
  335. package/dist/hook/session-end-hook.js.map +1 -1
  336. package/dist/hook/session-start-hook.js +26 -60
  337. package/dist/hook/session-start-hook.js.map +1 -1
  338. package/dist/hook/stop-hook.js +24 -60
  339. package/dist/hook/stop-hook.js.map +1 -1
  340. package/dist/http/app.d.ts +6 -0
  341. package/dist/http/app.js +52 -44
  342. package/dist/http/app.js.map +1 -1
  343. package/dist/install/setup.js +2 -0
  344. package/dist/install/setup.js.map +1 -1
  345. package/dist/llm/build-classifier.d.ts +22 -0
  346. package/dist/llm/build-classifier.js +42 -0
  347. package/dist/llm/build-classifier.js.map +1 -0
  348. package/dist/llm/build-embedder.d.ts +11 -0
  349. package/dist/llm/build-embedder.js +42 -0
  350. package/dist/llm/build-embedder.js.map +1 -0
  351. package/dist/llm/bundled-embedder-client.d.ts +37 -0
  352. package/dist/llm/bundled-embedder-client.js +90 -0
  353. package/dist/llm/bundled-embedder-client.js.map +1 -0
  354. package/dist/llm/classifier-box.d.ts +13 -5
  355. package/dist/llm/classifier-box.js +28 -6
  356. package/dist/llm/classifier-box.js.map +1 -1
  357. package/dist/llm/classifier-egress.d.ts +18 -0
  358. package/dist/llm/classifier-egress.js +59 -0
  359. package/dist/llm/classifier-egress.js.map +1 -0
  360. package/dist/llm/client-shared.d.ts +4 -0
  361. package/dist/llm/client-shared.js +39 -0
  362. package/dist/llm/client-shared.js.map +1 -0
  363. package/dist/llm/deepseek-client.d.ts +20 -0
  364. package/dist/llm/deepseek-client.js +59 -45
  365. package/dist/llm/deepseek-client.js.map +1 -1
  366. package/dist/llm/embedder-info.d.ts +15 -0
  367. package/dist/llm/embedder-info.js +27 -0
  368. package/dist/llm/embedder-info.js.map +1 -0
  369. package/dist/llm/model-quirks.d.ts +4 -0
  370. package/dist/llm/model-quirks.js +7 -0
  371. package/dist/llm/model-quirks.js.map +1 -0
  372. package/dist/llm/naming.d.ts +5 -0
  373. package/dist/llm/naming.js +20 -0
  374. package/dist/llm/naming.js.map +1 -0
  375. package/dist/llm/ollama-client.d.ts +7 -8
  376. package/dist/llm/ollama-client.js +59 -51
  377. package/dist/llm/ollama-client.js.map +1 -1
  378. package/dist/llm/ollama-code-embedder.d.ts +8 -3
  379. package/dist/llm/ollama-code-embedder.js +14 -24
  380. package/dist/llm/ollama-code-embedder.js.map +1 -1
  381. package/dist/llm/openai-code-embedder-client.d.ts +25 -0
  382. package/dist/llm/openai-code-embedder-client.js +52 -0
  383. package/dist/llm/openai-code-embedder-client.js.map +1 -0
  384. package/dist/llm/openai-embedder-client.d.ts +41 -0
  385. package/dist/llm/openai-embedder-client.js +83 -0
  386. package/dist/llm/openai-embedder-client.js.map +1 -0
  387. package/dist/mcp/server.d.ts +69 -2
  388. package/dist/mcp/server.js +404 -19
  389. package/dist/mcp/server.js.map +1 -1
  390. package/dist/ports/code-embedder.d.ts +6 -1
  391. package/dist/ports/code-exemplar-store.d.ts +5 -0
  392. package/dist/ports/entity-store.d.ts +35 -0
  393. package/dist/ports/entity-store.js +7 -0
  394. package/dist/ports/entity-store.js.map +1 -0
  395. package/dist/ports/fact-store.d.ts +0 -0
  396. package/dist/ports/fact-store.js.map +1 -1
  397. package/dist/ports/llm-client.d.ts +9 -1
  398. package/dist/ports/outcome.d.ts +85 -0
  399. package/dist/ports/outcome.js +11 -0
  400. package/dist/ports/outcome.js.map +1 -0
  401. package/dist/ports/session-store.d.ts +12 -6
  402. package/dist/ports/storage.d.ts +7 -30
  403. package/dist/ports/storage.js +3 -7
  404. package/dist/ports/storage.js.map +1 -1
  405. package/dist/ports/transcript-adapter.d.ts +2 -0
  406. package/dist/ports/workstream-store.d.ts +22 -0
  407. package/dist/ports/workstream-store.js +2 -0
  408. package/dist/ports/workstream-store.js.map +1 -0
  409. package/dist/shared/net.d.ts +3 -0
  410. package/dist/shared/net.js +4 -0
  411. package/dist/shared/net.js.map +1 -0
  412. package/dist/shared/types.d.ts +56 -2
  413. package/dist/shared/types.js +4 -1
  414. package/dist/shared/types.js.map +1 -1
  415. package/dist/ui/assets/{index-CMpQkbQy.js → index-CSaBtgXD.js} +1 -1
  416. package/dist/ui/index.html +1 -1
  417. package/fixtures/classifier-gold/README.md +94 -0
  418. package/fixtures/classifier-gold/reference.json +318 -0
  419. package/fixtures/classifier-gold/transcripts/01-bug-fix-null-pointer-api.txt +75 -0
  420. package/fixtures/classifier-gold/transcripts/02-bug-fix-race-condition-queue.txt +77 -0
  421. package/fixtures/classifier-gold/transcripts/03-bug-fix-jwt-clock-skew.txt +86 -0
  422. package/fixtures/classifier-gold/transcripts/04-feature-webhook-retry.txt +95 -0
  423. package/fixtures/classifier-gold/transcripts/05-feature-csv-export.txt +86 -0
  424. package/fixtures/classifier-gold/transcripts/06-feature-rate-limiting.txt +61 -0
  425. package/fixtures/classifier-gold/transcripts/07-refactor-db-abstraction.txt +72 -0
  426. package/fixtures/classifier-gold/transcripts/08-refactor-config-loading.txt +89 -0
  427. package/fixtures/classifier-gold/transcripts/09-ops-docker-staging.txt +94 -0
  428. package/fixtures/classifier-gold/transcripts/10-ops-nginx-ssl.txt +121 -0
  429. package/fixtures/classifier-gold/transcripts/11-ops-postgres-migration.txt +74 -0
  430. package/fixtures/classifier-gold/transcripts/12-ops-cron-debugging.txt +73 -0
  431. package/fixtures/classifier-gold/transcripts/13-research-vector-db-comparison.txt +59 -0
  432. package/fixtures/classifier-gold/transcripts/14-research-oauth-provider.txt +74 -0
  433. package/fixtures/classifier-gold/transcripts/15-writing-api-docs.txt +154 -0
  434. package/fixtures/classifier-gold/transcripts/16-meeting-sprint-planning.txt +69 -0
  435. package/fixtures/classifier-gold/transcripts/17-trivial-list-files.txt +14 -0
  436. package/fixtures/classifier-gold/transcripts/18-trivial-version-check.txt +21 -0
  437. package/fixtures/classifier-gold/transcripts/19-trivial-syntax-help.txt +29 -0
  438. package/fixtures/classifier-gold/transcripts/20-trivial-read-package-json.txt +41 -0
  439. package/migrations/000_initial_schema.sql +1 -1
  440. package/migrations/024_backfill_exemplar_repo_basename.sql +32 -0
  441. package/migrations/025_workstreams.sql +30 -0
  442. package/migrations/026_embedding_config.sql +12 -0
  443. package/migrations/027_drop_legacy_session_embeddings.sql +7 -0
  444. package/migrations/028_classifier_provenance.sql +5 -0
  445. package/migrations/029_project_scope.sql +21 -0
  446. package/migrations/032_session_derivables.sql +11 -0
  447. package/migrations/pg/001_initial.sql +34 -0
  448. package/migrations/pg/019_split_replaces.sql +16 -7
  449. package/migrations/pg/025_workstreams.sql +34 -0
  450. package/migrations/pg/026_embedding_config.sql +12 -0
  451. package/migrations/pg/028_classifier_provenance.sql +5 -0
  452. package/migrations/pg/029_entity_variants.sql +9 -0
  453. package/migrations/pg/030_project_scope.sql +23 -0
  454. package/migrations/pg/031_widen_session_edges_kind.sql +11 -0
  455. package/migrations/pg/032_session_derivables.sql +17 -0
  456. package/nlm/index.js +405 -39
  457. package/package.json +12 -2
  458. package/plugin/.codex-plugin/plugin.json +2 -2
  459. package/plugin/scripts/prompt-recall-hook.mjs +413 -43
  460. package/plugin/scripts/session-start-hook.mjs +295 -61
  461. package/plugin/scripts/stop-hook.mjs +89 -66
  462. package/plugin-hermes-agent/__init__.py +2 -1
  463. package/templates/agent-contract/README.md +24 -0
  464. package/templates/agent-contract/claude-code.md +40 -0
  465. package/templates/agent-contract/generic.md +38 -0
  466. package/dist/core/embedding/embed-normalize.d.ts +0 -28
  467. package/dist/core/embedding/embed-normalize.js +0 -98
  468. package/dist/core/embedding/embed-normalize.js.map +0 -1
  469. package/dist/core/storage/pg-tx-context.d.ts +0 -52
  470. package/dist/core/storage/pg-tx-context.js +0 -149
  471. package/dist/core/storage/pg-tx-context.js.map +0 -1
@@ -21,7 +21,15 @@ function appendHookLog(entry) {
21
21
  }
22
22
 
23
23
  // src/core/hook/memo.ts
24
- import { existsSync, mkdirSync as mkdirSync2, readFileSync, rmSync, writeFileSync } from "node:fs";
24
+ import {
25
+ existsSync,
26
+ mkdirSync as mkdirSync2,
27
+ readdirSync,
28
+ readFileSync,
29
+ rmSync,
30
+ statSync,
31
+ writeFileSync
32
+ } from "node:fs";
25
33
  import { homedir as homedir2 } from "node:os";
26
34
  import { join as join2 } from "node:path";
27
35
  function stateDir() {
@@ -53,6 +61,26 @@ function recordSurfaced(conversationId, ids) {
53
61
  }
54
62
 
55
63
  // src/core/hook/pointer-block.ts
64
+ function truncateSummary(s, max = 200) {
65
+ if (s.length <= max) return s;
66
+ const window = s.slice(0, max);
67
+ const last = window[window.length - 1];
68
+ if ((last === "." || last === "!" || last === "?") && window.length - 1 >= 60) {
69
+ return window;
70
+ }
71
+ for (let i = window.length - 2; i >= 60; i--) {
72
+ const c = window[i];
73
+ if ((c === "." || c === "!" || c === "?") && window[i + 1] === " ") {
74
+ return s.slice(0, i + 1);
75
+ }
76
+ }
77
+ for (let i = window.length - 1; i >= 60; i--) {
78
+ if (window[i] === " ") {
79
+ return s.slice(0, i) + " ...";
80
+ }
81
+ }
82
+ return window + " ...";
83
+ }
56
84
  function formatPointerBlock(hits, facts = [], exemplars = []) {
57
85
  if (hits.length === 0 && facts.length === 0 && exemplars.length === 0) return "";
58
86
  const out = [];
@@ -61,7 +89,7 @@ function formatPointerBlock(hits, facts = [], exemplars = []) {
61
89
  for (const h of hits) {
62
90
  const datePart = h.startedAt.slice(0, 10);
63
91
  if (h.summary) {
64
- out.push(`- ${h.id} \xB7 ${h.label} (${datePart}) \u2014 ${h.summary.slice(0, 120)}`);
92
+ out.push(`- ${h.id} \xB7 ${h.label} (${datePart}) \u2014 ${truncateSummary(h.summary)}`);
65
93
  } else {
66
94
  out.push(`- ${h.id} \xB7 ${h.label} (${datePart})`);
67
95
  }
@@ -80,7 +108,7 @@ function formatPointerBlock(hits, facts = [], exemplars = []) {
80
108
  out.push("## Related code exemplars (nlm-memory)");
81
109
  for (const e of exemplars) {
82
110
  const langPart = e.lang ? `${e.lang} \xB7 ` : "";
83
- out.push(`- [${e.outcome}] ${langPart}${e.repo} - ${e.taskContext.slice(0, 120)}`);
111
+ out.push(`- [${e.outcome}] ${langPart}${e.repo} - ${truncateSummary(e.taskContext)}`);
84
112
  }
85
113
  }
86
114
  const tools = exemplars.length > 0 ? "NLM tools: recall_sessions (search), get_session (full transcript), recall_facts (prior decisions), get_fact_history (how a decision evolved), recall_code (pull the full code for a related exemplar)." : "NLM tools: recall_sessions (search), get_session (full transcript), recall_facts (prior decisions), get_fact_history (how a decision evolved).";
@@ -89,10 +117,16 @@ function formatPointerBlock(hits, facts = [], exemplars = []) {
89
117
  }
90
118
 
91
119
  // src/core/hook/select.ts
120
+ function medianScore(hits) {
121
+ if (hits.length === 0) return 0;
122
+ const sorted = hits.map((h) => h.matchScore).sort((a, b) => a - b);
123
+ return sorted[Math.floor(sorted.length / 2)] ?? 0;
124
+ }
92
125
  function selectHits(params) {
93
- const { hits, surfaced, scoreThreshold, perFireCap, perConversationCap } = params;
126
+ const { hits, surfaced, scoreThreshold, perFireCap, perConversationCap, relativeFloor = 0 } = params;
127
+ const relCut = relativeFloor > 0 ? relativeFloor * medianScore(hits) : 0;
94
128
  const eligible = hits.filter(
95
- (h) => h.matchScore >= scoreThreshold && !surfaced.has(h.id)
129
+ (h) => h.matchScore >= scoreThreshold && h.matchScore >= relCut && !surfaced.has(h.id)
96
130
  );
97
131
  const budget = Math.max(0, perConversationCap - surfaced.size);
98
132
  const limit = Math.min(perFireCap, budget);
@@ -149,12 +183,248 @@ function hookAuthHeaders(extra = {}) {
149
183
  return { ...extra, authorization: `Bearer ${token}` };
150
184
  }
151
185
 
186
+ // src/hook/score-floor.ts
187
+ function parseScoreFloor(raw) {
188
+ if (raw === void 0) return 0;
189
+ const parsed = Number(raw);
190
+ if (!Number.isFinite(parsed) || parsed < 0) return 0;
191
+ return parsed;
192
+ }
193
+ function parseRelativeFloor(raw, fallback) {
194
+ if (raw === void 0) return fallback;
195
+ const parsed = Number(raw);
196
+ if (!Number.isFinite(parsed) || parsed < 0) return fallback;
197
+ return parsed;
198
+ }
199
+
200
+ // src/core/hook/query-extract.ts
201
+ var STOPWORDS = /* @__PURE__ */ new Set([
202
+ "a",
203
+ "an",
204
+ "the",
205
+ "is",
206
+ "are",
207
+ "was",
208
+ "were",
209
+ "be",
210
+ "been",
211
+ "being",
212
+ "have",
213
+ "has",
214
+ "had",
215
+ "do",
216
+ "does",
217
+ "did",
218
+ "will",
219
+ "would",
220
+ "could",
221
+ "should",
222
+ "may",
223
+ "might",
224
+ "shall",
225
+ "can",
226
+ "dare",
227
+ "ought",
228
+ "yes",
229
+ "no",
230
+ "not",
231
+ "please",
232
+ "thank",
233
+ "thanks",
234
+ "ok",
235
+ "okay",
236
+ "i",
237
+ "me",
238
+ "my",
239
+ "we",
240
+ "us",
241
+ "our",
242
+ "you",
243
+ "your",
244
+ "it",
245
+ "its",
246
+ "this",
247
+ "that",
248
+ "these",
249
+ "those",
250
+ "and",
251
+ "or",
252
+ "but",
253
+ "if",
254
+ "so",
255
+ "to",
256
+ "of",
257
+ "in",
258
+ "on",
259
+ "at",
260
+ "by",
261
+ "for",
262
+ "from",
263
+ "with",
264
+ "about",
265
+ "into",
266
+ "through",
267
+ "during",
268
+ "before",
269
+ "after",
270
+ "above",
271
+ "below",
272
+ "up",
273
+ "down",
274
+ "out",
275
+ "off",
276
+ "over",
277
+ "under",
278
+ "again",
279
+ "further",
280
+ "then",
281
+ "once",
282
+ "here",
283
+ "there",
284
+ "when",
285
+ "where",
286
+ "why",
287
+ "how",
288
+ "all",
289
+ "both",
290
+ "each",
291
+ "few",
292
+ "more",
293
+ "most",
294
+ "other",
295
+ "some",
296
+ "such",
297
+ "than",
298
+ "too",
299
+ "very",
300
+ "just",
301
+ "now",
302
+ "also",
303
+ "get",
304
+ "let",
305
+ "what",
306
+ "which",
307
+ "who",
308
+ "whom",
309
+ "whose",
310
+ "any",
311
+ "much",
312
+ "many",
313
+ "sounds",
314
+ "good",
315
+ "great",
316
+ "sure",
317
+ "right",
318
+ "well",
319
+ "done",
320
+ "nice",
321
+ "cool",
322
+ "perfect",
323
+ "exactly",
324
+ "proceed",
325
+ "continue",
326
+ "go",
327
+ "ahead",
328
+ "next",
329
+ "help"
330
+ ]);
331
+ var MIN_CONTENT_WORDS = 2;
332
+ var MIN_WORD_LEN = 3;
333
+ var SYSTEM_MESSAGE_PREFIX = /^<(task-notification|command-name|command-message|command-args|local-command-stdout|local-command-caveat|output-file|system-reminder)\b/;
334
+ function extractRecallQuery(prompt) {
335
+ if (SYSTEM_MESSAGE_PREFIX.test(prompt.trim())) return null;
336
+ const tokens = prompt.trim().split(/\s+/).map((t) => t.replace(/^[^\w-]+|[^\w-]+$/g, "")).filter((t) => t.length >= MIN_WORD_LEN);
337
+ const contentWords = tokens.filter((t) => !STOPWORDS.has(t.toLowerCase()));
338
+ if (contentWords.length < MIN_CONTENT_WORDS) return null;
339
+ return contentWords.join(" ");
340
+ }
341
+
342
+ // src/hook/hook-helpers.ts
343
+ function readStdin() {
344
+ return new Promise((resolve2) => {
345
+ let data = "";
346
+ process.stdin.setEncoding("utf8");
347
+ process.stdin.on("data", (chunk) => data += chunk);
348
+ process.stdin.on("end", () => resolve2(data));
349
+ process.stdin.on("error", () => resolve2(data));
350
+ });
351
+ }
352
+ async function fetchWithTimeout(url, init, ms) {
353
+ const controller = new AbortController();
354
+ const timer = setTimeout(() => controller.abort(), ms);
355
+ try {
356
+ return await fetch(url, { ...init, signal: controller.signal });
357
+ } finally {
358
+ clearTimeout(timer);
359
+ }
360
+ }
361
+ function hookModeFromEnv() {
362
+ return process.env["NLM_HOOK_MODE"] === "live" ? "live" : "shadow";
363
+ }
364
+
365
+ // src/shared/net.ts
366
+ var DEFAULT_NLM_PORT = "3940";
367
+
368
+ // src/hook/recall-over-http.ts
369
+ var RECALL_LIMIT = 5;
370
+ function parseRecallTimeout(raw) {
371
+ if (raw === void 0) return 4e3;
372
+ const n = Number(raw);
373
+ return Number.isFinite(n) && n > 0 ? n : 4e3;
374
+ }
375
+ var RECALL_TIMEOUT_MS = parseRecallTimeout(process.env["NLM_HOOK_RECALL_TIMEOUT_MS"]);
376
+ async function recallOverHttp(prompt, runtime, conversationId, mode = "keyword") {
377
+ const query = extractRecallQuery(prompt);
378
+ if (query === null) return { hits: [], facts: [], exemplars: [] };
379
+ const portValue = process.env["NLM_PORT"] ?? DEFAULT_NLM_PORT;
380
+ const url = (
381
+ // 127.0.0.1, not localhost: each hook is a fresh process with no connection
382
+ // reuse, and Node resolves localhost to IPv6 ::1 first — a measured ~50-300ms
383
+ // per-fire connect penalty vs ~3ms on the explicit IPv4 loopback.
384
+ `http://127.0.0.1:${portValue}/api/recall?q=${encodeURIComponent(query)}&mode=${mode}&limit=${RECALL_LIMIT}&withFacts=true&withExemplars=true` + (conversationId ? `&conversation_id=${encodeURIComponent(conversationId)}` : "")
385
+ );
386
+ try {
387
+ const extra = { "x-recall-source": "hook" };
388
+ if (runtime) extra["x-recall-runtime"] = runtime;
389
+ const res = await fetchWithTimeout(url, { headers: hookAuthHeaders(extra) }, RECALL_TIMEOUT_MS);
390
+ if (!res.ok) return { hits: [], facts: [], exemplars: [] };
391
+ let body;
392
+ try {
393
+ body = await res.json();
394
+ } catch {
395
+ return { hits: [], facts: [], exemplars: [] };
396
+ }
397
+ const hits = (body.results ?? []).map((r) => ({
398
+ id: r.id,
399
+ label: r.label,
400
+ startedAt: r.startedAt,
401
+ matchScore: r.matchScore,
402
+ ...r.summary !== void 0 ? { summary: r.summary } : {}
403
+ }));
404
+ const facts = (body.relatedFacts ?? []).map((f) => ({
405
+ subject: f.subject,
406
+ predicate: f.predicate,
407
+ value: f.value,
408
+ corroborationCount: f.corroborationCount
409
+ }));
410
+ const exemplars = (body.relatedExemplars ?? []).map((e) => ({
411
+ outcome: e.outcome,
412
+ lang: e.lang,
413
+ repo: e.repo,
414
+ taskContext: e.taskContext
415
+ }));
416
+ return { hits, facts, exemplars };
417
+ } catch {
418
+ return { hits: [], facts: [], exemplars: [] };
419
+ }
420
+ }
421
+
152
422
  // src/hook/session-start-hook.ts
153
- var SCORE_THRESHOLD = 0;
423
+ var SCORE_THRESHOLD = parseScoreFloor(process.env["NLM_RECALL_SCORE_FLOOR"]);
424
+ var RELATIVE_FLOOR = parseRelativeFloor(process.env["NLM_RECALL_REL_FLOOR"], 0.9);
154
425
  var PER_FIRE_CAP = 3;
155
426
  var PER_CONVERSATION_CAP = 10;
156
- var RECALL_LIMIT = 5;
157
- var RECALL_TIMEOUT_MS = 2e3;
427
+ var RECALL_TIMEOUT_MS2 = 2e3;
158
428
  async function runHook(input, deps) {
159
429
  let hits = [];
160
430
  try {
@@ -167,6 +437,7 @@ async function runHook(input, deps) {
167
437
  hits,
168
438
  surfaced,
169
439
  scoreThreshold: SCORE_THRESHOLD,
440
+ relativeFloor: RELATIVE_FLOOR,
170
441
  perFireCap: PER_FIRE_CAP,
171
442
  perConversationCap: PER_CONVERSATION_CAP
172
443
  });
@@ -193,22 +464,17 @@ function composeSessionStartOutput(failureModeBlock, recallBlock) {
193
464
  }
194
465
  async function fetchFailureModeBlock(repo) {
195
466
  if (!repo) return "";
196
- const portValue = process.env["NLM_PORT"] ?? "3940";
197
- const url = `http://localhost:${portValue}/api/signals/failure-modes?repo=${encodeURIComponent(repo)}`;
198
- const controller = new AbortController();
199
- const timer = setTimeout(() => controller.abort(), RECALL_TIMEOUT_MS);
467
+ const portValue = process.env["NLM_PORT"] ?? DEFAULT_NLM_PORT;
468
+ const url = `http://127.0.0.1:${portValue}/api/signals/failure-modes?repo=${encodeURIComponent(repo)}`;
200
469
  try {
201
- const res = await fetch(url, {
202
- headers: hookAuthHeaders({ "x-recall-source": "session-start-hook" }),
203
- signal: controller.signal
204
- });
470
+ const res = await fetchWithTimeout(url, {
471
+ headers: hookAuthHeaders({ "x-recall-source": "session-start-hook" })
472
+ }, RECALL_TIMEOUT_MS2);
205
473
  if (!res.ok) return "";
206
474
  const body = await res.json();
207
475
  return typeof body.block === "string" ? body.block : "";
208
476
  } catch {
209
477
  return "";
210
- } finally {
211
- clearTimeout(timer);
212
478
  }
213
479
  }
214
480
  function buildQuery(workingDirectory, projectName) {
@@ -216,46 +482,6 @@ function buildQuery(workingDirectory, projectName) {
216
482
  const parts = [dirTail, projectName].filter(Boolean);
217
483
  return parts.join(" ").trim() || "session start";
218
484
  }
219
- function readStdin() {
220
- return new Promise((resolve2) => {
221
- let data = "";
222
- process.stdin.setEncoding("utf8");
223
- process.stdin.on("data", (chunk) => data += chunk);
224
- process.stdin.on("end", () => resolve2(data));
225
- process.stdin.on("error", () => resolve2(data));
226
- });
227
- }
228
- async function recallOverHttp(query, conversationId) {
229
- const portValue = process.env["NLM_PORT"] ?? "3940";
230
- const url = `http://localhost:${portValue}/api/recall?q=${encodeURIComponent(query)}&mode=hybrid&limit=${RECALL_LIMIT}` + (conversationId ? `&conversation_id=${encodeURIComponent(conversationId)}` : "");
231
- const controller = new AbortController();
232
- const timer = setTimeout(() => controller.abort(), RECALL_TIMEOUT_MS);
233
- try {
234
- const res = await fetch(url, {
235
- headers: hookAuthHeaders({
236
- "x-recall-source": "session-start-hook",
237
- "x-recall-runtime": "claude-code"
238
- }),
239
- signal: controller.signal
240
- });
241
- if (!res.ok) return [];
242
- let body;
243
- try {
244
- body = await res.json();
245
- } catch {
246
- return [];
247
- }
248
- return (body.results ?? []).map((r) => ({
249
- id: r.id,
250
- label: r.label,
251
- startedAt: r.startedAt,
252
- matchScore: r.matchScore,
253
- ...r.summary !== void 0 ? { summary: r.summary } : {}
254
- }));
255
- } finally {
256
- clearTimeout(timer);
257
- }
258
- }
259
485
  async function main() {
260
486
  try {
261
487
  autoloadEnv();
@@ -265,9 +491,17 @@ async function main() {
265
491
  const workingDirectory = typeof payload.cwd === "string" ? payload.cwd : typeof payload.working_directory === "string" ? payload.working_directory : "";
266
492
  const projectName = typeof payload.project_name === "string" ? payload.project_name : "";
267
493
  const query = buildQuery(workingDirectory, projectName);
268
- const mode = process.env["NLM_HOOK_MODE"] === "live" ? "live" : "shadow";
269
- const out = await runHook({ conversationId, query }, { mode, recall: (q, cid) => recallOverHttp(q, cid === "unknown" ? void 0 : cid) });
270
- const failureModes = mode === "live" ? await fetchFailureModeBlock(workingDirectory) : "";
494
+ const mode = hookModeFromEnv();
495
+ const [out, failureModes] = await Promise.all([
496
+ runHook(
497
+ { conversationId, query },
498
+ {
499
+ mode,
500
+ recall: async (q, cid) => (await recallOverHttp(q, "claude-code", cid === "unknown" ? void 0 : cid, "hybrid")).hits
501
+ }
502
+ ),
503
+ mode === "live" ? fetchFailureModeBlock(workingDirectory) : Promise.resolve("")
504
+ ]);
271
505
  const combined = composeSessionStartOutput(failureModes, out);
272
506
  if (combined) process.stdout.write(combined);
273
507
  } catch {
@@ -2,9 +2,6 @@
2
2
 
3
3
  // src/hook/stop-hook.ts
4
4
  import { pathToFileURL } from "node:url";
5
- import { appendFileSync, mkdirSync as mkdirSync3 } from "node:fs";
6
- import { homedir as homedir5 } from "node:os";
7
- import { dirname as dirname2, join as join4 } from "node:path";
8
5
 
9
6
  // src/core/hook/citation-detect.ts
10
7
  var MIN_ID_LEN = 6;
@@ -96,7 +93,15 @@ function extractId(input) {
96
93
  }
97
94
 
98
95
  // src/core/hook/memo.ts
99
- import { existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
96
+ import {
97
+ existsSync,
98
+ mkdirSync,
99
+ readdirSync,
100
+ readFileSync,
101
+ rmSync,
102
+ statSync,
103
+ writeFileSync
104
+ } from "node:fs";
100
105
  import { homedir } from "node:os";
101
106
  import { join } from "node:path";
102
107
  function stateDir() {
@@ -163,10 +168,10 @@ function isEnabled() {
163
168
  if (raw === void 0) return true;
164
169
  return raw !== "0" && raw.toLowerCase() !== "false";
165
170
  }
166
- async function appendMisses(entries, logPath2) {
171
+ async function appendMisses(entries, logPath) {
167
172
  if (!isEnabled()) return;
168
173
  if (entries.length === 0) return;
169
- const path = logPath2 ?? defaultLogPath();
174
+ const path = logPath ?? defaultLogPath();
170
175
  try {
171
176
  await mkdir(dirname(path), { recursive: true });
172
177
  const ts = (/* @__PURE__ */ new Date()).toISOString();
@@ -178,7 +183,7 @@ async function appendMisses(entries, logPath2) {
178
183
  }
179
184
 
180
185
  // src/core/hook/transcript.ts
181
- import { existsSync as existsSync3, readFileSync as readFileSync3 } from "node:fs";
186
+ import { closeSync, existsSync as existsSync3, fstatSync, openSync, readSync } from "node:fs";
182
187
  function parseTurn(parsed) {
183
188
  if (parsed.type !== "assistant" || !parsed.message) return null;
184
189
  const content = parsed.message.content;
@@ -198,10 +203,22 @@ function parseTurn(parsed) {
198
203
  if (textParts.length === 0 && toolUses.length === 0) return null;
199
204
  return { text: textParts.join("\n"), toolUses };
200
205
  }
206
+ var MAX_TRANSCRIPT_BYTES = 256 * 1024;
201
207
  function readLines(transcriptPath) {
202
208
  if (!transcriptPath || !existsSync3(transcriptPath)) return null;
203
209
  try {
204
- return readFileSync3(transcriptPath, "utf8").split("\n");
210
+ const fd = openSync(transcriptPath, "r");
211
+ try {
212
+ const size = fstatSync(fd).size;
213
+ const start = size > MAX_TRANSCRIPT_BYTES ? size - MAX_TRANSCRIPT_BYTES : 0;
214
+ const len = size - start;
215
+ if (len <= 0) return [];
216
+ const buf = Buffer.alloc(len);
217
+ readSync(fd, buf, 0, len, start);
218
+ return buf.toString("utf8").split("\n");
219
+ } finally {
220
+ closeSync(fd);
221
+ }
205
222
  } catch {
206
223
  return null;
207
224
  }
@@ -226,7 +243,7 @@ function readAllAssistantTurns(transcriptPath) {
226
243
  }
227
244
 
228
245
  // src/llm/env-autoload.ts
229
- import { readFileSync as readFileSync4, existsSync as existsSync4 } from "node:fs";
246
+ import { readFileSync as readFileSync3, existsSync as existsSync4 } from "node:fs";
230
247
  import { homedir as homedir4 } from "node:os";
231
248
  import { resolve } from "node:path";
232
249
  var DEFAULT_SEARCH_PATHS = [
@@ -246,7 +263,7 @@ function autoloadEnv(extraPaths = []) {
246
263
  const path = expandHome(raw);
247
264
  if (!existsSync4(path)) continue;
248
265
  try {
249
- const content = readFileSync4(path, "utf8");
266
+ const content = readFileSync3(path, "utf8");
250
267
  for (const line of content.split("\n")) {
251
268
  const trimmed = line.trim();
252
269
  if (!trimmed || trimmed.startsWith("#") || !trimmed.includes("=")) continue;
@@ -275,6 +292,44 @@ function hookAuthHeaders(extra = {}) {
275
292
  return { ...extra, authorization: `Bearer ${token}` };
276
293
  }
277
294
 
295
+ // src/hook/hook-helpers.ts
296
+ import { appendFileSync, mkdirSync as mkdirSync3 } from "node:fs";
297
+ import { homedir as homedir5 } from "node:os";
298
+ import { dirname as dirname2, join as join4 } from "node:path";
299
+ function readStdin() {
300
+ return new Promise((resolve2) => {
301
+ let data = "";
302
+ process.stdin.setEncoding("utf8");
303
+ process.stdin.on("data", (chunk) => data += chunk);
304
+ process.stdin.on("end", () => resolve2(data));
305
+ process.stdin.on("error", () => resolve2(data));
306
+ });
307
+ }
308
+ async function fetchWithTimeout(url, init, ms) {
309
+ const controller = new AbortController();
310
+ const timer = setTimeout(() => controller.abort(), ms);
311
+ try {
312
+ return await fetch(url, { ...init, signal: controller.signal });
313
+ } finally {
314
+ clearTimeout(timer);
315
+ }
316
+ }
317
+ function hookModeFromEnv() {
318
+ return process.env["NLM_HOOK_MODE"] === "live" ? "live" : "shadow";
319
+ }
320
+ function appendHookEvent(data) {
321
+ try {
322
+ const path = process.env["NLM_HOOK_LOG"] ?? join4(homedir5(), ".nlm", "hook-log.jsonl");
323
+ mkdirSync3(dirname2(path), { recursive: true });
324
+ appendFileSync(path, `${JSON.stringify(data)}
325
+ `, "utf8");
326
+ } catch {
327
+ }
328
+ }
329
+
330
+ // src/shared/net.ts
331
+ var DEFAULT_NLM_PORT = "3940";
332
+
278
333
  // src/hook/stop-hook.ts
279
334
  var RESPONSE_PREVIEW_CHARS = 200;
280
335
  var POST_TIMEOUT_MS = 1500;
@@ -335,12 +390,9 @@ async function runStopHook(input, deps) {
335
390
  }
336
391
  const lastText = turns[turns.length - 1]?.text ?? "";
337
392
  const preview = lastText.slice(0, RESPONSE_PREVIEW_CHARS);
338
- for (const c of fresh) {
339
- try {
340
- await deps.postCitation(input.conversationId, c.id, c.kind, preview);
341
- } catch {
342
- }
343
- }
393
+ await Promise.allSettled(
394
+ fresh.map((c) => deps.postCitation(input.conversationId, c.id, c.kind, preview))
395
+ );
344
396
  if (fresh.length > 0) {
345
397
  recordCited(input.conversationId, fresh.map((c) => c.id));
346
398
  }
@@ -352,60 +404,31 @@ async function runStopHook(input, deps) {
352
404
  skipped: false
353
405
  };
354
406
  }
355
- function logPath() {
356
- return process.env["NLM_HOOK_LOG"] ?? join4(homedir5(), ".nlm", "hook-log.jsonl");
357
- }
358
407
  function logStopResult(result) {
359
- try {
360
- const path = logPath();
361
- mkdirSync3(dirname2(path), { recursive: true });
362
- appendFileSync(
363
- path,
364
- `${JSON.stringify({
365
- ts: (/* @__PURE__ */ new Date()).toISOString(),
366
- kind: "stop",
367
- conversationId: result.conversationId,
368
- surfacedCount: result.surfacedCount,
369
- citedIds: result.citations.map((c) => c.id),
370
- citationKinds: result.citations.map((c) => c.kind),
371
- skipped: result.skipped,
372
- mode: process.env["NLM_HOOK_MODE"] === "live" ? "live" : "shadow"
373
- })}
374
- `,
375
- "utf8"
376
- );
377
- } catch {
378
- }
379
- }
380
- function readStdin() {
381
- return new Promise((resolve2) => {
382
- let data = "";
383
- process.stdin.setEncoding("utf8");
384
- process.stdin.on("data", (chunk) => data += chunk);
385
- process.stdin.on("end", () => resolve2(data));
386
- process.stdin.on("error", () => resolve2(data));
408
+ appendHookEvent({
409
+ ts: (/* @__PURE__ */ new Date()).toISOString(),
410
+ kind: "stop",
411
+ conversationId: result.conversationId,
412
+ surfacedCount: result.surfacedCount,
413
+ citedIds: result.citations.map((c) => c.id),
414
+ citationKinds: result.citations.map((c) => c.kind),
415
+ skipped: result.skipped,
416
+ mode: hookModeFromEnv()
387
417
  });
388
418
  }
389
419
  async function postCitationOverHttp(conversationId, citedId, kind, responsePreview) {
390
- const port = process.env["NLM_PORT"] ?? "3940";
391
- const url = `http://localhost:${port}/api/recall/cite-event`;
392
- const controller = new AbortController();
393
- const timer = setTimeout(() => controller.abort(), POST_TIMEOUT_MS);
394
- try {
395
- await fetch(url, {
396
- method: "POST",
397
- headers: hookAuthHeaders({ "content-type": "application/json" }),
398
- body: JSON.stringify({
399
- ...conversationId !== "unknown" ? { conversation_id: conversationId } : {},
400
- cited_id: citedId,
401
- kind,
402
- response_preview: responsePreview
403
- }),
404
- signal: controller.signal
405
- });
406
- } finally {
407
- clearTimeout(timer);
408
- }
420
+ const port = process.env["NLM_PORT"] ?? DEFAULT_NLM_PORT;
421
+ const url = `http://127.0.0.1:${port}/api/recall/cite-event`;
422
+ await fetchWithTimeout(url, {
423
+ method: "POST",
424
+ headers: hookAuthHeaders({ "content-type": "application/json" }),
425
+ body: JSON.stringify({
426
+ ...conversationId !== "unknown" ? { conversation_id: conversationId } : {},
427
+ cited_id: citedId,
428
+ kind,
429
+ response_preview: responsePreview
430
+ })
431
+ }, POST_TIMEOUT_MS);
409
432
  }
410
433
  async function main() {
411
434
  try {
@@ -9,6 +9,7 @@ The daemon must be running for hooks to have effect. If it is unreachable,
9
9
  every hook silently returns None so the agent loop is never blocked.
10
10
  """
11
11
 
12
+ import http.client
12
13
  import json
13
14
  import os
14
15
  import urllib.error
@@ -33,7 +34,7 @@ def _post(path: str, payload: dict[str, Any]) -> dict[str, Any]:
33
34
  try:
34
35
  with urllib.request.urlopen(req, timeout=5) as resp:
35
36
  return json.loads(resp.read())
36
- except (urllib.error.URLError, OSError, json.JSONDecodeError):
37
+ except (urllib.error.URLError, OSError, json.JSONDecodeError, http.client.HTTPException):
37
38
  return {}
38
39
 
39
40