nlm-memory 0.21.0 → 0.21.13

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 (413) hide show
  1. package/README.md +67 -12
  2. package/assets/replay-eval-dark.svg +26 -0
  3. package/assets/replay-eval-light.svg +26 -0
  4. package/dist/cli/digest.js +34 -7
  5. package/dist/cli/digest.js.map +1 -1
  6. package/dist/cli/nlm.d.ts +14 -0
  7. package/dist/cli/nlm.js +450 -65
  8. package/dist/cli/nlm.js.map +1 -1
  9. package/dist/cli/reprocess-job-adapters.d.ts +35 -0
  10. package/dist/cli/reprocess-job-adapters.js +80 -0
  11. package/dist/cli/reprocess-job-adapters.js.map +1 -0
  12. package/dist/cli/scope-coverage.js +4 -1
  13. package/dist/cli/scope-coverage.js.map +1 -1
  14. package/dist/cli/supersede.js +8 -7
  15. package/dist/cli/supersede.js.map +1 -1
  16. package/dist/core/actions/actions-log.d.ts +65 -8
  17. package/dist/core/actions/actions-log.js +134 -17
  18. package/dist/core/actions/actions-log.js.map +1 -1
  19. package/dist/core/actions/overlay.d.ts +11 -2
  20. package/dist/core/actions/overlay.js +18 -6
  21. package/dist/core/actions/overlay.js.map +1 -1
  22. package/dist/core/adapters/from-source.js +11 -3
  23. package/dist/core/adapters/from-source.js.map +1 -1
  24. package/dist/core/adapters/fs-wiki-writer.d.ts +44 -0
  25. package/dist/core/adapters/fs-wiki-writer.js +132 -0
  26. package/dist/core/adapters/fs-wiki-writer.js.map +1 -0
  27. package/dist/core/adapters/hermes-agent.d.ts +15 -2
  28. package/dist/core/adapters/hermes-agent.js +57 -11
  29. package/dist/core/adapters/hermes-agent.js.map +1 -1
  30. package/dist/core/adapters/memory-wiki-writer.d.ts +8 -0
  31. package/dist/core/adapters/memory-wiki-writer.js +16 -0
  32. package/dist/core/adapters/memory-wiki-writer.js.map +1 -0
  33. package/dist/core/adapters/muse.d.ts +51 -0
  34. package/dist/core/adapters/muse.js +271 -0
  35. package/dist/core/adapters/muse.js.map +1 -0
  36. package/dist/core/adapters/opencode.d.ts +11 -4
  37. package/dist/core/adapters/opencode.js +41 -7
  38. package/dist/core/adapters/opencode.js.map +1 -1
  39. package/dist/core/alerts/alert-state.d.ts +17 -0
  40. package/dist/core/alerts/alert-state.js +45 -0
  41. package/dist/core/alerts/alert-state.js.map +1 -0
  42. package/dist/core/alerts/check-and-alert.d.ts +28 -0
  43. package/dist/core/alerts/check-and-alert.js +53 -0
  44. package/dist/core/alerts/check-and-alert.js.map +1 -0
  45. package/dist/core/alerts/drift-transition.d.ts +27 -0
  46. package/dist/core/alerts/drift-transition.js +56 -0
  47. package/dist/core/alerts/drift-transition.js.map +1 -0
  48. package/dist/core/alerts/embedder-transition.d.ts +23 -0
  49. package/dist/core/alerts/embedder-transition.js +40 -0
  50. package/dist/core/alerts/embedder-transition.js.map +1 -0
  51. package/dist/core/alerts/fire-alert.d.ts +24 -0
  52. package/dist/core/alerts/fire-alert.js +72 -0
  53. package/dist/core/alerts/fire-alert.js.map +1 -0
  54. package/dist/core/alerts/job-alert.d.ts +12 -0
  55. package/dist/core/alerts/job-alert.js +37 -0
  56. package/dist/core/alerts/job-alert.js.map +1 -0
  57. package/dist/core/alerts/types.d.ts +104 -0
  58. package/dist/core/alerts/types.js +11 -0
  59. package/dist/core/alerts/types.js.map +1 -0
  60. package/dist/core/alerts/wiki-alert.d.ts +15 -0
  61. package/dist/core/alerts/wiki-alert.js +31 -0
  62. package/dist/core/alerts/wiki-alert.js.map +1 -0
  63. package/dist/core/classifier/prompt.d.ts +13 -0
  64. package/dist/core/classifier/prompt.js +52 -2
  65. package/dist/core/classifier/prompt.js.map +1 -1
  66. package/dist/core/dataset/build-dataset.d.ts +16 -1
  67. package/dist/core/dataset/build-dataset.js +105 -45
  68. package/dist/core/dataset/build-dataset.js.map +1 -1
  69. package/dist/core/digest/compose.d.ts +8 -0
  70. package/dist/core/digest/compose.js +54 -0
  71. package/dist/core/digest/compose.js.map +1 -1
  72. package/dist/core/digest/hook-injection.d.ts +1 -1
  73. package/dist/core/digest/hook-injection.js +9 -1
  74. package/dist/core/digest/hook-injection.js.map +1 -1
  75. package/dist/core/digest/hook-version-parity.d.ts +31 -0
  76. package/dist/core/digest/hook-version-parity.js +56 -0
  77. package/dist/core/digest/hook-version-parity.js.map +1 -0
  78. package/dist/core/embedding/pg-embed-backfill.js +1 -1
  79. package/dist/core/embedding/pg-embed-backfill.js.map +1 -1
  80. package/dist/core/exemplars/capture-from-session.d.ts +1 -1
  81. package/dist/core/exemplars/capture-from-session.js +3 -3
  82. package/dist/core/exemplars/capture-from-session.js.map +1 -1
  83. package/dist/core/exemplars/embed-backfill.d.ts +9 -0
  84. package/dist/core/exemplars/embed-backfill.js +9 -1
  85. package/dist/core/exemplars/embed-backfill.js.map +1 -1
  86. package/dist/core/exemplars/recall-code.d.ts +1 -1
  87. package/dist/core/exemplars/recall-code.js +2 -2
  88. package/dist/core/exemplars/recall-code.js.map +1 -1
  89. package/dist/core/facts/backfill-facts.d.ts +1 -1
  90. package/dist/core/facts/backfill-facts.js +4 -4
  91. package/dist/core/facts/backfill-facts.js.map +1 -1
  92. package/dist/core/facts/embed-backfill.d.ts +54 -0
  93. package/dist/core/facts/embed-backfill.js +109 -0
  94. package/dist/core/facts/embed-backfill.js.map +1 -0
  95. package/dist/core/facts/reconcile-facts.d.ts +42 -0
  96. package/dist/core/facts/reconcile-facts.js +133 -0
  97. package/dist/core/facts/reconcile-facts.js.map +1 -0
  98. package/dist/core/health/embed-failure-state.d.ts +4 -0
  99. package/dist/core/health/embed-failure-state.js +12 -0
  100. package/dist/core/health/embed-failure-state.js.map +1 -0
  101. package/dist/core/health/warmup-retry.d.ts +27 -0
  102. package/dist/core/health/warmup-retry.js +53 -0
  103. package/dist/core/health/warmup-retry.js.map +1 -0
  104. package/dist/core/health/warmup-state.d.ts +15 -0
  105. package/dist/core/health/warmup-state.js +15 -1
  106. package/dist/core/health/warmup-state.js.map +1 -1
  107. package/dist/core/hook/cite-memo.d.ts +11 -7
  108. package/dist/core/hook/cite-memo.js +25 -17
  109. package/dist/core/hook/cite-memo.js.map +1 -1
  110. package/dist/core/hook/hook-log.d.ts +13 -2
  111. package/dist/core/hook/hook-log.js +16 -8
  112. package/dist/core/hook/hook-log.js.map +1 -1
  113. package/dist/core/hook/memo.d.ts +10 -6
  114. package/dist/core/hook/memo.js +25 -17
  115. package/dist/core/hook/memo.js.map +1 -1
  116. package/dist/core/hook/pointer-block.d.ts +8 -1
  117. package/dist/core/hook/pointer-block.js +17 -4
  118. package/dist/core/hook/pointer-block.js.map +1 -1
  119. package/dist/core/hook/resolve-conversation-by-query.d.ts +6 -5
  120. package/dist/core/hook/resolve-conversation-by-query.js +24 -12
  121. package/dist/core/hook/resolve-conversation-by-query.js.map +1 -1
  122. package/dist/core/ingest/ingest-session.d.ts +1 -1
  123. package/dist/core/ingest/ingest-session.js +3 -3
  124. package/dist/core/ingest/ingest-session.js.map +1 -1
  125. package/dist/core/ingest/reclassify-oversized.d.ts +1 -1
  126. package/dist/core/ingest/reclassify-oversized.js +2 -2
  127. package/dist/core/ingest/reclassify-oversized.js.map +1 -1
  128. package/dist/core/ingest/reprocess.d.ts +1 -1
  129. package/dist/core/ingest/reprocess.js +2 -2
  130. package/dist/core/ingest/reprocess.js.map +1 -1
  131. package/dist/core/jobs/job-supervisor.d.ts +168 -0
  132. package/dist/core/jobs/job-supervisor.js +335 -0
  133. package/dist/core/jobs/job-supervisor.js.map +1 -0
  134. package/dist/core/learner/model.d.ts +63 -0
  135. package/dist/core/learner/model.js +30 -0
  136. package/dist/core/learner/model.js.map +1 -0
  137. package/dist/core/learner/service.d.ts +42 -0
  138. package/dist/core/learner/service.js +74 -0
  139. package/dist/core/learner/service.js.map +1 -0
  140. package/dist/core/learner/summary.d.ts +28 -0
  141. package/dist/core/learner/summary.js +70 -0
  142. package/dist/core/learner/summary.js.map +1 -0
  143. package/dist/core/learner/validate.d.ts +18 -0
  144. package/dist/core/learner/validate.js +95 -0
  145. package/dist/core/learner/validate.js.map +1 -0
  146. package/dist/core/metrics/re-derivation.d.ts +31 -2
  147. package/dist/core/metrics/re-derivation.js +36 -4
  148. package/dist/core/metrics/re-derivation.js.map +1 -1
  149. package/dist/core/outcome/coverage.d.ts +1 -1
  150. package/dist/core/outcome/coverage.js +2 -2
  151. package/dist/core/outcome/coverage.js.map +1 -1
  152. package/dist/core/outcome/rollup.d.ts +1 -1
  153. package/dist/core/outcome/rollup.js +4 -4
  154. package/dist/core/outcome/rollup.js.map +1 -1
  155. package/dist/core/providers/provider-registry.d.ts +40 -23
  156. package/dist/core/providers/provider-registry.js +99 -81
  157. package/dist/core/providers/provider-registry.js.map +1 -1
  158. package/dist/core/recall/citation-log.d.ts +10 -5
  159. package/dist/core/recall/citation-log.js +21 -10
  160. package/dist/core/recall/citation-log.js.map +1 -1
  161. package/dist/core/recall/hook-recall-log.d.ts +5 -2
  162. package/dist/core/recall/hook-recall-log.js +12 -6
  163. package/dist/core/recall/hook-recall-log.js.map +1 -1
  164. package/dist/core/recall/miss-log.d.ts +7 -4
  165. package/dist/core/recall/miss-log.js +18 -11
  166. package/dist/core/recall/miss-log.js.map +1 -1
  167. package/dist/core/recall/query-log.d.ts +8 -5
  168. package/dist/core/recall/query-log.js +16 -9
  169. package/dist/core/recall/query-log.js.map +1 -1
  170. package/dist/core/recall/recall-service.d.ts +10 -1
  171. package/dist/core/recall/recall-service.js +31 -12
  172. package/dist/core/recall/recall-service.js.map +1 -1
  173. package/dist/core/recall/recent-log.d.ts +6 -1
  174. package/dist/core/recall/recent-log.js +13 -5
  175. package/dist/core/recall/recent-log.js.map +1 -1
  176. package/dist/core/recall/related-concepts.d.ts +29 -0
  177. package/dist/core/recall/related-concepts.js +67 -0
  178. package/dist/core/recall/related-concepts.js.map +1 -0
  179. package/dist/core/recall/related-exemplars.d.ts +1 -1
  180. package/dist/core/recall/related-exemplars.js +2 -2
  181. package/dist/core/recall/related-exemplars.js.map +1 -1
  182. package/dist/core/recall/related-facts.d.ts +1 -1
  183. package/dist/core/recall/related-facts.js +3 -3
  184. package/dist/core/recall/related-facts.js.map +1 -1
  185. package/dist/core/recall-facts/fact-query-log.d.ts +8 -4
  186. package/dist/core/recall-facts/fact-query-log.js +16 -8
  187. package/dist/core/recall-facts/fact-query-log.js.map +1 -1
  188. package/dist/core/recall-facts/fact-recall-service.d.ts +2 -2
  189. package/dist/core/recall-facts/fact-recall-service.js +12 -12
  190. package/dist/core/recall-facts/fact-recall-service.js.map +1 -1
  191. package/dist/core/scheduler/scan-once.js +15 -14
  192. package/dist/core/scheduler/scan-once.js.map +1 -1
  193. package/dist/core/scheduler/scheduler.d.ts +3 -0
  194. package/dist/core/scheduler/scheduler.js +15 -11
  195. package/dist/core/scheduler/scheduler.js.map +1 -1
  196. package/dist/core/signals/failure-mode-recall.d.ts +1 -1
  197. package/dist/core/signals/failure-mode-recall.js +2 -2
  198. package/dist/core/signals/failure-mode-recall.js.map +1 -1
  199. package/dist/core/signals/stamp-scope.d.ts +2 -2
  200. package/dist/core/signals/stamp-scope.js +2 -2
  201. package/dist/core/signals/stamp-scope.js.map +1 -1
  202. package/dist/core/sources/source-registry.d.ts +49 -28
  203. package/dist/core/sources/source-registry.js +113 -70
  204. package/dist/core/sources/source-registry.js.map +1 -1
  205. package/dist/core/storage/pg-code-exemplar-store.d.ts +19 -10
  206. package/dist/core/storage/pg-code-exemplar-store.js +58 -27
  207. package/dist/core/storage/pg-code-exemplar-store.js.map +1 -1
  208. package/dist/core/storage/pg-entity-store.d.ts +6 -1
  209. package/dist/core/storage/pg-entity-store.js +33 -18
  210. package/dist/core/storage/pg-entity-store.js.map +1 -1
  211. package/dist/core/storage/pg-fact-ingest.d.ts +1 -1
  212. package/dist/core/storage/pg-fact-ingest.js +10 -7
  213. package/dist/core/storage/pg-fact-ingest.js.map +1 -1
  214. package/dist/core/storage/pg-fact-store.d.ts +29 -17
  215. package/dist/core/storage/pg-fact-store.js +99 -46
  216. package/dist/core/storage/pg-fact-store.js.map +1 -1
  217. package/dist/core/storage/pg-learner-store.d.ts +27 -0
  218. package/dist/core/storage/pg-learner-store.js +174 -0
  219. package/dist/core/storage/pg-learner-store.js.map +1 -0
  220. package/dist/core/storage/pg-session-store.d.ts +24 -20
  221. package/dist/core/storage/pg-session-store.js +162 -90
  222. package/dist/core/storage/pg-session-store.js.map +1 -1
  223. package/dist/core/storage/pg-signal-store.d.ts +10 -5
  224. package/dist/core/storage/pg-signal-store.js +24 -12
  225. package/dist/core/storage/pg-signal-store.js.map +1 -1
  226. package/dist/core/storage/pg-storage.d.ts +4 -0
  227. package/dist/core/storage/pg-storage.js +6 -0
  228. package/dist/core/storage/pg-storage.js.map +1 -1
  229. package/dist/core/storage/pg-workstream-store.d.ts +16 -11
  230. package/dist/core/storage/pg-workstream-store.js +46 -29
  231. package/dist/core/storage/pg-workstream-store.js.map +1 -1
  232. package/dist/core/storage/sqlite-code-exemplar-store.d.ts +24 -10
  233. package/dist/core/storage/sqlite-code-exemplar-store.js +84 -40
  234. package/dist/core/storage/sqlite-code-exemplar-store.js.map +1 -1
  235. package/dist/core/storage/sqlite-entity-store.d.ts +8 -1
  236. package/dist/core/storage/sqlite-entity-store.js +46 -27
  237. package/dist/core/storage/sqlite-entity-store.js.map +1 -1
  238. package/dist/core/storage/sqlite-fact-store.d.ts +45 -19
  239. package/dist/core/storage/sqlite-fact-store.js +172 -64
  240. package/dist/core/storage/sqlite-fact-store.js.map +1 -1
  241. package/dist/core/storage/sqlite-learner-store.d.ts +33 -0
  242. package/dist/core/storage/sqlite-learner-store.js +178 -0
  243. package/dist/core/storage/sqlite-learner-store.js.map +1 -0
  244. package/dist/core/storage/sqlite-outcome-store.d.ts +37 -22
  245. package/dist/core/storage/sqlite-outcome-store.js +90 -37
  246. package/dist/core/storage/sqlite-outcome-store.js.map +1 -1
  247. package/dist/core/storage/sqlite-session-store.d.ts +29 -20
  248. package/dist/core/storage/sqlite-session-store.js +207 -121
  249. package/dist/core/storage/sqlite-session-store.js.map +1 -1
  250. package/dist/core/storage/sqlite-signal-store.d.ts +12 -5
  251. package/dist/core/storage/sqlite-signal-store.js +27 -15
  252. package/dist/core/storage/sqlite-signal-store.js.map +1 -1
  253. package/dist/core/storage/sqlite-storage.d.ts +4 -0
  254. package/dist/core/storage/sqlite-storage.js +10 -2
  255. package/dist/core/storage/sqlite-storage.js.map +1 -1
  256. package/dist/core/storage/sqlite-workstream-store.d.ts +16 -11
  257. package/dist/core/storage/sqlite-workstream-store.js +52 -31
  258. package/dist/core/storage/sqlite-workstream-store.js.map +1 -1
  259. package/dist/core/storage/supersedence-log.d.ts +10 -7
  260. package/dist/core/storage/supersedence-log.js +19 -14
  261. package/dist/core/storage/supersedence-log.js.map +1 -1
  262. package/dist/core/tenancy/default-team.d.ts +6 -0
  263. package/dist/core/tenancy/default-team.js +7 -0
  264. package/dist/core/tenancy/default-team.js.map +1 -0
  265. package/dist/core/tenancy/hosted-mode.d.ts +9 -0
  266. package/dist/core/tenancy/hosted-mode.js +12 -0
  267. package/dist/core/tenancy/hosted-mode.js.map +1 -0
  268. package/dist/core/tenancy/team-auth.d.ts +16 -0
  269. package/dist/core/tenancy/team-auth.js +22 -0
  270. package/dist/core/tenancy/team-auth.js.map +1 -0
  271. package/dist/core/tenancy/team-token-store.d.ts +45 -0
  272. package/dist/core/tenancy/team-token-store.js +52 -0
  273. package/dist/core/tenancy/team-token-store.js.map +1 -0
  274. package/dist/core/tenancy/tenant-clause.d.ts +37 -0
  275. package/dist/core/tenancy/tenant-clause.js +11 -0
  276. package/dist/core/tenancy/tenant-clause.js.map +1 -0
  277. package/dist/core/tenancy/tenant-state-path.d.ts +28 -0
  278. package/dist/core/tenancy/tenant-state-path.js +49 -0
  279. package/dist/core/tenancy/tenant-state-path.js.map +1 -0
  280. package/dist/core/wiki/project.d.ts +17 -0
  281. package/dist/core/wiki/project.js +37 -0
  282. package/dist/core/wiki/project.js.map +1 -0
  283. package/dist/core/wiki/reconcile.d.ts +15 -0
  284. package/dist/core/wiki/reconcile.js +14 -0
  285. package/dist/core/wiki/reconcile.js.map +1 -0
  286. package/dist/core/wiki/render.d.ts +5 -0
  287. package/dist/core/wiki/render.js +84 -0
  288. package/dist/core/wiki/render.js.map +1 -0
  289. package/dist/core/wiki/rollup.d.ts +28 -0
  290. package/dist/core/wiki/rollup.js +91 -0
  291. package/dist/core/wiki/rollup.js.map +1 -0
  292. package/dist/core/wiki/select.d.ts +8 -0
  293. package/dist/core/wiki/select.js +7 -0
  294. package/dist/core/wiki/select.js.map +1 -0
  295. package/dist/core/wiki/slug.d.ts +36 -0
  296. package/dist/core/wiki/slug.js +71 -0
  297. package/dist/core/wiki/slug.js.map +1 -0
  298. package/dist/core/wiki/types.d.ts +37 -0
  299. package/dist/core/wiki/types.js +2 -0
  300. package/dist/core/wiki/types.js.map +1 -0
  301. package/dist/core/work-digest/build-work-digest.d.ts +1 -1
  302. package/dist/core/work-digest/build-work-digest.js +3 -3
  303. package/dist/core/work-digest/build-work-digest.js.map +1 -1
  304. package/dist/core/workstream/bind.d.ts +1 -1
  305. package/dist/core/workstream/bind.js +10 -10
  306. package/dist/core/workstream/bind.js.map +1 -1
  307. package/dist/core/workstream/rollup.d.ts +1 -1
  308. package/dist/core/workstream/rollup.js +6 -6
  309. package/dist/core/workstream/rollup.js.map +1 -1
  310. package/dist/hook/hook-helpers.d.ts +14 -0
  311. package/dist/hook/hook-helpers.js +29 -2
  312. package/dist/hook/hook-helpers.js.map +1 -1
  313. package/dist/hook/pre-compact-hook.js +6 -4
  314. package/dist/hook/pre-compact-hook.js.map +1 -1
  315. package/dist/hook/prompt-recall-hook.d.ts +5 -2
  316. package/dist/hook/prompt-recall-hook.js +28 -17
  317. package/dist/hook/prompt-recall-hook.js.map +1 -1
  318. package/dist/hook/recall-over-http.d.ts +27 -3
  319. package/dist/hook/recall-over-http.js +44 -11
  320. package/dist/hook/recall-over-http.js.map +1 -1
  321. package/dist/hook/session-end-hook.js +7 -5
  322. package/dist/hook/session-end-hook.js.map +1 -1
  323. package/dist/hook/session-start-hook.d.ts +14 -1
  324. package/dist/hook/session-start-hook.js +55 -15
  325. package/dist/hook/session-start-hook.js.map +1 -1
  326. package/dist/hook/stop-hook.js +9 -7
  327. package/dist/hook/stop-hook.js.map +1 -1
  328. package/dist/http/app.d.ts +41 -3
  329. package/dist/http/app.js +355 -96
  330. package/dist/http/app.js.map +1 -1
  331. package/dist/install/claude-code.d.ts +17 -0
  332. package/dist/install/claude-code.js +44 -5
  333. package/dist/install/claude-code.js.map +1 -1
  334. package/dist/install/codex.d.ts +23 -2
  335. package/dist/install/codex.js +111 -14
  336. package/dist/install/codex.js.map +1 -1
  337. package/dist/install/cursor.d.ts +2 -2
  338. package/dist/install/cursor.js +7 -7
  339. package/dist/install/cursor.js.map +1 -1
  340. package/dist/install/hermes.d.ts +8 -0
  341. package/dist/install/hermes.js +28 -5
  342. package/dist/install/hermes.js.map +1 -1
  343. package/dist/install/local-source.d.ts +20 -0
  344. package/dist/install/local-source.js +28 -0
  345. package/dist/install/local-source.js.map +1 -0
  346. package/dist/install/mcp-block.d.ts +77 -0
  347. package/dist/install/mcp-block.js +99 -0
  348. package/dist/install/mcp-block.js.map +1 -0
  349. package/dist/install/pi.d.ts +26 -0
  350. package/dist/install/pi.js +43 -0
  351. package/dist/install/pi.js.map +1 -1
  352. package/dist/install/setup.js +6 -0
  353. package/dist/install/setup.js.map +1 -1
  354. package/dist/install/windsurf.d.ts +2 -2
  355. package/dist/install/windsurf.js +7 -7
  356. package/dist/install/windsurf.js.map +1 -1
  357. package/dist/llm/classifier-box.d.ts +5 -0
  358. package/dist/llm/classifier-box.js +5 -0
  359. package/dist/llm/classifier-box.js.map +1 -1
  360. package/dist/mcp/server.d.ts +73 -16
  361. package/dist/mcp/server.js +306 -83
  362. package/dist/mcp/server.js.map +1 -1
  363. package/dist/ports/clock.d.ts +12 -0
  364. package/dist/ports/clock.js +10 -0
  365. package/dist/ports/clock.js.map +1 -0
  366. package/dist/ports/code-exemplar-store.d.ts +13 -13
  367. package/dist/ports/entity-store.d.ts +5 -1
  368. package/dist/ports/fact-store.d.ts +0 -0
  369. package/dist/ports/fact-store.js +0 -0
  370. package/dist/ports/fact-store.js.map +1 -1
  371. package/dist/ports/learner-store.d.ts +80 -0
  372. package/dist/ports/learner-store.js +19 -0
  373. package/dist/ports/learner-store.js.map +1 -0
  374. package/dist/ports/outcome.d.ts +3 -3
  375. package/dist/ports/session-store.d.ts +20 -14
  376. package/dist/ports/session-store.js +5 -0
  377. package/dist/ports/session-store.js.map +1 -1
  378. package/dist/ports/signal-store.d.ts +12 -8
  379. package/dist/ports/spawn-child.d.ts +14 -0
  380. package/dist/ports/spawn-child.js +10 -0
  381. package/dist/ports/spawn-child.js.map +1 -0
  382. package/dist/ports/storage.d.ts +6 -0
  383. package/dist/ports/transcript-adapter.d.ts +11 -0
  384. package/dist/ports/wiki-writer.d.ts +19 -0
  385. package/dist/ports/wiki-writer.js +12 -0
  386. package/dist/ports/wiki-writer.js.map +1 -0
  387. package/dist/ports/workstream-store.d.ts +19 -12
  388. package/dist/shared/types.d.ts +22 -0
  389. package/dist/shared/types.js.map +1 -1
  390. package/dist/shared/version.d.ts +9 -0
  391. package/dist/shared/version.js +11 -0
  392. package/dist/shared/version.js.map +1 -0
  393. package/dist/ui/assets/index-D6F3xAis.js +11 -0
  394. package/dist/ui/index.html +1 -1
  395. package/migrations/033_actions_kind_check.sql +58 -0
  396. package/migrations/034_tenancy_teams_and_stamps.sql +40 -0
  397. package/migrations/035_tenancy_entity_rekey.sql +75 -0
  398. package/migrations/036_tenant_name_uniqueness.sql +59 -0
  399. package/migrations/037_sessions_covering_index.sql +25 -0
  400. package/migrations/038_sources_muse.sql +33 -0
  401. package/migrations/039_learner_evidence.sql +51 -0
  402. package/migrations/pg/033_actions_kind_check.sql +17 -0
  403. package/migrations/pg/034_tenancy.sql +77 -0
  404. package/migrations/pg/035_tenant_name_uniqueness.sql +15 -0
  405. package/migrations/pg/036_sources_muse.sql +12 -0
  406. package/migrations/pg/037_learner_evidence.sql +55 -0
  407. package/nlm/index.js +246 -55
  408. package/package.json +10 -5
  409. package/plugin/.codex-plugin/plugin.json +2 -2
  410. package/plugin/scripts/prompt-recall-hook.mjs +244 -55
  411. package/plugin/scripts/session-start-hook.mjs +273 -53
  412. package/plugin/scripts/stop-hook.mjs +195 -37
  413. package/dist/ui/assets/index-CSaBtgXD.js +0 -11
