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
@@ -0,0 +1,335 @@
1
+ /**
2
+ * JobSupervisor — owns at most one child `reprocess` run at a time.
3
+ *
4
+ * Built because a detached `nlm reprocess` died on a V8 OOM and sat dead
5
+ * ~16h unobserved (twice: 07-27 and 07-27→08-03). Supervision now lives in
6
+ * the daemon instead of a bolt-on watchdog script: this class tracks a
7
+ * single child process's progress, restarts it on a non-clean exit while
8
+ * work remains, and reports stalls when the child is alive but stuck.
9
+ *
10
+ * One job type only (reprocess) — no generic job framework (YAGNI). Ports
11
+ * (`spawnChild`, `clock`) are injected so this stays pure-core: no real
12
+ * child_process, no real timers, testable with fakes. Same posture as
13
+ * `ScanScheduler` (src/core/scheduler/scheduler.ts).
14
+ *
15
+ * Progress-line format verified directly in src/cli/nlm.ts's `reprocess`
16
+ * command (the only place reprocess's `onProgress` callback is wired to
17
+ * output — reprocess.ts itself never prints, it just calls the callback):
18
+ *
19
+ * process.stderr.write(` [${i}/${n}] ${sid} ${status}\n`);
20
+ *
21
+ * i.e. two leading spaces, `[i/n]` (no zero-padding), a single space, the
22
+ * session id, TWO spaces, then a status word (`ok`, `empty_body`,
23
+ * `classify_failed`, or `ingest_failed`). The plan's assumed format
24
+ * (`[N/total] <id> ok`) was close but understated the leading indent and
25
+ * the double space before status. `processed` in reprocess.ts increments
26
+ * on every branch (success or failure), so ANY line matching `[i/n]` is a
27
+ * genuine advance regardless of the trailing status word — the parser
28
+ * below only needs the two numbers.
29
+ *
30
+ * Restart policy: child exit with work remaining → re-spawn same args, up
31
+ * to `maxRestartsWithoutProgress` (default 3) consecutive restarts with no
32
+ * progress observed since the last one. A progress line resets the
33
+ * counter to 0. Exceeding the cap fires `{kind: "exhausted"}` and stops
34
+ * respawning. "Work remaining" is `code !== 0` (covers OOM/signal kills
35
+ * and any non-clean exit, including one where no progress line was ever
36
+ * seen) OR `processed < total` on an otherwise-clean exit — reprocess only
37
+ * exits 0 after its `work` loop finishes every item, so a clean exit with
38
+ * `processed < total` is a defensive fallback, not the expected path.
39
+ *
40
+ * Stall: child alive, no progress advance for `stallMinutes` (default 20,
41
+ * from `NLM_JOB_STALL_MINUTES`) → fires `{kind: "stalled"}` ONCE per stall
42
+ * episode. A later progress advance ends the episode (state returns to
43
+ * "running"); a subsequent stall re-arms and can fire again. Checked on a
44
+ * coarse interval behind the injected `clock` port so tests never need
45
+ * real timers.
46
+ *
47
+ * `restarts` in both the snapshot and event payloads is the
48
+ * restarts-without-progress counter (reset by any advance), not a
49
+ * lifetime total — it directly explains "why is this run stalled/
50
+ * exhausted" the way an operator reading /api/health would want. It counts
51
+ * only respawns that actually happened: the cap check runs BEFORE the
52
+ * increment, so an exhausted run's `restarts` equals the number of real
53
+ * respawns performed (== `maxRestartsWithoutProgress`), never that value
54
+ * plus the refused final attempt.
55
+ *
56
+ * `restartsTotal` is a companion lifetime counter: every successful respawn
57
+ * increments it, and unlike `restarts` it is never reset by progress —
58
+ * only a fresh `start()` resets it to 0. It exists because `restarts`
59
+ * resetting on every advance makes OOM churn invisible on a long run (a job
60
+ * that OOM-restarts 40 times but keeps making progress between crashes
61
+ * reports `restarts: 0`); `restartsTotal` is the "how much churn has this
62
+ * run actually had" number surfaced alongside it.
63
+ *
64
+ * State machine: "idle" (never started, or this JobSupervisor instance's
65
+ * initial state — not one of the plan's five snapshot states, but the
66
+ * natural "no job block" representation Task 2's health-route mapping
67
+ * needs) → "running" → ("stalled" ⇄ "running" across stall episodes) →
68
+ * terminal "completed" | "exhausted" | "stopped". A terminal state is
69
+ * left only by calling `start()` again, which begins a fresh run.
70
+ *
71
+ * Spawn-failure safety: `spawnChild` can throw synchronously (EMFILE,
72
+ * ENOMEM, a bad binary path) instead of returning a handle — both at
73
+ * `start()` and at every mid-run respawn inside `handleExit`. Both call
74
+ * sites wrap `spawnAndWire()` in try/catch: on a thrown spawn, the run
75
+ * lands in the terminal "exhausted" state (never leaves `state` reading
76
+ * "running" with no child) and fires a `{kind: "spawn_failed"}` event so
77
+ * an operator can tell "never got off the ground" apart from "ran out of
78
+ * restart budget". `start()` still rethrows the original error after
79
+ * landing that terminal state, preserving its existing contract (the HTTP
80
+ * route at `POST /api/jobs/reprocess` catches the throw and returns a
81
+ * clean 500) — the fix is that by the time it throws, `snapshot()` already
82
+ * reads a terminal state, so a subsequent `start()` call is not refused
83
+ * with "already active" and a subsequent POST does not 409 forever.
84
+ * `handleExit` has no caller to rethrow to (it runs off an async `onExit`
85
+ * callback), so it only logs and stops there.
86
+ *
87
+ * Stale-callback safety: `spawnAndWire` captures the child instance it just
88
+ * spawned and every onLine/onExit callback checks `this.child === child`
89
+ * before touching any state. A killed child (stop(), or a fast respawn
90
+ * chain) can still deliver a queued exit or line after this.child has moved
91
+ * on to a newer child or gone null; without the identity guard that stale
92
+ * delivery would corrupt the live run's processed/total/restarts, null out
93
+ * the live child (leaking it — now unmanaged and unkillable), or trigger a
94
+ * bogus respawn.
95
+ */
96
+ const DEFAULT_STALL_MINUTES = 20;
97
+ const DEFAULT_MAX_RESTARTS_WITHOUT_PROGRESS = 3;
98
+ const DEFAULT_STALL_CHECK_INTERVAL_MS = 60_000;
99
+ // Matches the two-space-indented `[i/n] <sid> <status>` verbose line reprocess's
100
+ // CLI wiring writes (src/cli/nlm.ts, reprocess command). Only the counters
101
+ // matter: reprocess increments `processed` on every outcome branch, so any
102
+ // line of this shape is an advance regardless of what follows it.
103
+ const PROGRESS_RE = /^\s*\[(\d+)\/(\d+)\]/;
104
+ function stallMinutesFromEnv() {
105
+ const raw = process.env["NLM_JOB_STALL_MINUTES"];
106
+ if (raw === undefined)
107
+ return DEFAULT_STALL_MINUTES;
108
+ const parsed = Number(raw);
109
+ return Number.isFinite(parsed) && parsed > 0 ? parsed : DEFAULT_STALL_MINUTES;
110
+ }
111
+ export class JobSupervisor {
112
+ spawnChildFn;
113
+ clock;
114
+ onEventCb;
115
+ stallMs;
116
+ maxRestartsWithoutProgress;
117
+ log;
118
+ state = "idle";
119
+ args = [];
120
+ child = null;
121
+ processed = 0;
122
+ total = 0;
123
+ startedAt = null;
124
+ lastAdvanceAt = null;
125
+ restarts = 0;
126
+ restartsTotal = 0;
127
+ stallEpisodeFired = false;
128
+ stopRequested = false;
129
+ constructor(opts) {
130
+ this.spawnChildFn = opts.spawnChild;
131
+ this.clock = opts.clock;
132
+ this.onEventCb = opts.onEvent ?? (() => { });
133
+ this.stallMs = (opts.stallMinutes ?? stallMinutesFromEnv()) * 60_000;
134
+ this.maxRestartsWithoutProgress =
135
+ opts.maxRestartsWithoutProgress ?? DEFAULT_MAX_RESTARTS_WITHOUT_PROGRESS;
136
+ this.log = opts.log ?? ((msg) => console.error(msg));
137
+ const stallCheckIntervalMs = opts.stallCheckIntervalMs ?? DEFAULT_STALL_CHECK_INTERVAL_MS;
138
+ this.clock.setInterval(() => this.checkStall(), stallCheckIntervalMs);
139
+ }
140
+ /** Starts a new run. Refuses (throws) if a run is already active. */
141
+ start(args) {
142
+ if (this.state === "running" || this.state === "stalled") {
143
+ throw new Error("reprocess job already active");
144
+ }
145
+ this.args = args;
146
+ this.processed = 0;
147
+ this.total = 0;
148
+ this.restarts = 0;
149
+ this.restartsTotal = 0;
150
+ this.stallEpisodeFired = false;
151
+ this.stopRequested = false;
152
+ this.startedAt = new Date(this.clock.now()).toISOString();
153
+ this.lastAdvanceAt = null;
154
+ this.state = "running";
155
+ try {
156
+ this.spawnAndWire();
157
+ }
158
+ catch (e) {
159
+ this.child = null;
160
+ this.state = "exhausted";
161
+ this.emitEvent({
162
+ kind: "spawn_failed",
163
+ job: "reprocess",
164
+ processed: this.processed,
165
+ total: this.total,
166
+ restarts: this.restarts,
167
+ restartsTotal: this.restartsTotal,
168
+ lastAdvanceAt: this.lastAdvanceAt,
169
+ });
170
+ throw e;
171
+ }
172
+ }
173
+ /** Kills the active child (if any) and moves to "stopped". No-op if idle/terminal. */
174
+ stop() {
175
+ if (this.state !== "running" && this.state !== "stalled")
176
+ return;
177
+ this.stopRequested = true;
178
+ const child = this.child;
179
+ this.child = null;
180
+ this.state = "stopped";
181
+ try {
182
+ child?.kill();
183
+ }
184
+ catch (e) {
185
+ this.log(`[job-supervisor] kill failed: ${e instanceof Error ? e.message : String(e)}`);
186
+ }
187
+ }
188
+ snapshot() {
189
+ return {
190
+ name: "reprocess",
191
+ state: this.state,
192
+ processed: this.processed,
193
+ total: this.total,
194
+ startedAt: this.startedAt,
195
+ lastAdvanceAt: this.lastAdvanceAt,
196
+ restarts: this.restarts,
197
+ restartsTotal: this.restartsTotal,
198
+ };
199
+ }
200
+ spawnAndWire() {
201
+ const child = this.spawnChildFn(this.args);
202
+ this.child = child;
203
+ // Identity-guard every callback against `this.child`: a killed-but-not-yet-
204
+ // exited child (stop(), or a respawn racing a slow exit/line delivery) can
205
+ // still deliver onLine/onExit after this.child has moved on to a newer
206
+ // child (or null). Without this guard a stale callback from child A would
207
+ // mutate state for the live run, null out this.child (leaking A's
208
+ // now-unmanaged, unkillable successor if A had one), or trigger a bogus
209
+ // restart/respawn.
210
+ child.onLine((line) => {
211
+ if (this.child !== child)
212
+ return;
213
+ try {
214
+ this.handleLine(line);
215
+ }
216
+ catch (e) {
217
+ this.log(`[job-supervisor] onLine handler error: ${e instanceof Error ? e.message : String(e)}`);
218
+ }
219
+ });
220
+ child.onExit((code) => {
221
+ if (this.child !== child)
222
+ return;
223
+ try {
224
+ this.handleExit(code);
225
+ }
226
+ catch (e) {
227
+ this.log(`[job-supervisor] onExit handler error: ${e instanceof Error ? e.message : String(e)}`);
228
+ }
229
+ });
230
+ }
231
+ handleLine(line) {
232
+ const match = PROGRESS_RE.exec(line);
233
+ if (!match)
234
+ return;
235
+ const processed = Number(match[1]);
236
+ const total = Number(match[2]);
237
+ if (!Number.isFinite(processed) || !Number.isFinite(total))
238
+ return;
239
+ this.processed = processed;
240
+ this.total = total;
241
+ this.lastAdvanceAt = new Date(this.clock.now()).toISOString();
242
+ this.restarts = 0;
243
+ if (this.state === "stalled")
244
+ this.state = "running";
245
+ this.stallEpisodeFired = false;
246
+ }
247
+ handleExit(code) {
248
+ this.child = null;
249
+ if (this.stopRequested)
250
+ return; // stop() already set state to "stopped"
251
+ const workRemaining = code !== 0 || (this.total > 0 && this.processed < this.total);
252
+ if (!workRemaining) {
253
+ this.state = "completed";
254
+ return;
255
+ }
256
+ // Checked BEFORE incrementing: `restarts` only counts respawns that
257
+ // actually happened, so exhaustion trips exactly when that many real
258
+ // respawns have already been spent — the emitted `restarts` on the
259
+ // "exhausted" event below is never one higher than the real count.
260
+ if (this.restarts >= this.maxRestartsWithoutProgress) {
261
+ this.state = "exhausted";
262
+ this.emitEvent({
263
+ kind: "exhausted",
264
+ job: "reprocess",
265
+ processed: this.processed,
266
+ total: this.total,
267
+ restarts: this.restarts,
268
+ restartsTotal: this.restartsTotal,
269
+ lastAdvanceAt: this.lastAdvanceAt,
270
+ });
271
+ return;
272
+ }
273
+ this.state = "running";
274
+ this.stallEpisodeFired = false;
275
+ try {
276
+ this.spawnAndWire();
277
+ }
278
+ catch (e) {
279
+ this.child = null;
280
+ this.state = "exhausted";
281
+ this.log(`[job-supervisor] respawn failed: ${e instanceof Error ? e.message : String(e)}`);
282
+ this.emitEvent({
283
+ kind: "spawn_failed",
284
+ job: "reprocess",
285
+ processed: this.processed,
286
+ total: this.total,
287
+ restarts: this.restarts,
288
+ restartsTotal: this.restartsTotal,
289
+ lastAdvanceAt: this.lastAdvanceAt,
290
+ });
291
+ return;
292
+ }
293
+ this.restarts += 1;
294
+ this.restartsTotal += 1;
295
+ }
296
+ checkStall() {
297
+ try {
298
+ if (this.state !== "running" && this.state !== "stalled")
299
+ return;
300
+ if (!this.child)
301
+ return;
302
+ if (this.stallEpisodeFired)
303
+ return;
304
+ const referenceIso = this.lastAdvanceAt ?? this.startedAt;
305
+ if (!referenceIso)
306
+ return;
307
+ const elapsed = this.clock.now() - Date.parse(referenceIso);
308
+ if (elapsed < this.stallMs)
309
+ return;
310
+ this.stallEpisodeFired = true;
311
+ this.state = "stalled";
312
+ this.emitEvent({
313
+ kind: "stalled",
314
+ job: "reprocess",
315
+ processed: this.processed,
316
+ total: this.total,
317
+ restarts: this.restarts,
318
+ restartsTotal: this.restartsTotal,
319
+ lastAdvanceAt: this.lastAdvanceAt,
320
+ });
321
+ }
322
+ catch (e) {
323
+ this.log(`[job-supervisor] stall check error: ${e instanceof Error ? e.message : String(e)}`);
324
+ }
325
+ }
326
+ emitEvent(event) {
327
+ try {
328
+ this.onEventCb(event);
329
+ }
330
+ catch (e) {
331
+ this.log(`[job-supervisor] onEvent handler threw: ${e instanceof Error ? e.message : String(e)}`);
332
+ }
333
+ }
334
+ }
335
+ //# sourceMappingURL=job-supervisor.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"job-supervisor.js","sourceRoot":"","sources":["../../../src/core/jobs/job-supervisor.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,63 @@
1
+ export type EnrollmentStatus = "active" | "completed";
2
+ export type AttemptResult = "correct" | "partial" | "incorrect";
3
+ export type AttemptHelp = "none" | "hint" | "worked-example";
4
+ export type AttemptKind = "application" | "retrieval" | "transfer";
5
+ export interface Learner {
6
+ readonly id: string;
7
+ readonly label: string;
8
+ readonly profile: Record<string, unknown>;
9
+ readonly createdAt: string;
10
+ readonly updatedAt: string;
11
+ }
12
+ export interface Attempt {
13
+ readonly concept: string;
14
+ readonly task: string;
15
+ readonly response: string;
16
+ readonly result: AttemptResult;
17
+ readonly help: AttemptHelp;
18
+ readonly kind: AttemptKind;
19
+ }
20
+ export interface LearningEvent {
21
+ readonly id: string;
22
+ readonly date: string;
23
+ readonly covered: ReadonlyArray<string>;
24
+ readonly attempts: ReadonlyArray<Attempt>;
25
+ readonly interpretation: string;
26
+ readonly nextStep: string;
27
+ readonly createdAt: string;
28
+ }
29
+ export interface Enrollment {
30
+ readonly id: string;
31
+ readonly learnerId: string;
32
+ readonly courseId: string;
33
+ readonly workstreamId: string | null;
34
+ readonly planRef: string;
35
+ readonly planRevision: number;
36
+ readonly planFingerprint: string;
37
+ readonly status: EnrollmentStatus;
38
+ readonly completionHistory: ReadonlyArray<unknown>;
39
+ readonly createdAt: string;
40
+ readonly updatedAt: string;
41
+ }
42
+ export declare function makeEnrollmentId(): string;
43
+ /**
44
+ * Canonical JSON: object keys sorted recursively, arrays preserved. pg JSONB
45
+ * reorders object keys on write, so byte comparison of round-tripped JSON
46
+ * false-conflicts; canonical form is the only backend-agnostic equality.
47
+ */
48
+ export declare function canonicalJson(value: unknown): string;
49
+ export interface StoredEventContent {
50
+ readonly date: string;
51
+ readonly covered: ReadonlyArray<string>;
52
+ readonly attempts: ReadonlyArray<Attempt>;
53
+ readonly interpretation: string;
54
+ readonly nextStep: string;
55
+ }
56
+ /** Idempotent-record comparison: same event if all content fields match. */
57
+ export declare function learningEventContentEqual(input: {
58
+ date: string;
59
+ covered: ReadonlyArray<string>;
60
+ attempts: ReadonlyArray<Attempt>;
61
+ interpretation: string;
62
+ nextStep: string;
63
+ }, stored: StoredEventContent): boolean;
@@ -0,0 +1,30 @@
1
+ // src/core/learner/model.ts
2
+ import { randomUUID } from "node:crypto";
3
+ export function makeEnrollmentId() {
4
+ return `enr_${randomUUID()}`;
5
+ }
6
+ /**
7
+ * Canonical JSON: object keys sorted recursively, arrays preserved. pg JSONB
8
+ * reorders object keys on write, so byte comparison of round-tripped JSON
9
+ * false-conflicts; canonical form is the only backend-agnostic equality.
10
+ */
11
+ export function canonicalJson(value) {
12
+ if (Array.isArray(value))
13
+ return `[${value.map(canonicalJson).join(",")}]`;
14
+ if (value !== null && typeof value === "object") {
15
+ const entries = Object.entries(value)
16
+ .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))
17
+ .map(([k, v]) => `${JSON.stringify(k)}:${canonicalJson(v)}`);
18
+ return `{${entries.join(",")}}`;
19
+ }
20
+ return JSON.stringify(value) ?? "null";
21
+ }
22
+ /** Idempotent-record comparison: same event if all content fields match. */
23
+ export function learningEventContentEqual(input, stored) {
24
+ return (input.date === stored.date &&
25
+ canonicalJson(input.covered) === canonicalJson(stored.covered) &&
26
+ canonicalJson(input.attempts) === canonicalJson(stored.attempts) &&
27
+ input.interpretation === stored.interpretation &&
28
+ input.nextStep === stored.nextStep);
29
+ }
30
+ //# sourceMappingURL=model.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"model.js","sourceRoot":"","sources":["../../../src/core/learner/model.ts"],"names":[],"mappings":"AAAA,4BAA4B;AAC5B,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAiDzC,MAAM,UAAU,gBAAgB;IAC9B,OAAO,OAAO,UAAU,EAAE,EAAE,CAAC;AAC/B,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,aAAa,CAAC,KAAc;IAC1C,IAAI,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC;QAAE,OAAO,IAAI,KAAK,CAAC,GAAG,CAAC,aAAa,CAAC,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;IAC3E,IAAI,KAAK,KAAK,IAAI,IAAI,OAAO,KAAK,KAAK,QAAQ,EAAE,CAAC;QAChD,MAAM,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC,KAAgC,CAAC;aAC7D,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC;aAChD,GAAG,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,IAAI,aAAa,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QAC/D,OAAO,IAAI,OAAO,CAAC,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;IAClC,CAAC;IACD,OAAO,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,IAAI,MAAM,CAAC;AACzC,CAAC;AAUD,4EAA4E;AAC5E,MAAM,UAAU,yBAAyB,CACvC,KAAmI,EACnI,MAA0B;IAE1B,OAAO,CACL,KAAK,CAAC,IAAI,KAAK,MAAM,CAAC,IAAI;QAC1B,aAAa,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,aAAa,CAAC,MAAM,CAAC,OAAO,CAAC;QAC9D,aAAa,CAAC,KAAK,CAAC,QAAQ,CAAC,KAAK,aAAa,CAAC,MAAM,CAAC,QAAQ,CAAC;QAChE,KAAK,CAAC,cAAc,KAAK,MAAM,CAAC,cAAc;QAC9C,KAAK,CAAC,QAAQ,KAAK,MAAM,CAAC,QAAQ,CACnC,CAAC;AACJ,CAAC"}
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Learner service — orchestration over LearnerStore + WorkstreamStore.
3
+ *
4
+ * Owns validation (validate.ts), learner auto-init, workstream id-or-label
5
+ * resolution, and summary assembly. Idempotency, fingerprint matching, and
6
+ * tenant isolation stay in the store. The MCP layer (M3) calls these
7
+ * operations; unrecorded-session listing needs SessionStore and lands in M3.
8
+ */
9
+ import type { LearnerStore, LearningEventInput, RecordResult } from "../../ports/learner-store.js";
10
+ import type { WorkstreamStore } from "../../ports/workstream-store.js";
11
+ import type { Enrollment, EnrollmentStatus } from "./model.js";
12
+ import { type ConceptSummary } from "./summary.js";
13
+ export interface LearnerServiceDeps {
14
+ readonly learners: LearnerStore;
15
+ readonly workstreams: Pick<WorkstreamStore, "getById" | "findByNormalizedLabel">;
16
+ }
17
+ export interface EnrollRequest {
18
+ readonly learnerId: string;
19
+ readonly learnerLabel?: string;
20
+ readonly courseId: string;
21
+ /** Workstream id or label; resolved like recall_workstream. */
22
+ readonly workstream: string;
23
+ readonly planRef: string;
24
+ readonly planRevision: number;
25
+ readonly planFingerprint: string;
26
+ }
27
+ export interface CourseSummary {
28
+ readonly learnerId: string;
29
+ readonly courseId: string;
30
+ readonly status: EnrollmentStatus;
31
+ readonly workstreamId: string | null;
32
+ readonly planRevision: number;
33
+ readonly concepts: ReadonlyArray<ConceptSummary>;
34
+ /** Latest event's next step, or null when nothing is recorded yet. */
35
+ readonly nextStep: string | null;
36
+ readonly eventCount: number;
37
+ readonly lastEventDate: string | null;
38
+ }
39
+ export declare function enrollCourse(deps: LearnerServiceDeps, tenantId: string, req: EnrollRequest): Promise<Enrollment>;
40
+ export declare function recordLearningEvent(deps: LearnerServiceDeps, tenantId: string, learnerId: string, courseId: string, event: LearningEventInput): Promise<RecordResult>;
41
+ export declare function completeLearnerCourse(deps: LearnerServiceDeps, tenantId: string, learnerId: string, courseId: string): Promise<void>;
42
+ export declare function getCourseSummary(deps: LearnerServiceDeps, tenantId: string, learnerId: string, courseId: string): Promise<CourseSummary | null>;
@@ -0,0 +1,74 @@
1
+ /**
2
+ * Learner service — orchestration over LearnerStore + WorkstreamStore.
3
+ *
4
+ * Owns validation (validate.ts), learner auto-init, workstream id-or-label
5
+ * resolution, and summary assembly. Idempotency, fingerprint matching, and
6
+ * tenant isolation stay in the store. The MCP layer (M3) calls these
7
+ * operations; unrecorded-session listing needs SessionStore and lands in M3.
8
+ */
9
+ import { normalizeLabel } from "../workstream/model.js";
10
+ import { assertValidEnrollmentInput, assertValidEventInput, assertValidLearnerInit, assertValidSlug, } from "./validate.js";
11
+ import { summarizeConcepts } from "./summary.js";
12
+ async function resolveWorkstreamId(deps, tenantId, idOrLabel) {
13
+ const trimmed = idOrLabel.trim();
14
+ if (!trimmed)
15
+ throw new Error("learner service: workstream id or label is required");
16
+ const found = (await deps.workstreams.getById(tenantId, trimmed)) ??
17
+ (await deps.workstreams.findByNormalizedLabel(tenantId, normalizeLabel(trimmed)));
18
+ if (!found)
19
+ throw new Error(`learner service: no workstream matches "${trimmed}"`);
20
+ return found.id;
21
+ }
22
+ export async function enrollCourse(deps, tenantId, req) {
23
+ assertValidSlug(req.learnerId, "enrollment.learnerId");
24
+ assertValidSlug(req.courseId, "enrollment.courseId");
25
+ const workstreamId = await resolveWorkstreamId(deps, tenantId, req.workstream);
26
+ const existing = await deps.learners.getLearner(tenantId, req.learnerId);
27
+ if (!existing) {
28
+ const init = { id: req.learnerId, label: req.learnerLabel ?? req.learnerId };
29
+ assertValidLearnerInit(init);
30
+ await deps.learners.initLearner(tenantId, init);
31
+ }
32
+ const input = {
33
+ learnerId: req.learnerId,
34
+ courseId: req.courseId,
35
+ workstreamId,
36
+ planRef: req.planRef,
37
+ planRevision: req.planRevision,
38
+ planFingerprint: req.planFingerprint,
39
+ };
40
+ assertValidEnrollmentInput(input);
41
+ return deps.learners.enroll(tenantId, input);
42
+ }
43
+ export async function recordLearningEvent(deps, tenantId, learnerId, courseId, event) {
44
+ assertValidSlug(learnerId, "enrollment.learnerId");
45
+ assertValidSlug(courseId, "enrollment.courseId");
46
+ assertValidEventInput(event);
47
+ return deps.learners.recordEvent(tenantId, learnerId, courseId, event);
48
+ }
49
+ export async function completeLearnerCourse(deps, tenantId, learnerId, courseId) {
50
+ assertValidSlug(learnerId, "enrollment.learnerId");
51
+ assertValidSlug(courseId, "enrollment.courseId");
52
+ return deps.learners.completeCourse(tenantId, learnerId, courseId);
53
+ }
54
+ export async function getCourseSummary(deps, tenantId, learnerId, courseId) {
55
+ assertValidSlug(learnerId, "enrollment.learnerId");
56
+ assertValidSlug(courseId, "enrollment.courseId");
57
+ const enrollment = await deps.learners.getEnrollment(tenantId, learnerId, courseId);
58
+ if (!enrollment)
59
+ return null;
60
+ const events = await deps.learners.listEvents(tenantId, learnerId, courseId);
61
+ const last = events.length > 0 ? events[events.length - 1] : null;
62
+ return {
63
+ learnerId,
64
+ courseId,
65
+ status: enrollment.status,
66
+ workstreamId: enrollment.workstreamId,
67
+ planRevision: enrollment.planRevision,
68
+ concepts: summarizeConcepts(events),
69
+ nextStep: last ? last.nextStep : null,
70
+ eventCount: events.length,
71
+ lastEventDate: last ? last.date : null,
72
+ };
73
+ }
74
+ //# sourceMappingURL=service.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"service.js","sourceRoot":"","sources":["../../../src/core/learner/service.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,28 @@
1
+ /**
2
+ * Learner summary derivation — port of the Gnos deterministic summary rule
3
+ * (`skills/learner-tracking/references/evidence.md`, MIT, attribution kept).
4
+ *
5
+ * Per concept, from events in `listEvents` order ((date, id) ascending):
6
+ *
7
+ * - covered, never attempted: **exposed**
8
+ * - latest attempt assisted, partial, or incorrect: **practicing**
9
+ * - latest attempt correct + unassisted: **demonstrated**
10
+ * - latest success is retrieval/transfer on a strictly later date than an
11
+ * earlier unassisted success: **retained**
12
+ *
13
+ * Latest failure demotes back to practicing; earlier evidence is preserved in
14
+ * the events, not in the label. These are workflow labels, not calibrated
15
+ * scores — the summary never issues "mastered".
16
+ */
17
+ import type { LearningEvent } from "./model.js";
18
+ export type ConceptStatus = "exposed" | "practicing" | "demonstrated" | "retained";
19
+ export interface ConceptSummary {
20
+ readonly concept: string;
21
+ readonly status: ConceptStatus;
22
+ readonly attemptCount: number;
23
+ /** Latest attempt date, else the date the concept was last covered. */
24
+ readonly latestDate: string | null;
25
+ /** Latest attempt's task, or null when never attempted. */
26
+ readonly latestTask: string | null;
27
+ }
28
+ export declare function summarizeConcepts(events: ReadonlyArray<LearningEvent>): ReadonlyArray<ConceptSummary>;