package/dist/cli/nlm.js CHANGED
@@ -22,12 +22,13 @@
22
22
  * nlm disconnect claude-code — remove MCP block from ~/.mcp.json
23
23
  * nlm disconnect codex — remove Codex plugin
24
24
  * nlm digest — print a daily-activity digest (or --telegram to post)
25
+ * nlm reconcile-facts — retire live facts contradicting an authorities file
25
26
  * nlm init - print (or write) the agent recall contract snippet
26
27
  */
27
28
  import { fileURLToPath, pathToFileURL } from "node:url";
28
29
  import { dirname, resolve, join } from "node:path";
29
30
  import { homedir } from "node:os";
30
- import { mkdirSync, readFileSync, writeFileSync, existsSync, rmSync, copyFileSync, realpathSync, appendFileSync } from "node:fs";
31
+ import { mkdirSync, readFileSync, writeFileSync, existsSync, rmSync, copyFileSync, realpathSync, appendFileSync, renameSync } from "node:fs";
31
32
  import { execFileSync, spawn, spawnSync } from "node:child_process";
32
33
  import { Command } from "commander";
33
34
  import pkg from "../../package.json" with { type: "json" };
@@ -39,6 +40,7 @@ import { ProviderRegistry } from "../core/providers/provider-registry.js";
39
40
  import { SqliteStorage } from "../core/storage/sqlite-storage.js";
40
41
  import { PgStorage } from "../core/storage/pg-storage.js";
41
42
  import { applyPendingRestore, stageRestore } from "../core/storage/db-restore.js";
43
+ import { applyRetirements, findContradictions, formatReport, loadAuthorities, } from "../core/facts/reconcile-facts.js";
42
44
  import { listBackupDates, resolveBackup, runRollingBackup } from "../core/storage/backup-rotation.js";
43
45
  import { createApp } from "../http/app.js";
44
46
  import { createMcpServer, listMergeSuggestionsHandler, mergeWorkstreamsHandler, rebindSessionHandler, recallWorkstreamHandler, renameWorkstreamHandler, retireWorkstreamHandler } from "../mcp/server.js";
@@ -50,32 +52,47 @@ import { resolveEmbedderInfo } from "../llm/embedder-info.js";
50
52
  import { autoloadEnv } from "../llm/env-autoload.js";
51
53
  import { buildEmbedder as _buildEmbedder } from "../llm/build-embedder.js";
52
54
  import { addHook, buildHookCommand, removeHook } from "../core/hook/claude-settings.js";
53
- import { codexBinaryAvailable, connectCodex, disconnectCodex, repairCodex, pluginScriptsDir, } from "../install/codex.js";
54
- import { connectClaudeCode, disconnectClaudeCode, installClaudeCodeHooks, mcpConfigPath } from "../install/claude-code.js";
55
+ import { codexBinaryAvailable, codexHasEffectiveMcpServer, connectCodex, disconnectCodex, repairCodex, pluginScriptsDir, } from "../install/codex.js";
56
+ import { connectClaudeCode, disconnectClaudeCode, installClaudeCodeHooks, mcpConfigPath, nvmPinnedHookWarning } from "../install/claude-code.js";
55
57
  import { evaluateInstallHealth, evaluateModelHealth, evaluateRecallSmoke } from "../install/health.js";
56
58
  import { codexConfigPath } from "../install/codex.js";
57
59
  import { hardenNlmDirPermissions } from "../install/nlm-dir-perms.js";
58
60
  import { embeddingModelPresent, ensureMcpToken, ollamaModelPresent } from "../install/ollama.js";
59
61
  import { connectCursor, disconnectCursor } from "../install/cursor.js";
62
+ import { connectLocalSource } from "../install/local-source.js";
63
+ import { defaultDbPath as openCodeDefaultDbPath } from "../core/adapters/opencode.js";
64
+ import { defaultSessionsPath as museDefaultSessionsPath } from "../core/adapters/muse.js";
60
65
  import { describeRemove, describeUpsert, installCursorRules, installOpencodeRules, installWindsurfRules, uninstallCursorRules, uninstallOpencodeRules, uninstallWindsurfRules, } from "../install/rules-install.js";
61
66
  import { runSupersedeCommand } from "./supersede.js";
62
67
  import { runInitCommand } from "./init.js";
68
+ import { DEFAULT_TEAM_ID } from "../core/tenancy/default-team.js";
69
+ import { hashTeamToken } from "../core/tenancy/team-auth.js";
63
70
  import { runScopeBackfill, formatBackfillResult } from "./scope-backfill.js";
64
71
  import { runScopeCoverage, formatCoverageResult } from "./scope-coverage.js";
65
72
  import { loadAliasMap } from "../core/scope/alias-map.js";
66
73
  import { getUpdateStatus } from "../core/update-check/check.js";
74
+ import { checkDriftAndAlert, checkEmbedderAndAlert } from "../core/alerts/check-and-alert.js";
75
+ import { fireAlert } from "../core/alerts/fire-alert.js";
76
+ import { buildJobAlertEvent } from "../core/alerts/job-alert.js";
77
+ import { buildWikiDriftEvent, buildWikiFailureEvent } from "../core/alerts/wiki-alert.js";
78
+ import { projectWiki } from "../core/wiki/project.js";
79
+ import { FsWikiWriter } from "../core/adapters/fs-wiki-writer.js";
80
+ import { JobSupervisor } from "../core/jobs/job-supervisor.js";
81
+ import { realClock, createReprocessSpawnChild } from "./reprocess-job-adapters.js";
67
82
  import { connectHermes, disconnectHermes, hermesConfigPath } from "../install/hermes.js";
68
83
  import { connectHermesAgent, disconnectHermesAgent, hermesAgentPluginDir } from "../install/hermes-agent.js";
69
84
  import { connectWindsurf, disconnectWindsurf } from "../install/windsurf.js";
70
- import { connectPi, disconnectPi, piSettingsPath } from "../install/pi.js";
85
+ import { connectPi, connectPiMcp, disconnectPi, piSettingsPath } from "../install/pi.js";
71
86
  import { runSetup } from "../install/setup.js";
72
87
  import { runParity } from "./classify-parity.js";
73
88
  import { reembedCorpus } from "../core/embedding/embed-backfill.js";
74
89
  import { reembedCorpusPg } from "../core/embedding/pg-embed-backfill.js";
75
90
  import { backfillExemplarEmbeddings } from "../core/exemplars/embed-backfill.js";
91
+ import { backfillFactEmbeddings } from "../core/facts/embed-backfill.js";
76
92
  import { warmCodeEmbedder } from "../core/exemplars/warm-embedder.js";
77
- import { markWarm } from "../core/health/warmup-state.js";
93
+ import { markWarm, markWarmFailure } from "../core/health/warmup-state.js";
78
94
  import { setLaneHealth } from "../core/health/embedding-lane-state.js";
95
+ import { retryUntilWarm } from "../core/health/warmup-retry.js";
79
96
  import { setCorpusSnapshot, corpusSnapshot } from "../core/health/corpus-state.js";
80
97
  import { computeCorpusStats, sqliteCorpusStatsDeps, parseCorpusThresholds, thresholdState } from "../core/metrics/corpus-stats.js";
81
98
  import { computeReDerivationRate, sqliteReDerivationDeps } from "../core/metrics/re-derivation.js";
@@ -118,6 +135,10 @@ function port() {
118
135
  return DEFAULT_PORT;
119
136
  return n;
120
137
  }
138
+ function positiveIntEnv(name, fallback) {
139
+ const parsed = Number.parseInt(process.env[name] ?? "", 10);
140
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
141
+ }
121
142
  function ollamaUrl() {
122
143
  return process.env["NLM_OLLAMA_URL"] ?? "http://localhost:11434";
123
144
  }
@@ -150,6 +171,28 @@ export function shouldAppendTrend(lastLineTs, now) {
150
171
  return true;
151
172
  return now - parsed >= TREND_INTERVAL_MS;
152
173
  }
174
+ /**
175
+ * Recomputes `computeReDerivationRate` and overwrites the pairs cache file
176
+ * (#405) that `@core/storage/sqlite-outcome-store.js` reads instead of
177
+ * recomputing the same O(n^2) scan inline. Called every 24h corpus-monitor
178
+ * cycle (not gated by `shouldAppendTrend`, which only paces the separate
179
+ * append-only trend log) so the cache stays ~24h fresh while the monitor
180
+ * runs. Exported for direct testing and smoke runs against a corpus copy.
181
+ * `tenantId` scopes both the session scan (sqliteReDerivationDeps) and the
182
+ * cache file this write targets — two tenants must never compute or read
183
+ * each other's pairs.
184
+ */
185
+ export async function persistReDerivationPairs(rawDb, tenantId, pairsPath, windowDays) {
186
+ const deps = sqliteReDerivationDeps(rawDb, tenantId);
187
+ const report = await computeReDerivationRate(deps, windowDays);
188
+ // Write-temp-then-rename: the outcome adapters read this file on every
189
+ // get_session, so a crash mid-write must never leave truncated JSON at
190
+ // the final path. rename is atomic within the same directory.
191
+ const tmpPath = `${pairsPath}.tmp-${process.pid}-${Date.now()}`;
192
+ writeFileSync(tmpPath, JSON.stringify(report.pairs), "utf8");
193
+ renameSync(tmpPath, pairsPath);
194
+ return report;
195
+ }
153
196
  function loadTopicProvider() {
154
197
  try {
155
198
  const raw = readFileSync(join(homedir(), ".nlm", "work-topics.json"), "utf8");
@@ -237,14 +280,14 @@ async function reconcileEmbeddingLanes(storage, embedder, codeEmbedder) {
237
280
  // probe failure; code lane stays unknown
238
281
  }
239
282
  }
240
- async function buildAdapters(sources) {
283
+ async function buildAdapters(sources, tenantId) {
241
284
  // Sources table is the source of truth. Each enabled row maps to one
242
285
  // adapter via adapterFromSource(). Detection still gates registration —
243
286
  // a row pointing at a missing dir won't poll. NLM_ADAPTERS keeps working
244
287
  // as a name-based filter for forcing a subset during dev.
245
288
  const explicit = process.env["NLM_ADAPTERS"];
246
289
  const allowed = explicit ? new Set(explicit.split(",").map((s) => s.trim())) : null;
247
- const rows = await sources.list();
290
+ const rows = await sources.list(tenantId);
248
291
  const out = [];
249
292
  for (const row of rows) {
250
293
  if (!row.enabled)
@@ -290,13 +333,13 @@ async function buildStack() {
290
333
  const signals = storage.signals;
291
334
  const scope = installScope();
292
335
  const sources = storage.sources;
293
- await sources.seedDefaults();
336
+ await sources.seedDefaults(DEFAULT_TEAM_ID);
294
337
  const providers = storage.providers;
295
338
  // Provider seeding is SQLite-only: it bridges from the local DEEPSEEK_API_KEY
296
339
  // env, which is wrong for a hosted multi-tenant PG. PgProviderRegistry has no
297
340
  // seedDefaults (not on ProviderRegistryPort), so this narrows to the SQLite case.
298
341
  if (providers instanceof ProviderRegistry)
299
- await providers.seedDefaults();
342
+ await providers.seedDefaults(DEFAULT_TEAM_ID);
300
343
  // Recall only uses embed(). Default embedder is local Ollama; NLM_EMBED_*
301
344
  // can point it at any OpenAI-compatible endpoint. Classifier is wired
302
345
  // separately for Phase D ingest.
@@ -307,11 +350,12 @@ async function buildStack() {
307
350
  store,
308
351
  llm: embedder,
309
352
  factStore: facts,
353
+ learnerDeps: { learners: storage.learners, sessions: store },
310
354
  exemplarStore: storage.exemplars,
311
355
  codeEmbedder: buildCodeEmbedder(),
312
356
  installScope: scope,
313
357
  resolveWorkstreamMembers: async (idOrLabel) => {
314
- const all = await wsStore.listAll();
358
+ const all = await wsStore.listAll(DEFAULT_TEAM_ID);
315
359
  const byId = new Map(all.map((w) => [w.id, { id: w.id, mergedInto: w.mergedInto }]));
316
360
  const target = all.find((w) => w.id === idOrLabel)
317
361
  ?? all.find((w) => normalizeLabel(w.label) === normalizeLabel(idOrLabel));
@@ -364,11 +408,32 @@ program
364
408
  // Generate NLM_MCP_TOKEN if missing so /api/* gets Bearer-protected for
365
409
  // non-browser callers. Idempotent: re-reads persisted token first.
366
410
  autoloadEnv();
367
- ensureMcpToken();
411
+ const mcpToken = ensureMcpToken();
368
412
  const { storage, store, facts, signals, scope, sources, providers, recall, factRecall, embedder, classifier } = await buildStack();
413
+ // Local-mode continuity (program spec §3 M3): idempotently seed the
414
+ // existing NLM_MCP_TOKEN's hash for the default team, so an install that
415
+ // already has a token keeps authenticating through the same
416
+ // resolveTeamByToken path as any future per-team token. No-op after the
417
+ // first boot (ensureActive is insert-if-absent).
418
+ await storage.teamTokens.ensureActive(hashTeamToken(mcpToken), DEFAULT_TEAM_ID);
369
419
  const { existsSync } = await import("node:fs");
370
420
  const hasMcpToken = Boolean(process.env["NLM_MCP_TOKEN"]);
371
421
  const codeEmb = buildCodeEmbedder();
422
+ // Job supervision: owns the daemon's own reprocess child, restarting it
423
+ // on a non-clean exit while work remains and reporting stalls — see
424
+ // core/jobs/job-supervisor.ts for why this exists (a detached
425
+ // `nlm reprocess` died on OOM and sat unobserved).
426
+ // onEvent -> fireAlert is fire-and-forget on purpose: fireAlert already
427
+ // never throws (best-effort delivery, see fire-alert.ts) and JobSupervisor
428
+ // itself catches/logs anything an onEvent callback throws, so this can't
429
+ // crash the daemon either way.
430
+ const jobSupervisor = new JobSupervisor({
431
+ spawnChild: createReprocessSpawnChild({ execPath: process.execPath, nlmScriptPath: __filename }),
432
+ clock: realClock,
433
+ onEvent: (event) => {
434
+ void fireAlert(buildJobAlertEvent(event));
435
+ },
436
+ });
372
437
  const app = createApp({
373
438
  recall,
374
439
  store,
@@ -378,7 +443,9 @@ program
378
443
  dbPath: dbPath(),
379
444
  classifier,
380
445
  sources,
446
+ jobSupervisor,
381
447
  providers,
448
+ teamTokens: storage.teamTokens,
382
449
  ingest: {
383
450
  classifier,
384
451
  embedder,
@@ -387,6 +454,7 @@ program
387
454
  classifierDescriptor: { provider: classifier.provider, model: classifier.model },
388
455
  },
389
456
  signalStore: signals,
457
+ learnerRecall: { learners: storage.learners, sessions: store },
390
458
  installScope: scope,
391
459
  // Signal scope inheritance (NLM_SCOPE_STAMP): a signal with only a
392
460
  // session_id inherits that session's stamped scope at ingest.
@@ -417,19 +485,29 @@ program
417
485
  sessionScopeReader: store,
418
486
  workDigest: { store, topicProvider: loadTopicProvider(), workstreams: storage.workstreams, ...workDigestEnv() },
419
487
  workstreams: { store: storage.workstreams, sessions: store, facts: facts, exemplars: storage.exemplars },
488
+ learners: { store: storage.learners, workstreams: storage.workstreams, sessions: store },
420
489
  },
421
490
  }
422
491
  : {}),
423
492
  });
424
493
  warmCodeEmbedder(codeEmb);
425
494
  void reconcileEmbeddingLanes(storage, embedder, codeEmb).catch(() => { });
426
- void embedder
427
- .embed("warmup init", "query")
428
- .then(() => markWarm("textEmbedder"))
429
- .catch(() => { });
495
+ // Retried, not one-shot. A single attempt with a swallowed catch latched
496
+ // this stage cold for the whole uptime whenever the backend was not yet
497
+ // servable, which is the normal case when the embedder host is started by
498
+ // hand after a reboot.
499
+ void retryUntilWarm({
500
+ attempt: () => embedder.embed("warmup init", "query"),
501
+ onSuccess: () => {
502
+ markWarm("textEmbedder");
503
+ setLaneHealth("prose", "ok");
504
+ },
505
+ onFailure: (reason, attempt) => markWarmFailure("textEmbedder", reason, attempt),
506
+ sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms).unref?.()),
507
+ });
430
508
  setImmediate(() => {
431
509
  void recall
432
- .search({ query: "warmup init", mode: "keyword", limit: 1 })
510
+ .search(DEFAULT_TEAM_ID, { query: "warmup init", mode: "keyword", limit: 1 })
433
511
  .then(() => markWarm("fts5"))
434
512
  .catch(() => { });
435
513
  });
@@ -492,17 +570,36 @@ program
492
570
  const SIGNAL_PRUNE_INTERVAL_MS = 6 * 60 * 60_000;
493
571
  const signalPruneTimer = setInterval(() => {
494
572
  const cutoff = new Date(Date.now() - SIGNAL_RETENTION_DAYS * 86_400_000).toISOString();
495
- void signals.pruneOlderThan(cutoff).catch(() => { });
573
+ void signals.pruneOlderThan(DEFAULT_TEAM_ID, cutoff).catch(() => { });
496
574
  }, SIGNAL_PRUNE_INTERVAL_MS);
497
575
  signalPruneTimer.unref();
498
- // Corpus monitor: 24h stats job + weekly re-derivation trend. SQLite only.
499
- // First run is delayed ~60s so warmup finishes first; then repeats every 24h.
576
+ // NLM_ALERT_WEBHOOK self-reporting: version-drift + embedder-cold
577
+ // transitions POST to an operator-owned webhook. No-op (zero network
578
+ // calls) when the env var is unset — see core/alerts/fire-alert.ts.
579
+ const ALERT_DRIFT_CHECK_INTERVAL_MS = 60 * 60_000;
580
+ const alertDriftTimer = setInterval(() => {
581
+ void checkDriftAndAlert({ currentVersion: pkg.version }).catch(() => {
582
+ // Best-effort self-reporting — a failed check never blocks the daemon.
583
+ });
584
+ }, ALERT_DRIFT_CHECK_INTERVAL_MS);
585
+ alertDriftTimer.unref();
586
+ const ALERT_EMBEDDER_CHECK_INTERVAL_MS = 5 * 60_000;
587
+ const alertEmbedderTimer = setInterval(() => {
588
+ void checkEmbedderAndAlert().catch(() => {
589
+ // Best-effort self-reporting — a failed check never blocks the daemon.
590
+ });
591
+ }, ALERT_EMBEDDER_CHECK_INTERVAL_MS);
592
+ alertEmbedderTimer.unref();
593
+ // Corpus monitor: 24h stats job + pairs-cache refresh + weekly trend log.
594
+ // SQLite only. First run is delayed ~60s so warmup finishes first; then
595
+ // repeats every 24h.
500
596
  if (!(storage instanceof PgStorage)) {
501
597
  const CORPUS_MONITOR_INTERVAL_MS = 24 * 60 * 60 * 1000;
502
598
  const CORPUS_MONITOR_INITIAL_DELAY_MS = 60 * 1000;
503
599
  const nlmDataDir = join(homedir(), ".nlm");
504
600
  const corpusStatsPath = join(nlmDataDir, "corpus-stats.jsonl");
505
601
  const rederivTrendPath = join(nlmDataDir, "re-derivation-trend.jsonl");
602
+ const rederivPairsPath = join(nlmDataDir, "re-derivation-pairs.json");
506
603
  const runCorpusMonitor = async () => {
507
604
  try {
508
605
  const sqliteStorage = storage;
@@ -536,9 +633,14 @@ program
536
633
  catch {
537
634
  // file absent or unreadable; treat as no prior entry
538
635
  }
636
+ // Pairs cache is refreshed every 24h cycle regardless of the trend
637
+ // gate below - the trend JSONL is an append-only history and stays
638
+ // on its existing weekly cadence, but the pairs cache is a single
639
+ // overwritten file so refreshing it daily costs nothing extra and
640
+ // keeps sqlite-outcome-store.ts's reads within a ~24h staleness
641
+ // bound (see #405).
642
+ const report = await persistReDerivationPairs(rawDb, DEFAULT_TEAM_ID, rederivPairsPath, 42);
539
643
  if (shouldAppendTrend(lastTrendTs, now)) {
540
- const rdDeps = sqliteReDerivationDeps(rawDb);
541
- const report = await computeReDerivationRate(rdDeps, 42);
542
644
  appendFileSync(rederivTrendPath, JSON.stringify({ ts, windowDays: 42, rate: report.rate, pairCount: report.pairs.length, eligible: report.eligible }) + "\n", "utf8");
543
645
  }
544
646
  }
@@ -549,6 +651,78 @@ program
549
651
  const corpusMonitorTimer = setInterval(() => { void runCorpusMonitor(); }, CORPUS_MONITOR_INTERVAL_MS);
550
652
  corpusMonitorTimer.unref();
551
653
  setTimeout(() => { void runCorpusMonitor(); }, CORPUS_MONITOR_INITIAL_DELAY_MS).unref();
654
+ // Wiki projection: regenerate the generated markdown tree. SQLite only,
655
+ // same as the corpus monitor. Idempotent, so a run with no corpus change
656
+ // writes zero bytes. Failure leaves the previous tree untouched and
657
+ // self-reports on the operator's webhook if one is configured. Opt-in:
658
+ // nlm-memory is a published public package, so the projection only
659
+ // registers (timer, directory, log line) when an operator explicitly
660
+ // sets NLM_WIKI_DIR — an upgrade must never start writing ~500 markdown
661
+ // files under a stranger's home directory on its own.
662
+ const wikiDir = process.env["NLM_WIKI_DIR"];
663
+ if (wikiDir) {
664
+ const parsedWikiHours = Number.parseInt(process.env["NLM_WIKI_INTERVAL_HOURS"] ?? "6", 10);
665
+ const WIKI_INTERVAL_MS = (Number.isFinite(parsedWikiHours) && parsedWikiHours > 0 ? parsedWikiHours : 6) * 60 * 60_000;
666
+ const WIKI_INITIAL_DELAY_MS = 90 * 1000;
667
+ const wikiConfig = {
668
+ minFacts: positiveIntEnv("NLM_WIKI_MIN_FACTS", 3),
669
+ minSessions: positiveIntEnv("NLM_WIKI_MIN_SESSIONS", 3),
670
+ linkBase: process.env["NLM_WIKI_LINK_BASE"] ?? `http://127.0.0.1:${p}`,
671
+ };
672
+ const wikiTarget = join(wikiDir, "Memory");
673
+ const runWikiProjection = async () => {
674
+ try {
675
+ const writer = new FsWikiWriter(wikiTarget);
676
+ const today = new Date().toISOString().slice(0, 10);
677
+ const result = await projectWiki({ facts, writer }, DEFAULT_TEAM_ID, wikiConfig, today);
678
+ console.error(`[wiki] ${result.written} written, ${result.unchanged} unchanged, ${result.removed} removed`);
679
+ const drift = buildWikiDriftEvent(result);
680
+ if (drift)
681
+ await fireAlert(drift);
682
+ }
683
+ catch (e) {
684
+ console.error(`[wiki] error: ${e instanceof Error ? e.message : String(e)}`);
685
+ await fireAlert(buildWikiFailureEvent(e)).catch(() => {
686
+ // Self-reporting is best-effort; a webhook outage never escalates.
687
+ });
688
+ }
689
+ };
690
+ console.error(` wiki: enabled, projecting to ${wikiTarget}`);
691
+ const wikiTimer = setInterval(() => { void runWikiProjection(); }, WIKI_INTERVAL_MS);
692
+ wikiTimer.unref();
693
+ setTimeout(() => { void runWikiProjection(); }, WIKI_INITIAL_DELAY_MS).unref();
694
+ }
695
+ }
696
+ // Exemplar embed-backfill: repairs code_exemplars rows whose vector was
697
+ // dropped by a cold CodeRankEmbed on first capture (see
698
+ // core/exemplars/embed-backfill.ts). SQLite only, same constraint as the
699
+ // corpus monitor above — discovery reads code_exemplars_vec directly via
700
+ // dbPath(). Daily cadence: the drop is a rare cold-embedder race, not a
701
+ // steady leak, so this just keeps recall_code from silently degrading
702
+ // between operator-run `nlm embed-backfill --exemplars` passes.
703
+ if (!(storage instanceof PgStorage) && process.env["NLM_CODE_EXEMPLARS_ENABLED"] === "1") {
704
+ const EXEMPLAR_BACKFILL_INTERVAL_MS = 24 * 60 * 60 * 1000;
705
+ const EXEMPLAR_BACKFILL_INITIAL_DELAY_MS = 120 * 1000;
706
+ const runExemplarBackfill = async () => {
707
+ try {
708
+ const report = await backfillExemplarEmbeddings({
709
+ tenantId: DEFAULT_TEAM_ID,
710
+ dbPath: dbPath(),
711
+ embedder: buildCodeEmbedder(),
712
+ store: storage.exemplars,
713
+ });
714
+ if (report.succeeded > 0 || report.failed > 0) {
715
+ console.error(`[exemplar-backfill] ${report.succeeded}/${report.total} repaired (${report.failed} failed)`);
716
+ }
717
+ }
718
+ catch (e) {
719
+ console.error(`[exemplar-backfill] error: ${e instanceof Error ? e.message : String(e)}`);
720
+ }
721
+ };
722
+ const exemplarBackfillTimer = setInterval(() => { void runExemplarBackfill(); }, EXEMPLAR_BACKFILL_INTERVAL_MS);
723
+ exemplarBackfillTimer.unref();
724
+ setTimeout(() => { void runExemplarBackfill(); }, EXEMPLAR_BACKFILL_INITIAL_DELAY_MS).unref();
725
+ console.error(" exemplar-backfill: enabled, repair sweep every 24h");
552
726
  }
553
727
  // Memo sweep runs independently of the transcript scheduler — it's the
554
728
  // backstop for SessionEnd hook unreliability (crashes, kill -9, IDE
@@ -558,7 +732,7 @@ program
558
732
  memoSweep.start();
559
733
  console.error(" memo sweep: dormant cleanup every 5m (threshold 24h)");
560
734
  if (opts.scheduler !== false) {
561
- const adapters = await buildAdapters(sources);
735
+ const adapters = await buildAdapters(sources, DEFAULT_TEAM_ID);
562
736
  if (adapters.length === 0) {
563
737
  console.error(" scheduler: no adapters detected (set NLM_ADAPTERS to force-enable)");
564
738
  }
@@ -584,6 +758,10 @@ program
584
758
  clearInterval(checkpointTimer);
585
759
  scheduler.stop();
586
760
  memoSweep.stop();
761
+ // Reaps an active reprocess child instead of leaving it orphaned
762
+ // when the daemon exits. stop() is idempotent and a no-op when no
763
+ // run is active.
764
+ jobSupervisor.stop();
587
765
  await storage.close();
588
766
  process.exit(0);
589
767
  };
@@ -619,7 +797,7 @@ program
619
797
  .action(async (query, opts) => {
620
798
  const { storage, recall } = await buildStack();
621
799
  try {
622
- const result = await recall.search({
800
+ const result = await recall.search(DEFAULT_TEAM_ID, {
623
801
  query,
624
802
  mode: opts.mode,
625
803
  limit: opts.limit,
@@ -653,7 +831,7 @@ program
653
831
  .action(async (idOrLabel) => {
654
832
  const { storage, store } = await buildStack();
655
833
  try {
656
- const r = await recallWorkstreamHandler({ recall: {}, store, workstreams: { store: storage.workstreams, sessions: store, facts: storage.facts, exemplars: storage.exemplars } }, { idOrLabel });
834
+ const r = await recallWorkstreamHandler({ recall: {}, store, workstreams: { store: storage.workstreams, sessions: store, facts: storage.facts, exemplars: storage.exemplars } }, DEFAULT_TEAM_ID, { idOrLabel });
657
835
  process.stdout.write(r.content[0].text + "\n");
658
836
  }
659
837
  finally {
@@ -668,7 +846,7 @@ program
668
846
  .action(async (sessionId, workstream) => {
669
847
  const { storage, store } = await buildStack();
670
848
  try {
671
- const r = await rebindSessionHandler({ recall: {}, store, workstreams: { store: storage.workstreams, sessions: store, facts: storage.facts, exemplars: storage.exemplars } }, { sessionId, workstream });
849
+ const r = await rebindSessionHandler({ recall: {}, store, workstreams: { store: storage.workstreams, sessions: store, facts: storage.facts, exemplars: storage.exemplars } }, DEFAULT_TEAM_ID, { sessionId, workstream });
672
850
  process.stdout.write(r.content[0].text + "\n");
673
851
  }
674
852
  finally {
@@ -683,7 +861,7 @@ program
683
861
  .action(async (from, into) => {
684
862
  const { storage, store } = await buildStack();
685
863
  try {
686
- const r = await mergeWorkstreamsHandler({ recall: {}, store, workstreams: { store: storage.workstreams, sessions: store, facts: storage.facts, exemplars: storage.exemplars } }, { from, into });
864
+ const r = await mergeWorkstreamsHandler({ recall: {}, store, workstreams: { store: storage.workstreams, sessions: store, facts: storage.facts, exemplars: storage.exemplars } }, DEFAULT_TEAM_ID, { from, into });
687
865
  process.stdout.write(r.content[0].text + "\n");
688
866
  }
689
867
  finally {
@@ -698,7 +876,7 @@ program
698
876
  .action(async (idOrLabel, label) => {
699
877
  const { storage, store } = await buildStack();
700
878
  try {
701
- const r = await renameWorkstreamHandler({ recall: {}, store, workstreams: { store: storage.workstreams, sessions: store, facts: storage.facts, exemplars: storage.exemplars } }, { idOrLabel, label });
879
+ const r = await renameWorkstreamHandler({ recall: {}, store, workstreams: { store: storage.workstreams, sessions: store, facts: storage.facts, exemplars: storage.exemplars } }, DEFAULT_TEAM_ID, { idOrLabel, label });
702
880
  process.stdout.write(r.content[0].text + "\n");
703
881
  }
704
882
  finally {
@@ -712,7 +890,7 @@ program
712
890
  .action(async (idOrLabel) => {
713
891
  const { storage, store } = await buildStack();
714
892
  try {
715
- const r = await retireWorkstreamHandler({ recall: {}, store, workstreams: { store: storage.workstreams, sessions: store, facts: storage.facts, exemplars: storage.exemplars } }, { idOrLabel });
893
+ const r = await retireWorkstreamHandler({ recall: {}, store, workstreams: { store: storage.workstreams, sessions: store, facts: storage.facts, exemplars: storage.exemplars } }, DEFAULT_TEAM_ID, { idOrLabel });
716
894
  process.stdout.write(r.content[0].text + "\n");
717
895
  }
718
896
  finally {
@@ -732,7 +910,7 @@ program
732
910
  }
733
911
  const { storage, store } = await buildStack();
734
912
  try {
735
- const r = await listMergeSuggestionsHandler({ recall: {}, store, workstreams: { store: storage.workstreams, sessions: store, facts: storage.facts, exemplars: storage.exemplars } }, { minScore });
913
+ const r = await listMergeSuggestionsHandler({ recall: {}, store, workstreams: { store: storage.workstreams, sessions: store, facts: storage.facts, exemplars: storage.exemplars } }, DEFAULT_TEAM_ID, { minScore });
736
914
  process.stdout.write(r.content[0].text + "\n");
737
915
  }
738
916
  finally {
@@ -777,7 +955,7 @@ program
777
955
  const { runEval } = await import("../core/eval/run-eval.js");
778
956
  const { storage, recall } = await buildStack();
779
957
  try {
780
- const report = await runEval({ recall }, queries, { mode: opts.mode, k: 5 });
958
+ const report = await runEval({ recall: { search: (q) => recall.search(DEFAULT_TEAM_ID, q) } }, queries, { mode: opts.mode, k: 5 });
781
959
  if (opts.json) {
782
960
  process.stdout.write(JSON.stringify(report, null, 2) + "\n");
783
961
  return;
@@ -808,7 +986,7 @@ program
808
986
  const { installScope: getScope } = await import("../core/signals/install-scope.js");
809
987
  try {
810
988
  const embedder = buildCodeEmbedder();
811
- const result = await recallCode({
989
+ const result = await recallCode(DEFAULT_TEAM_ID, {
812
990
  query,
813
991
  installScope: getScope(),
814
992
  ...(opts.repo ? { repo: opts.repo } : {}),
@@ -889,7 +1067,7 @@ program
889
1067
  .option("--json", "emit JSON instead of a human-readable table")
890
1068
  .action(async (opts) => {
891
1069
  const { missStats } = await import("../core/recall/miss-log.js");
892
- const stats = await missStats(opts.days);
1070
+ const stats = await missStats(DEFAULT_TEAM_ID, opts.days);
893
1071
  if (opts.json) {
894
1072
  process.stdout.write(`${JSON.stringify(stats, null, 2)}\n`);
895
1073
  return;
@@ -924,9 +1102,9 @@ program
924
1102
  const { readQueryLog } = await import("../core/recall/query-log.js");
925
1103
  const { readCitationLog } = await import("../core/recall/citation-log.js");
926
1104
  const [recallEntries, queryEntries, citationEntries] = await Promise.all([
927
- readHookRecallLog(opts.days),
928
- readQueryLog(opts.days),
929
- readCitationLog(opts.days),
1105
+ readHookRecallLog(DEFAULT_TEAM_ID, opts.days),
1106
+ readQueryLog(DEFAULT_TEAM_ID, opts.days),
1107
+ readCitationLog(DEFAULT_TEAM_ID, opts.days),
930
1108
  ]);
931
1109
  const result = computePrecision(recallEntries, citationEntries);
932
1110
  const { perSource, unmeasurable } = computePerSourcePrecision(queryEntries, citationEntries);
@@ -984,7 +1162,7 @@ program
984
1162
  process.exitCode = 1;
985
1163
  return;
986
1164
  }
987
- const deps = sqliteReDerivationDeps(store.rawDb());
1165
+ const deps = sqliteReDerivationDeps(store.rawDb(), DEFAULT_TEAM_ID);
988
1166
  const report = await computeReDerivationRate(deps, opts.window);
989
1167
  if (opts.json) {
990
1168
  process.stdout.write(JSON.stringify(report, null, 2) + "\n");
@@ -1016,6 +1194,46 @@ program
1016
1194
  yes: Boolean(opts.yes),
1017
1195
  });
1018
1196
  });
1197
+ program
1198
+ .command("reconcile-facts")
1199
+ .description("Find live facts that contradict an authorities file (dry run unless --apply)")
1200
+ .requiredOption("--authorities <file>", "JSON file of authoritative values and deleted files")
1201
+ .option("--apply", "retire the findings (backs up the DB first)")
1202
+ .option("-l, --limit <n>", "max facts changed per run", (v) => Number.parseInt(v, 10), 100)
1203
+ .option("--json", "emit the report as JSON")
1204
+ .action(async (opts) => {
1205
+ if (process.env["NLM_PG_URL"]) {
1206
+ console.error("nlm reconcile-facts: the Postgres backend is not supported yet");
1207
+ process.exit(1);
1208
+ }
1209
+ const storage = SqliteStorage.create({ dbPath: dbPath(), migrationsDir: MIGRATIONS_DIR });
1210
+ try {
1211
+ const db = storage.rawDb();
1212
+ const findings = findContradictions(db, loadAuthorities(opts.authorities));
1213
+ const limit = opts.limit;
1214
+ const applied = opts.apply ? await applyRetirements(db, dbPath(), findings, limit) : null;
1215
+ if (opts.json) {
1216
+ process.stdout.write(JSON.stringify({
1217
+ applied: Boolean(opts.apply),
1218
+ total: findings.length,
1219
+ findings,
1220
+ retired: applied?.retired.map((f) => f.factId) ?? [],
1221
+ backupPath: applied?.backupPath ?? null,
1222
+ }, null, 2) + "\n");
1223
+ return;
1224
+ }
1225
+ console.log(formatReport(findings, limit));
1226
+ if (applied) {
1227
+ console.log(`retired ${applied.retired.length} fact(s); backup: ${applied.backupPath ?? "none"}`);
1228
+ }
1229
+ else {
1230
+ console.log("dry run: nothing written (pass --apply to retire)");
1231
+ }
1232
+ }
1233
+ finally {
1234
+ await storage.close();
1235
+ }
1236
+ });
1019
1237
  program
1020
1238
  .command("classify-parity")
1021
1239
  .description("Run TS classifier against ~/.nlm/canonical.sqlite and diff vs persisted Python output")
@@ -1042,13 +1260,39 @@ program
1042
1260
  .option("-l, --limit <n>", "session cap (default: all)", (v) => Number.parseInt(v, 10))
1043
1261
  .option("--state <path>", "resume state file (default ~/.nlm/embed_reembed.state)")
1044
1262
  .option("--exemplars", "instead: embed code_exemplars rows missing a vector (repairs dropped capture embeds)")
1263
+ .option("--facts", "instead: embed active facts missing a vector (repairs dropped ingest embeds)")
1045
1264
  .option("--dry-run", "report dim mismatch and what would be dropped without writing")
1046
1265
  .option("-v, --verbose", "per-session progress on stderr")
1047
1266
  .action(async (opts) => {
1267
+ if (opts.facts) {
1268
+ const { storage } = await buildStack();
1269
+ try {
1270
+ const report = await backfillFactEmbeddings({
1271
+ tenantId: DEFAULT_TEAM_ID,
1272
+ dbPath: dbPath(),
1273
+ embedder: buildEmbedder(),
1274
+ store: storage.facts,
1275
+ ...(opts.limit ? { limit: opts.limit } : {}),
1276
+ ...(opts.verbose
1277
+ ? {
1278
+ onProgress: (i, n, id, status) => {
1279
+ process.stderr.write(` [${i}/${n}] ${id} ${status}\n`);
1280
+ },
1281
+ }
1282
+ : {}),
1283
+ });
1284
+ process.stdout.write(JSON.stringify(report, null, 2) + "\n");
1285
+ }
1286
+ finally {
1287
+ await storage.close();
1288
+ }
1289
+ return;
1290
+ }
1048
1291
  if (opts.exemplars) {
1049
1292
  const { storage } = await buildStack();
1050
1293
  try {
1051
1294
  const report = await backfillExemplarEmbeddings({
1295
+ tenantId: DEFAULT_TEAM_ID,
1052
1296
  dbPath: dbPath(),
1053
1297
  embedder: buildCodeEmbedder(),
1054
1298
  store: storage.exemplars,
@@ -1120,7 +1364,7 @@ program
1120
1364
  },
1121
1365
  }
1122
1366
  : {}),
1123
- });
1367
+ }, DEFAULT_TEAM_ID);
1124
1368
  process.stdout.write(JSON.stringify(report, null, 2) + "\n");
1125
1369
  }
1126
1370
  finally {
@@ -1141,7 +1385,7 @@ program
1141
1385
  await stack.storage.close();
1142
1386
  process.exit(1);
1143
1387
  }
1144
- const adapters = await buildAdapters(stack.sources);
1388
+ const adapters = await buildAdapters(stack.sources, DEFAULT_TEAM_ID);
1145
1389
  const r = await reclassifyOversized({
1146
1390
  db: stack.storage.rawDb(),
1147
1391
  store: stack.storage.sessions,
@@ -1150,7 +1394,7 @@ program
1150
1394
  classifier: stack.classifier,
1151
1395
  classifierDescriptor: { provider: stack.classifier.provider, model: stack.classifier.model },
1152
1396
  adapters,
1153
- }, { ...(opts.limit ? { limit: opts.limit } : {}), dryRun: Boolean(opts.dryRun) });
1397
+ }, DEFAULT_TEAM_ID, { ...(opts.limit ? { limit: opts.limit } : {}), dryRun: Boolean(opts.dryRun) });
1154
1398
  console.log(`reclassify-oversized: attempted=${r.attempted} ingested=${r.ingested} ` +
1155
1399
  `lowConfidence=${r.skippedLowConfidence} missingFile=${r.missingFile} failed=${r.failed} ` +
1156
1400
  `| gained entities=${r.entities} decisions=${r.decisions} facts=${r.facts}`);
@@ -1172,7 +1416,59 @@ program
1172
1416
  .option("--dry-run", "print cohort report without writing")
1173
1417
  .option("-v, --verbose", "per-session progress on stderr")
1174
1418
  .option("--force-embed", "proceed even when the stored prose embedding lane mismatches the runtime embedder")
1419
+ .option("--daemon", "submit to the running daemon's job supervisor instead of running in this process (restarts on OOM/crash, reports stalls)")
1175
1420
  .action(async (opts) => {
1421
+ if (opts.daemon) {
1422
+ // Same daemon-call auth pattern as `nlm ui`'s bootstrap-nonce POST:
1423
+ // autoload ~/.nlm/.env so a fresh shell still has NLM_MCP_TOKEN, then
1424
+ // send it as Authorization: Bearer (absent token = local-mode
1425
+ // fallback on the daemon side, never a hard requirement).
1426
+ autoloadEnv();
1427
+ const p = port();
1428
+ const token = process.env["NLM_MCP_TOKEN"];
1429
+ const args = [];
1430
+ if (opts.limit !== undefined)
1431
+ args.push("--limit", String(opts.limit));
1432
+ if (opts.minConfidence !== undefined)
1433
+ args.push("--min-confidence", String(opts.minConfidence));
1434
+ if (opts.onlyNull)
1435
+ args.push("--only-null");
1436
+ for (const tag of opts.excludeModel ?? [])
1437
+ args.push("--exclude-model", tag);
1438
+ if (opts.state)
1439
+ args.push("--state", opts.state);
1440
+ if (opts.dryRun)
1441
+ args.push("--dry-run");
1442
+ if (opts.forceEmbed)
1443
+ args.push("--force-embed");
1444
+ // -v is always added by the daemon's spawn adapter (job-supervisor.ts
1445
+ // parses reprocess's [i/n] progress lines from it) — a local
1446
+ // --verbose here would only affect this short-lived CLI invocation's
1447
+ // own stdout, which prints nothing itself, so it's not forwarded.
1448
+ try {
1449
+ const res = await fetch(`http://localhost:${p}/api/jobs/reprocess`, {
1450
+ method: "POST",
1451
+ headers: {
1452
+ "content-type": "application/json",
1453
+ ...(token ? { authorization: `Bearer ${token}` } : {}),
1454
+ },
1455
+ body: JSON.stringify({ args }),
1456
+ });
1457
+ const body = await res.json().catch(() => ({}));
1458
+ if (res.status === 202) {
1459
+ console.log(`reprocess --daemon: submitted (${JSON.stringify(body)})`);
1460
+ process.exit(0);
1461
+ }
1462
+ console.error(`reprocess --daemon: daemon returned ${res.status}: ${JSON.stringify(body)}`);
1463
+ process.exit(1);
1464
+ }
1465
+ catch (e) {
1466
+ console.error(`reprocess --daemon: could not reach the daemon at localhost:${p}. Is it running?`);
1467
+ console.error(` ${e instanceof Error ? e.message : String(e)}`);
1468
+ process.exit(1);
1469
+ }
1470
+ return;
1471
+ }
1176
1472
  const { reprocess: runReprocess } = await import("../core/ingest/reprocess.js");
1177
1473
  const stack = await buildStack();
1178
1474
  try {
@@ -1191,7 +1487,7 @@ program
1191
1487
  classifierDescriptor: { provider: stack.classifier.provider, model: stack.classifier.model },
1192
1488
  embeddingConfig: stack.storage.embeddingConfig,
1193
1489
  embedderDescriptor: { provider: embedderInfo.provider, model: embedderInfo.model },
1194
- }, {
1490
+ }, DEFAULT_TEAM_ID, {
1195
1491
  ...(opts.limit ? { limit: opts.limit } : {}),
1196
1492
  ...(opts.minConfidence !== undefined ? { minConfidence: opts.minConfidence } : {}),
1197
1493
  ...(opts.onlyNull ? { onlyNull: true } : {}),
@@ -1259,10 +1555,14 @@ program
1259
1555
  sessionScopeReader: store,
1260
1556
  workDigest: { store, topicProvider: loadTopicProvider(), workstreams: storage.workstreams, ...workDigestEnv() },
1261
1557
  workstreams: { store: storage.workstreams, sessions: store, facts: facts, exemplars: storage.exemplars },
1558
+ learners: { store: storage.learners, workstreams: storage.workstreams, sessions: store },
1262
1559
  // Tier-B outcome rollup (#352 phase 2) only has a SQLite adapter today;
1263
1560
  // PgStorage deployments skip get_session's `outcome` field until parity lands.
1264
1561
  ...(storage instanceof SqliteStorage ? { outcomeDb: storage.rawDb() } : {}),
1265
- });
1562
+ },
1563
+ // stdio MCP transport carries no bearer token — it is local-mode only
1564
+ // by construction (program spec §3), bound to the default team.
1565
+ DEFAULT_TEAM_ID);
1266
1566
  const transport = new StdioServerTransport();
1267
1567
  await server.connect(transport);
1268
1568
  });
@@ -1690,6 +1990,9 @@ hook
1690
1990
  console.error(" Session-end hook cleans up ~/.nlm/hook-state/<session>.json on session close.");
1691
1991
  console.error(" To run silently for calibration (no injection): set NLM_HOOK_MODE=shadow in the command.");
1692
1992
  console.error(" To remove: nlm hook uninstall");
1993
+ const nvmWarning = nvmPinnedHookWarning(process.execPath, homedir());
1994
+ if (nvmWarning)
1995
+ console.error(`nlm: ${nvmWarning}`);
1693
1996
  });
1694
1997
  hook
1695
1998
  .command("uninstall")
@@ -1714,13 +2017,19 @@ connect
1714
2017
  .option("--with-hooks", "additionally write absolute paths to ~/.codex/hooks.json (Codex Desktop fallback for openai/codex#16430)")
1715
2018
  .option("--repair", "first strip a stale pre-rename nlm-memory-ts install (config block + plugin + marketplace), then connect")
1716
2019
  .option("--dry-run", "print what would happen without invoking codex")
2020
+ .option("--stdio", "use the stdio transport instead of the shared HTTP endpoint (works with the daemon stopped)")
1717
2021
  .action((opts) => {
1718
2022
  if (!opts.dryRun && !codexBinaryAvailable()) {
1719
2023
  console.error("nlm connect codex: `codex` binary not on PATH. Install via `npm i -g @openai/codex` or `brew install codex`.");
1720
2024
  process.exit(1);
1721
2025
  }
1722
2026
  const source = opts.local ? REPO_ROOT : opts.source;
1723
- const connectOpts = { source, withHooks: Boolean(opts.withHooks), dryRun: Boolean(opts.dryRun) };
2027
+ const connectOpts = {
2028
+ source,
2029
+ withHooks: Boolean(opts.withHooks),
2030
+ dryRun: Boolean(opts.dryRun),
2031
+ transport: opts.stdio ? "stdio" : "http",
2032
+ };
1724
2033
  const repair = opts.repair
1725
2034
  ? repairCodex(connectOpts, pluginScriptsDir(REPO_ROOT))
1726
2035
  : null;
@@ -1773,17 +2082,25 @@ connect
1773
2082
  .description("Write the nlm-memory MCP server block into ~/.mcp.json")
1774
2083
  .option("--with-hooks", "also install Claude Code session hooks")
1775
2084
  .option("--dry-run", "print what would happen without changing files")
2085
+ .option("--stdio", "use the stdio transport instead of the shared HTTP endpoint (works with the daemon stopped)")
1776
2086
  .action((opts) => {
2087
+ // A fresh shell has no NLM_MCP_TOKEN exported even though ~/.nlm/.env
2088
+ // holds one; the HTTP block needs it. Mirror `nlm ui`'s lookup.
2089
+ autoloadEnv();
2090
+ const transport = opts.stdio ? "stdio" : "http";
1777
2091
  if (opts.dryRun) {
1778
2092
  console.error("nlm connect claude-code (dry run):");
1779
- console.error(` write [mcpServers.nlm-memory] to ${mcpConfigPath()}`);
2093
+ console.error(` write [mcpServers.nlm-memory] (${transport}) to ${mcpConfigPath()}`);
1780
2094
  if (opts.withHooks)
1781
2095
  console.error(" install 6 Claude Code hooks");
1782
2096
  return;
1783
2097
  }
1784
- const report = connectClaudeCode({ nlmBinPath: __filename, nodeExecPath: process.execPath });
2098
+ const report = connectClaudeCode({ nlmBinPath: __filename, nodeExecPath: process.execPath, transport });
1785
2099
  const action = report.alreadyPresent ? "updated" : "written";
1786
- console.error(`nlm: [mcpServers.nlm-memory] ${action} → ${report.mcpConfigPath}`);
2100
+ console.error(`nlm: [mcpServers.nlm-memory] ${action} (${report.transport}) → ${report.mcpConfigPath}`);
2101
+ if (report.migratedFromStdio) {
2102
+ console.error(" migrated from stdio → shared HTTP endpoint (one daemon-backed server for every session).");
2103
+ }
1787
2104
  console.error(" Restart Claude Code to activate the MCP server.");
1788
2105
  if (opts.withHooks) {
1789
2106
  const path = claudeSettingsPath();
@@ -1800,20 +2117,31 @@ connect
1800
2117
  process.exit(1);
1801
2118
  }
1802
2119
  console.error(`nlm: ${result.count} hooks installed → ${path}`);
2120
+ const nvmWarning = nvmPinnedHookWarning(process.execPath, homedir());
2121
+ if (nvmWarning)
2122
+ console.error(`nlm: ${nvmWarning}`);
1803
2123
  }
1804
2124
  });
1805
2125
  connect
1806
2126
  .command("hermes")
1807
2127
  .description("Write the nlm-memory MCP server entry into ~/.hermes/config.yaml")
1808
2128
  .option("--dry-run", "print what would happen without changing files")
2129
+ .option("--stdio", "use the stdio transport instead of the shared HTTP endpoint (works with the daemon stopped)")
1809
2130
  .action((opts) => {
2131
+ // A fresh shell has no NLM_MCP_TOKEN exported even though ~/.nlm/.env
2132
+ // holds one; the HTTP block needs it. Mirror `nlm ui`'s lookup.
2133
+ autoloadEnv();
2134
+ const transport = opts.stdio ? "stdio" : "http";
1810
2135
  if (opts.dryRun) {
1811
- console.error(`nlm connect hermes (dry run): write [mcp_servers.nlm-memory] to ${hermesConfigPath()}`);
2136
+ console.error(`nlm connect hermes (dry run): write [mcp_servers.nlm-memory] (${transport}) to ${hermesConfigPath()}`);
1812
2137
  return;
1813
2138
  }
1814
- const report = connectHermes({ nlmBinPath: __filename, nodeExecPath: process.execPath, dryRun: false });
2139
+ const report = connectHermes({ nlmBinPath: __filename, nodeExecPath: process.execPath, dryRun: false, transport });
1815
2140
  const action = report.alreadyPresent ? "updated" : "written";
1816
- console.error(`nlm: [mcp_servers.nlm-memory] ${action} → ${report.configPath}`);
2141
+ console.error(`nlm: [mcp_servers.nlm-memory] ${action} (${report.transport}) → ${report.configPath}`);
2142
+ if (report.migratedFromStdio) {
2143
+ console.error(" migrated from stdio → shared HTTP endpoint (one daemon-backed server for every session).");
2144
+ }
1817
2145
  console.error(" Restart Hermes to activate the MCP server.");
1818
2146
  });
1819
2147
  connect
@@ -1848,7 +2176,7 @@ connect
1848
2176
  const storage = SqliteStorage.create({ dbPath: dbPath(), migrationsDir: MIGRATIONS_DIR });
1849
2177
  await storage.init();
1850
2178
  try {
1851
- const report = await connectCursor(storage.sources, {
2179
+ const report = await connectCursor(storage.sources, DEFAULT_TEAM_ID, {
1852
2180
  ...(opts.dbPath ? { dbPath: opts.dbPath } : {}),
1853
2181
  dryRun: Boolean(opts.dryRun),
1854
2182
  });
@@ -1880,7 +2208,7 @@ connect
1880
2208
  const storage = SqliteStorage.create({ dbPath: dbPath(), migrationsDir: MIGRATIONS_DIR });
1881
2209
  await storage.init();
1882
2210
  try {
1883
- const report = await connectWindsurf(storage.sources, {
2211
+ const report = await connectWindsurf(storage.sources, DEFAULT_TEAM_ID, {
1884
2212
  ...(opts.userDir ? { userDir: opts.userDir } : {}),
1885
2213
  dryRun: Boolean(opts.dryRun),
1886
2214
  });
@@ -1904,30 +2232,66 @@ connect
1904
2232
  connect
1905
2233
  .command("opencode")
1906
2234
  .description("Register OpenCode as an nlm source (reads opencode.db directly) and optionally install rules nudge")
2235
+ .option("--db-path <path>", "override path to opencode.db")
1907
2236
  .option("--with-rules", "also install global rules nudge at ~/.config/opencode/AGENTS.md")
1908
2237
  .option("--dry-run", "print what would happen without changing files")
1909
- .action((opts) => {
2238
+ .action(async (opts) => {
2239
+ const spec = {
2240
+ kind: "opencode",
2241
+ name: "OpenCode",
2242
+ runtimeLabel: "opencode/1.0",
2243
+ path: opts.dbPath ?? openCodeDefaultDbPath(),
2244
+ };
2245
+ await connectLocalSourceCommand(spec, Boolean(opts.dryRun));
1910
2246
  if (opts.dryRun) {
1911
- console.error("nlm connect opencode (dry run):");
1912
- console.error(" OpenCode adapter is already wired via migrations/010_sources_opencode.sql — no source-registry mutation required");
1913
2247
  if (opts.withRules)
1914
- console.error(" install global rules nudge at ~/.config/opencode/AGENTS.md");
2248
+ console.error(" also install global rules nudge at ~/.config/opencode/AGENTS.md");
1915
2249
  return;
1916
2250
  }
1917
- console.error("nlm: OpenCode source already registered (see migration 010). No source-registry changes needed.");
1918
2251
  if (opts.withRules) {
1919
2252
  const rules = installOpencodeRules();
1920
2253
  console.error(` ${describeUpsert("OpenCode", rules)}`);
1921
2254
  }
1922
- else {
1923
- console.error(" Pass --with-rules to install the recall nudge at ~/.config/opencode/AGENTS.md");
1924
- }
1925
2255
  });
2256
+ connect
2257
+ .command("muse")
2258
+ .description("Register Muse as an nlm source (reads its session store directly — no files installed)")
2259
+ .option("--sessions-path <path>", "override path to the Muse sessions directory")
2260
+ .option("--dry-run", "print what would happen without changing files")
2261
+ .action(async (opts) => {
2262
+ await connectLocalSourceCommand({
2263
+ kind: "muse",
2264
+ name: "Muse",
2265
+ runtimeLabel: "muse/1.0",
2266
+ path: opts.sessionsPath ?? museDefaultSessionsPath(),
2267
+ }, Boolean(opts.dryRun));
2268
+ });
2269
+ async function connectLocalSourceCommand(spec, dryRun) {
2270
+ const storage = SqliteStorage.create({ dbPath: dbPath(), migrationsDir: MIGRATIONS_DIR });
2271
+ await storage.init();
2272
+ try {
2273
+ const report = await connectLocalSource(storage.sources, DEFAULT_TEAM_ID, spec, { dryRun });
2274
+ const missing = report.exists ? "" : " (not found yet)";
2275
+ if (dryRun) {
2276
+ console.error(`nlm connect ${spec.kind} (dry run): register source at ${report.path}${missing}`);
2277
+ return;
2278
+ }
2279
+ console.error(`nlm: ${spec.name} source ${report.action} → ${report.path}${missing}`);
2280
+ console.error(" restart the daemon to start scanning it");
2281
+ }
2282
+ finally {
2283
+ await storage.close();
2284
+ }
2285
+ }
1926
2286
  connect
1927
2287
  .command("pi")
1928
- .description("Register the nlm-memory prompt-recall extension in ~/.pi/agent/settings.json")
2288
+ .description("Register the nlm-memory prompt-recall extension + MCP server for pi")
1929
2289
  .option("--dry-run", "print what would happen without changing files")
2290
+ .option("--stdio", "use the stdio transport instead of the shared HTTP endpoint (works with the daemon stopped)")
1930
2291
  .action((opts) => {
2292
+ // The HTTP block needs NLM_MCP_TOKEN, which a fresh shell has not exported.
2293
+ autoloadEnv();
2294
+ const transport = opts.stdio ? "stdio" : "http";
1931
2295
  const pluginDir = join(REPO_ROOT, "nlm");
1932
2296
  const report = connectPi({ pluginDir, dryRun: Boolean(opts.dryRun) });
1933
2297
  if (opts.dryRun) {
@@ -1942,6 +2306,19 @@ connect
1942
2306
  console.error(`nlm: pi extension registered → ${report.settingsPath}`);
1943
2307
  console.error(` Packages entry: ${report.pluginDir}`);
1944
2308
  }
2309
+ // Two different files: the extension supplies the hook, mcp.json supplies
2310
+ // the callable tools. pi core has no MCP concept; pi-mcp-adapter reads it.
2311
+ const mcp = connectPiMcp({
2312
+ transport,
2313
+ nlmBinPath: __filename,
2314
+ nodeExecPath: process.execPath,
2315
+ dryRun: Boolean(opts.dryRun),
2316
+ });
2317
+ const mcpVerb = opts.dryRun ? "would write" : (mcp.alreadyPresent ? "updated" : "written");
2318
+ console.error(`nlm: [mcpServers.nlm-memory] ${mcpVerb} (${mcp.transport}) → ${mcp.mcpConfigPath}`);
2319
+ if (mcp.migratedFromStdio) {
2320
+ console.error(" migrated from stdio → shared HTTP endpoint (one daemon-backed server for every session).");
2321
+ }
1945
2322
  console.error(" Restart pi to activate the prompt-recall hook.");
1946
2323
  console.error(" Set NLM_HOOK_MODE=live in ~/.nlm/.env to flip from shadow → live.");
1947
2324
  });
@@ -1953,6 +2330,7 @@ disconnect
1953
2330
  .description("Remove the nlm-memory plugin + marketplace from Codex")
1954
2331
  .option("--with-hooks", "also strip our entries from ~/.codex/hooks.json")
1955
2332
  .option("--dry-run", "print what would happen without invoking codex")
2333
+ .option("--stdio", "use the stdio transport instead of the shared HTTP endpoint (works with the daemon stopped)")
1956
2334
  .action((opts) => {
1957
2335
  if (!opts.dryRun && !codexBinaryAvailable()) {
1958
2336
  console.error("nlm disconnect codex: `codex` binary not on PATH.");
@@ -2043,7 +2421,7 @@ disconnect
2043
2421
  const storage = SqliteStorage.create({ dbPath: dbPath(), migrationsDir: MIGRATIONS_DIR });
2044
2422
  await storage.init();
2045
2423
  try {
2046
- const report = await disconnectCursor(storage.sources, { dryRun: Boolean(opts.dryRun) });
2424
+ const report = await disconnectCursor(storage.sources, DEFAULT_TEAM_ID, { dryRun: Boolean(opts.dryRun) });
2047
2425
  if (opts.dryRun) {
2048
2426
  console.error("nlm disconnect cursor (dry run): disable Cursor source in registry");
2049
2427
  if (opts.withRules)
@@ -2071,7 +2449,7 @@ disconnect
2071
2449
  const storage = SqliteStorage.create({ dbPath: dbPath(), migrationsDir: MIGRATIONS_DIR });
2072
2450
  await storage.init();
2073
2451
  try {
2074
- const report = await disconnectWindsurf(storage.sources, { dryRun: Boolean(opts.dryRun) });
2452
+ const report = await disconnectWindsurf(storage.sources, DEFAULT_TEAM_ID, { dryRun: Boolean(opts.dryRun) });
2075
2453
  if (opts.dryRun) {
2076
2454
  console.error("nlm disconnect windsurf (dry run): disable Windsurf source in registry");
2077
2455
  if (opts.withRules)
@@ -2149,7 +2527,7 @@ program
2149
2527
  const storage = await buildStorage(dbPath());
2150
2528
  const scope = installScope();
2151
2529
  const sinceTs = new Date(Date.now() - opts.days * 86_400_000).toISOString();
2152
- const rows = await storage.signals.listForAggregation({ installScope: scope, sinceTs });
2530
+ const rows = await storage.signals.listForAggregation(DEFAULT_TEAM_ID, { installScope: scope, sinceTs });
2153
2531
  const { aggregateFailureModes } = await import("../core/signals/aggregate.js");
2154
2532
  const { recommendActions } = await import("../core/signals/recommend.js");
2155
2533
  const modes = aggregateFailureModes(rows);
@@ -2196,7 +2574,7 @@ program
2196
2574
  const date = resolveDigestDate(opts.date);
2197
2575
  const { storage, store } = await buildStack();
2198
2576
  try {
2199
- const digest = await buildWorkDigest({ store, topicProvider: loadTopicProvider(), workstreams: storage.workstreams, ...workDigestEnv() }, date);
2577
+ const digest = await buildWorkDigest({ store, topicProvider: loadTopicProvider(), workstreams: storage.workstreams, ...workDigestEnv() }, DEFAULT_TEAM_ID, date);
2200
2578
  console.log(composeWorkDigest(digest));
2201
2579
  }
2202
2580
  finally {
@@ -2246,6 +2624,13 @@ async function gatherInstallProbe() {
2246
2624
  // trust entry is Codex's registry (not nlm's), so it must not trip this check.
2247
2625
  codexStale = txt.includes("@nlm-memory-ts");
2248
2626
  }
2627
+ // Codex plugins contribute MCP servers to the effective configuration
2628
+ // without writing a table into config.toml. Prefer Codex's own resolved
2629
+ // view so `nlm verify` does not report a healthy plugin install as unwired.
2630
+ if (!codexMcp && codexBinaryAvailable()) {
2631
+ codexPresent = true;
2632
+ codexMcp = codexHasEffectiveMcpServer("nlm-memory");
2633
+ }
2249
2634
  const envPath = join(homedir(), ".nlm", ".env");
2250
2635
  return {
2251
2636
  daemon: { reachable, version, expectedVersion: pkg.version },
@@ -2558,7 +2943,7 @@ entitiesCmd
2558
2943
  if (opts.applySafe) {
2559
2944
  for (const s of safe) {
2560
2945
  try {
2561
- await storage.entities.merge(s.source, s.target);
2946
+ await storage.entities.merge(DEFAULT_TEAM_ID, s.source, s.target);
2562
2947
  console.log(`merged: ${s.source} -> ${s.target}`);
2563
2948
  merged++;
2564
2949
  }
@@ -2582,7 +2967,7 @@ entitiesCmd
2582
2967
  const answer = await ask(`merge ${s.source} -> ${s.target}? [y/N] `);
2583
2968
  if (answer.trim().toLowerCase() === "y") {
2584
2969
  try {
2585
- await storage.entities.merge(s.source, s.target);
2970
+ await storage.entities.merge(DEFAULT_TEAM_ID, s.source, s.target);
2586
2971
  console.log(`merged: ${s.source} -> ${s.target}`);
2587
2972
  merged++;
2588
2973
  }