nlm-memory 0.21.0 → 0.21.12
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.
- package/README.md +67 -12
- package/assets/replay-eval-dark.svg +26 -0
- package/assets/replay-eval-light.svg +26 -0
- package/dist/cli/digest.js +34 -7
- package/dist/cli/digest.js.map +1 -1
- package/dist/cli/nlm.d.ts +14 -0
- package/dist/cli/nlm.js +450 -65
- package/dist/cli/nlm.js.map +1 -1
- package/dist/cli/reprocess-job-adapters.d.ts +35 -0
- package/dist/cli/reprocess-job-adapters.js +80 -0
- package/dist/cli/reprocess-job-adapters.js.map +1 -0
- package/dist/cli/scope-coverage.js +4 -1
- package/dist/cli/scope-coverage.js.map +1 -1
- package/dist/cli/supersede.js +8 -7
- package/dist/cli/supersede.js.map +1 -1
- package/dist/core/actions/actions-log.d.ts +65 -8
- package/dist/core/actions/actions-log.js +134 -17
- package/dist/core/actions/actions-log.js.map +1 -1
- package/dist/core/actions/overlay.d.ts +11 -2
- package/dist/core/actions/overlay.js +18 -6
- package/dist/core/actions/overlay.js.map +1 -1
- package/dist/core/adapters/from-source.js +11 -3
- package/dist/core/adapters/from-source.js.map +1 -1
- package/dist/core/adapters/fs-wiki-writer.d.ts +44 -0
- package/dist/core/adapters/fs-wiki-writer.js +132 -0
- package/dist/core/adapters/fs-wiki-writer.js.map +1 -0
- package/dist/core/adapters/hermes-agent.d.ts +15 -2
- package/dist/core/adapters/hermes-agent.js +57 -11
- package/dist/core/adapters/hermes-agent.js.map +1 -1
- package/dist/core/adapters/memory-wiki-writer.d.ts +8 -0
- package/dist/core/adapters/memory-wiki-writer.js +16 -0
- package/dist/core/adapters/memory-wiki-writer.js.map +1 -0
- package/dist/core/adapters/muse.d.ts +51 -0
- package/dist/core/adapters/muse.js +271 -0
- package/dist/core/adapters/muse.js.map +1 -0
- package/dist/core/adapters/opencode.d.ts +11 -4
- package/dist/core/adapters/opencode.js +41 -7
- package/dist/core/adapters/opencode.js.map +1 -1
- package/dist/core/alerts/alert-state.d.ts +17 -0
- package/dist/core/alerts/alert-state.js +45 -0
- package/dist/core/alerts/alert-state.js.map +1 -0
- package/dist/core/alerts/check-and-alert.d.ts +28 -0
- package/dist/core/alerts/check-and-alert.js +53 -0
- package/dist/core/alerts/check-and-alert.js.map +1 -0
- package/dist/core/alerts/drift-transition.d.ts +27 -0
- package/dist/core/alerts/drift-transition.js +56 -0
- package/dist/core/alerts/drift-transition.js.map +1 -0
- package/dist/core/alerts/embedder-transition.d.ts +23 -0
- package/dist/core/alerts/embedder-transition.js +40 -0
- package/dist/core/alerts/embedder-transition.js.map +1 -0
- package/dist/core/alerts/fire-alert.d.ts +24 -0
- package/dist/core/alerts/fire-alert.js +72 -0
- package/dist/core/alerts/fire-alert.js.map +1 -0
- package/dist/core/alerts/job-alert.d.ts +12 -0
- package/dist/core/alerts/job-alert.js +37 -0
- package/dist/core/alerts/job-alert.js.map +1 -0
- package/dist/core/alerts/types.d.ts +104 -0
- package/dist/core/alerts/types.js +11 -0
- package/dist/core/alerts/types.js.map +1 -0
- package/dist/core/alerts/wiki-alert.d.ts +15 -0
- package/dist/core/alerts/wiki-alert.js +31 -0
- package/dist/core/alerts/wiki-alert.js.map +1 -0
- package/dist/core/classifier/prompt.d.ts +13 -0
- package/dist/core/classifier/prompt.js +52 -2
- package/dist/core/classifier/prompt.js.map +1 -1
- package/dist/core/dataset/build-dataset.d.ts +16 -1
- package/dist/core/dataset/build-dataset.js +45 -13
- package/dist/core/dataset/build-dataset.js.map +1 -1
- package/dist/core/digest/compose.d.ts +8 -0
- package/dist/core/digest/compose.js +54 -0
- package/dist/core/digest/compose.js.map +1 -1
- package/dist/core/digest/hook-injection.d.ts +1 -1
- package/dist/core/digest/hook-injection.js +9 -1
- package/dist/core/digest/hook-injection.js.map +1 -1
- package/dist/core/digest/hook-version-parity.d.ts +31 -0
- package/dist/core/digest/hook-version-parity.js +56 -0
- package/dist/core/digest/hook-version-parity.js.map +1 -0
- package/dist/core/embedding/pg-embed-backfill.js +1 -1
- package/dist/core/embedding/pg-embed-backfill.js.map +1 -1
- package/dist/core/exemplars/capture-from-session.d.ts +1 -1
- package/dist/core/exemplars/capture-from-session.js +3 -3
- package/dist/core/exemplars/capture-from-session.js.map +1 -1
- package/dist/core/exemplars/embed-backfill.d.ts +9 -0
- package/dist/core/exemplars/embed-backfill.js +9 -1
- package/dist/core/exemplars/embed-backfill.js.map +1 -1
- package/dist/core/exemplars/recall-code.d.ts +1 -1
- package/dist/core/exemplars/recall-code.js +2 -2
- package/dist/core/exemplars/recall-code.js.map +1 -1
- package/dist/core/facts/backfill-facts.d.ts +1 -1
- package/dist/core/facts/backfill-facts.js +4 -4
- package/dist/core/facts/backfill-facts.js.map +1 -1
- package/dist/core/facts/embed-backfill.d.ts +54 -0
- package/dist/core/facts/embed-backfill.js +109 -0
- package/dist/core/facts/embed-backfill.js.map +1 -0
- package/dist/core/facts/reconcile-facts.d.ts +42 -0
- package/dist/core/facts/reconcile-facts.js +133 -0
- package/dist/core/facts/reconcile-facts.js.map +1 -0
- package/dist/core/health/embed-failure-state.d.ts +4 -0
- package/dist/core/health/embed-failure-state.js +12 -0
- package/dist/core/health/embed-failure-state.js.map +1 -0
- package/dist/core/health/warmup-retry.d.ts +27 -0
- package/dist/core/health/warmup-retry.js +53 -0
- package/dist/core/health/warmup-retry.js.map +1 -0
- package/dist/core/health/warmup-state.d.ts +15 -0
- package/dist/core/health/warmup-state.js +15 -1
- package/dist/core/health/warmup-state.js.map +1 -1
- package/dist/core/hook/cite-memo.d.ts +11 -7
- package/dist/core/hook/cite-memo.js +25 -17
- package/dist/core/hook/cite-memo.js.map +1 -1
- package/dist/core/hook/hook-log.d.ts +13 -2
- package/dist/core/hook/hook-log.js +16 -8
- package/dist/core/hook/hook-log.js.map +1 -1
- package/dist/core/hook/memo.d.ts +10 -6
- package/dist/core/hook/memo.js +25 -17
- package/dist/core/hook/memo.js.map +1 -1
- package/dist/core/hook/pointer-block.d.ts +8 -1
- package/dist/core/hook/pointer-block.js +17 -4
- package/dist/core/hook/pointer-block.js.map +1 -1
- package/dist/core/hook/resolve-conversation-by-query.d.ts +6 -5
- package/dist/core/hook/resolve-conversation-by-query.js +24 -12
- package/dist/core/hook/resolve-conversation-by-query.js.map +1 -1
- package/dist/core/ingest/ingest-session.d.ts +1 -1
- package/dist/core/ingest/ingest-session.js +3 -3
- package/dist/core/ingest/ingest-session.js.map +1 -1
- package/dist/core/ingest/reclassify-oversized.d.ts +1 -1
- package/dist/core/ingest/reclassify-oversized.js +2 -2
- package/dist/core/ingest/reclassify-oversized.js.map +1 -1
- package/dist/core/ingest/reprocess.d.ts +1 -1
- package/dist/core/ingest/reprocess.js +2 -2
- package/dist/core/ingest/reprocess.js.map +1 -1
- package/dist/core/jobs/job-supervisor.d.ts +168 -0
- package/dist/core/jobs/job-supervisor.js +335 -0
- package/dist/core/jobs/job-supervisor.js.map +1 -0
- package/dist/core/learner/model.d.ts +63 -0
- package/dist/core/learner/model.js +30 -0
- package/dist/core/learner/model.js.map +1 -0
- package/dist/core/learner/service.d.ts +42 -0
- package/dist/core/learner/service.js +74 -0
- package/dist/core/learner/service.js.map +1 -0
- package/dist/core/learner/summary.d.ts +28 -0
- package/dist/core/learner/summary.js +70 -0
- package/dist/core/learner/summary.js.map +1 -0
- package/dist/core/learner/validate.d.ts +18 -0
- package/dist/core/learner/validate.js +95 -0
- package/dist/core/learner/validate.js.map +1 -0
- package/dist/core/metrics/re-derivation.d.ts +31 -2
- package/dist/core/metrics/re-derivation.js +36 -4
- package/dist/core/metrics/re-derivation.js.map +1 -1
- package/dist/core/outcome/coverage.d.ts +1 -1
- package/dist/core/outcome/coverage.js +2 -2
- package/dist/core/outcome/coverage.js.map +1 -1
- package/dist/core/outcome/rollup.d.ts +1 -1
- package/dist/core/outcome/rollup.js +4 -4
- package/dist/core/outcome/rollup.js.map +1 -1
- package/dist/core/providers/provider-registry.d.ts +40 -23
- package/dist/core/providers/provider-registry.js +99 -81
- package/dist/core/providers/provider-registry.js.map +1 -1
- package/dist/core/recall/citation-log.d.ts +10 -5
- package/dist/core/recall/citation-log.js +21 -10
- package/dist/core/recall/citation-log.js.map +1 -1
- package/dist/core/recall/hook-recall-log.d.ts +5 -2
- package/dist/core/recall/hook-recall-log.js +12 -6
- package/dist/core/recall/hook-recall-log.js.map +1 -1
- package/dist/core/recall/miss-log.d.ts +7 -4
- package/dist/core/recall/miss-log.js +18 -11
- package/dist/core/recall/miss-log.js.map +1 -1
- package/dist/core/recall/query-log.d.ts +8 -5
- package/dist/core/recall/query-log.js +16 -9
- package/dist/core/recall/query-log.js.map +1 -1
- package/dist/core/recall/recall-service.d.ts +10 -1
- package/dist/core/recall/recall-service.js +31 -12
- package/dist/core/recall/recall-service.js.map +1 -1
- package/dist/core/recall/recent-log.d.ts +6 -1
- package/dist/core/recall/recent-log.js +13 -5
- package/dist/core/recall/recent-log.js.map +1 -1
- package/dist/core/recall/related-concepts.d.ts +29 -0
- package/dist/core/recall/related-concepts.js +67 -0
- package/dist/core/recall/related-concepts.js.map +1 -0
- package/dist/core/recall/related-exemplars.d.ts +1 -1
- package/dist/core/recall/related-exemplars.js +2 -2
- package/dist/core/recall/related-exemplars.js.map +1 -1
- package/dist/core/recall/related-facts.d.ts +1 -1
- package/dist/core/recall/related-facts.js +3 -3
- package/dist/core/recall/related-facts.js.map +1 -1
- package/dist/core/recall-facts/fact-query-log.d.ts +8 -4
- package/dist/core/recall-facts/fact-query-log.js +16 -8
- package/dist/core/recall-facts/fact-query-log.js.map +1 -1
- package/dist/core/recall-facts/fact-recall-service.d.ts +2 -2
- package/dist/core/recall-facts/fact-recall-service.js +12 -12
- package/dist/core/recall-facts/fact-recall-service.js.map +1 -1
- package/dist/core/scheduler/scan-once.js +15 -14
- package/dist/core/scheduler/scan-once.js.map +1 -1
- package/dist/core/scheduler/scheduler.d.ts +3 -0
- package/dist/core/scheduler/scheduler.js +15 -11
- package/dist/core/scheduler/scheduler.js.map +1 -1
- package/dist/core/signals/failure-mode-recall.d.ts +1 -1
- package/dist/core/signals/failure-mode-recall.js +2 -2
- package/dist/core/signals/failure-mode-recall.js.map +1 -1
- package/dist/core/signals/stamp-scope.d.ts +2 -2
- package/dist/core/signals/stamp-scope.js +2 -2
- package/dist/core/signals/stamp-scope.js.map +1 -1
- package/dist/core/sources/source-registry.d.ts +49 -28
- package/dist/core/sources/source-registry.js +113 -70
- package/dist/core/sources/source-registry.js.map +1 -1
- package/dist/core/storage/pg-code-exemplar-store.d.ts +19 -10
- package/dist/core/storage/pg-code-exemplar-store.js +58 -27
- package/dist/core/storage/pg-code-exemplar-store.js.map +1 -1
- package/dist/core/storage/pg-entity-store.d.ts +6 -1
- package/dist/core/storage/pg-entity-store.js +33 -18
- package/dist/core/storage/pg-entity-store.js.map +1 -1
- package/dist/core/storage/pg-fact-ingest.d.ts +1 -1
- package/dist/core/storage/pg-fact-ingest.js +10 -7
- package/dist/core/storage/pg-fact-ingest.js.map +1 -1
- package/dist/core/storage/pg-fact-store.d.ts +29 -17
- package/dist/core/storage/pg-fact-store.js +99 -46
- package/dist/core/storage/pg-fact-store.js.map +1 -1
- package/dist/core/storage/pg-learner-store.d.ts +27 -0
- package/dist/core/storage/pg-learner-store.js +174 -0
- package/dist/core/storage/pg-learner-store.js.map +1 -0
- package/dist/core/storage/pg-session-store.d.ts +24 -20
- package/dist/core/storage/pg-session-store.js +162 -90
- package/dist/core/storage/pg-session-store.js.map +1 -1
- package/dist/core/storage/pg-signal-store.d.ts +10 -5
- package/dist/core/storage/pg-signal-store.js +24 -12
- package/dist/core/storage/pg-signal-store.js.map +1 -1
- package/dist/core/storage/pg-storage.d.ts +4 -0
- package/dist/core/storage/pg-storage.js +6 -0
- package/dist/core/storage/pg-storage.js.map +1 -1
- package/dist/core/storage/pg-workstream-store.d.ts +16 -11
- package/dist/core/storage/pg-workstream-store.js +46 -29
- package/dist/core/storage/pg-workstream-store.js.map +1 -1
- package/dist/core/storage/sqlite-code-exemplar-store.d.ts +24 -10
- package/dist/core/storage/sqlite-code-exemplar-store.js +84 -40
- package/dist/core/storage/sqlite-code-exemplar-store.js.map +1 -1
- package/dist/core/storage/sqlite-entity-store.d.ts +8 -1
- package/dist/core/storage/sqlite-entity-store.js +46 -27
- package/dist/core/storage/sqlite-entity-store.js.map +1 -1
- package/dist/core/storage/sqlite-fact-store.d.ts +45 -19
- package/dist/core/storage/sqlite-fact-store.js +172 -64
- package/dist/core/storage/sqlite-fact-store.js.map +1 -1
- package/dist/core/storage/sqlite-learner-store.d.ts +33 -0
- package/dist/core/storage/sqlite-learner-store.js +178 -0
- package/dist/core/storage/sqlite-learner-store.js.map +1 -0
- package/dist/core/storage/sqlite-outcome-store.d.ts +37 -22
- package/dist/core/storage/sqlite-outcome-store.js +90 -37
- package/dist/core/storage/sqlite-outcome-store.js.map +1 -1
- package/dist/core/storage/sqlite-session-store.d.ts +29 -20
- package/dist/core/storage/sqlite-session-store.js +207 -121
- package/dist/core/storage/sqlite-session-store.js.map +1 -1
- package/dist/core/storage/sqlite-signal-store.d.ts +12 -5
- package/dist/core/storage/sqlite-signal-store.js +27 -15
- package/dist/core/storage/sqlite-signal-store.js.map +1 -1
- package/dist/core/storage/sqlite-storage.d.ts +4 -0
- package/dist/core/storage/sqlite-storage.js +10 -2
- package/dist/core/storage/sqlite-storage.js.map +1 -1
- package/dist/core/storage/sqlite-workstream-store.d.ts +16 -11
- package/dist/core/storage/sqlite-workstream-store.js +52 -31
- package/dist/core/storage/sqlite-workstream-store.js.map +1 -1
- package/dist/core/storage/supersedence-log.d.ts +10 -7
- package/dist/core/storage/supersedence-log.js +19 -14
- package/dist/core/storage/supersedence-log.js.map +1 -1
- package/dist/core/tenancy/default-team.d.ts +6 -0
- package/dist/core/tenancy/default-team.js +7 -0
- package/dist/core/tenancy/default-team.js.map +1 -0
- package/dist/core/tenancy/hosted-mode.d.ts +9 -0
- package/dist/core/tenancy/hosted-mode.js +12 -0
- package/dist/core/tenancy/hosted-mode.js.map +1 -0
- package/dist/core/tenancy/team-auth.d.ts +16 -0
- package/dist/core/tenancy/team-auth.js +22 -0
- package/dist/core/tenancy/team-auth.js.map +1 -0
- package/dist/core/tenancy/team-token-store.d.ts +45 -0
- package/dist/core/tenancy/team-token-store.js +52 -0
- package/dist/core/tenancy/team-token-store.js.map +1 -0
- package/dist/core/tenancy/tenant-clause.d.ts +37 -0
- package/dist/core/tenancy/tenant-clause.js +11 -0
- package/dist/core/tenancy/tenant-clause.js.map +1 -0
- package/dist/core/tenancy/tenant-state-path.d.ts +28 -0
- package/dist/core/tenancy/tenant-state-path.js +49 -0
- package/dist/core/tenancy/tenant-state-path.js.map +1 -0
- package/dist/core/wiki/project.d.ts +17 -0
- package/dist/core/wiki/project.js +37 -0
- package/dist/core/wiki/project.js.map +1 -0
- package/dist/core/wiki/reconcile.d.ts +15 -0
- package/dist/core/wiki/reconcile.js +14 -0
- package/dist/core/wiki/reconcile.js.map +1 -0
- package/dist/core/wiki/render.d.ts +5 -0
- package/dist/core/wiki/render.js +84 -0
- package/dist/core/wiki/render.js.map +1 -0
- package/dist/core/wiki/rollup.d.ts +28 -0
- package/dist/core/wiki/rollup.js +91 -0
- package/dist/core/wiki/rollup.js.map +1 -0
- package/dist/core/wiki/select.d.ts +8 -0
- package/dist/core/wiki/select.js +7 -0
- package/dist/core/wiki/select.js.map +1 -0
- package/dist/core/wiki/slug.d.ts +36 -0
- package/dist/core/wiki/slug.js +71 -0
- package/dist/core/wiki/slug.js.map +1 -0
- package/dist/core/wiki/types.d.ts +37 -0
- package/dist/core/wiki/types.js +2 -0
- package/dist/core/wiki/types.js.map +1 -0
- package/dist/core/work-digest/build-work-digest.d.ts +1 -1
- package/dist/core/work-digest/build-work-digest.js +3 -3
- package/dist/core/work-digest/build-work-digest.js.map +1 -1
- package/dist/core/workstream/bind.d.ts +1 -1
- package/dist/core/workstream/bind.js +10 -10
- package/dist/core/workstream/bind.js.map +1 -1
- package/dist/core/workstream/rollup.d.ts +1 -1
- package/dist/core/workstream/rollup.js +6 -6
- package/dist/core/workstream/rollup.js.map +1 -1
- package/dist/hook/hook-helpers.d.ts +14 -0
- package/dist/hook/hook-helpers.js +29 -2
- package/dist/hook/hook-helpers.js.map +1 -1
- package/dist/hook/pre-compact-hook.js +6 -4
- package/dist/hook/pre-compact-hook.js.map +1 -1
- package/dist/hook/prompt-recall-hook.d.ts +5 -2
- package/dist/hook/prompt-recall-hook.js +28 -17
- package/dist/hook/prompt-recall-hook.js.map +1 -1
- package/dist/hook/recall-over-http.d.ts +27 -3
- package/dist/hook/recall-over-http.js +44 -11
- package/dist/hook/recall-over-http.js.map +1 -1
- package/dist/hook/session-end-hook.js +7 -5
- package/dist/hook/session-end-hook.js.map +1 -1
- package/dist/hook/session-start-hook.d.ts +14 -1
- package/dist/hook/session-start-hook.js +55 -15
- package/dist/hook/session-start-hook.js.map +1 -1
- package/dist/hook/stop-hook.js +9 -7
- package/dist/hook/stop-hook.js.map +1 -1
- package/dist/http/app.d.ts +41 -3
- package/dist/http/app.js +355 -96
- package/dist/http/app.js.map +1 -1
- package/dist/install/claude-code.d.ts +17 -0
- package/dist/install/claude-code.js +44 -5
- package/dist/install/claude-code.js.map +1 -1
- package/dist/install/codex.d.ts +23 -2
- package/dist/install/codex.js +111 -14
- package/dist/install/codex.js.map +1 -1
- package/dist/install/cursor.d.ts +2 -2
- package/dist/install/cursor.js +7 -7
- package/dist/install/cursor.js.map +1 -1
- package/dist/install/hermes.d.ts +8 -0
- package/dist/install/hermes.js +28 -5
- package/dist/install/hermes.js.map +1 -1
- package/dist/install/local-source.d.ts +20 -0
- package/dist/install/local-source.js +28 -0
- package/dist/install/local-source.js.map +1 -0
- package/dist/install/mcp-block.d.ts +77 -0
- package/dist/install/mcp-block.js +99 -0
- package/dist/install/mcp-block.js.map +1 -0
- package/dist/install/pi.d.ts +26 -0
- package/dist/install/pi.js +43 -0
- package/dist/install/pi.js.map +1 -1
- package/dist/install/setup.js +6 -0
- package/dist/install/setup.js.map +1 -1
- package/dist/install/windsurf.d.ts +2 -2
- package/dist/install/windsurf.js +7 -7
- package/dist/install/windsurf.js.map +1 -1
- package/dist/llm/classifier-box.d.ts +5 -0
- package/dist/llm/classifier-box.js +5 -0
- package/dist/llm/classifier-box.js.map +1 -1
- package/dist/mcp/server.d.ts +73 -16
- package/dist/mcp/server.js +306 -83
- package/dist/mcp/server.js.map +1 -1
- package/dist/ports/clock.d.ts +12 -0
- package/dist/ports/clock.js +10 -0
- package/dist/ports/clock.js.map +1 -0
- package/dist/ports/code-exemplar-store.d.ts +13 -13
- package/dist/ports/entity-store.d.ts +5 -1
- package/dist/ports/fact-store.d.ts +0 -0
- package/dist/ports/fact-store.js +0 -0
- package/dist/ports/fact-store.js.map +1 -1
- package/dist/ports/learner-store.d.ts +80 -0
- package/dist/ports/learner-store.js +19 -0
- package/dist/ports/learner-store.js.map +1 -0
- package/dist/ports/outcome.d.ts +3 -3
- package/dist/ports/session-store.d.ts +20 -14
- package/dist/ports/session-store.js +5 -0
- package/dist/ports/session-store.js.map +1 -1
- package/dist/ports/signal-store.d.ts +12 -8
- package/dist/ports/spawn-child.d.ts +14 -0
- package/dist/ports/spawn-child.js +10 -0
- package/dist/ports/spawn-child.js.map +1 -0
- package/dist/ports/storage.d.ts +6 -0
- package/dist/ports/transcript-adapter.d.ts +11 -0
- package/dist/ports/wiki-writer.d.ts +19 -0
- package/dist/ports/wiki-writer.js +12 -0
- package/dist/ports/wiki-writer.js.map +1 -0
- package/dist/ports/workstream-store.d.ts +19 -12
- package/dist/shared/types.d.ts +22 -0
- package/dist/shared/types.js.map +1 -1
- package/dist/shared/version.d.ts +9 -0
- package/dist/shared/version.js +11 -0
- package/dist/shared/version.js.map +1 -0
- package/dist/ui/assets/index-D6F3xAis.js +11 -0
- package/dist/ui/index.html +1 -1
- package/migrations/033_actions_kind_check.sql +58 -0
- package/migrations/034_tenancy_teams_and_stamps.sql +40 -0
- package/migrations/035_tenancy_entity_rekey.sql +75 -0
- package/migrations/036_tenant_name_uniqueness.sql +59 -0
- package/migrations/037_sessions_covering_index.sql +25 -0
- package/migrations/038_sources_muse.sql +33 -0
- package/migrations/039_learner_evidence.sql +51 -0
- package/migrations/pg/033_actions_kind_check.sql +17 -0
- package/migrations/pg/034_tenancy.sql +77 -0
- package/migrations/pg/035_tenant_name_uniqueness.sql +15 -0
- package/migrations/pg/036_sources_muse.sql +12 -0
- package/migrations/pg/037_learner_evidence.sql +55 -0
- package/nlm/index.js +246 -55
- package/package.json +10 -5
- package/plugin/.codex-plugin/plugin.json +2 -2
- package/plugin/scripts/prompt-recall-hook.mjs +244 -55
- package/plugin/scripts/session-start-hook.mjs +273 -53
- package/plugin/scripts/stop-hook.mjs +195 -37
- package/dist/ui/assets/index-CSaBtgXD.js +0 -11
|
@@ -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>;
|
|
@@ -0,0 +1,70 @@
|
|
|
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
|
+
function isUnassistedSuccess(a) {
|
|
18
|
+
return a.result === "correct" && a.help === "none";
|
|
19
|
+
}
|
|
20
|
+
export function summarizeConcepts(events) {
|
|
21
|
+
const coveredOn = new Map();
|
|
22
|
+
const attemptsByConcept = new Map();
|
|
23
|
+
for (const event of events) {
|
|
24
|
+
for (const concept of event.covered)
|
|
25
|
+
coveredOn.set(concept, event.date);
|
|
26
|
+
for (const attempt of event.attempts) {
|
|
27
|
+
let list = attemptsByConcept.get(attempt.concept);
|
|
28
|
+
if (!list) {
|
|
29
|
+
list = [];
|
|
30
|
+
attemptsByConcept.set(attempt.concept, list);
|
|
31
|
+
}
|
|
32
|
+
list.push({ attempt, date: event.date });
|
|
33
|
+
if (!coveredOn.has(attempt.concept))
|
|
34
|
+
coveredOn.set(attempt.concept, event.date);
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
const concepts = [...new Set([...coveredOn.keys(), ...attemptsByConcept.keys()])].sort();
|
|
38
|
+
return concepts.map((concept) => {
|
|
39
|
+
const attempts = attemptsByConcept.get(concept) ?? [];
|
|
40
|
+
if (attempts.length === 0) {
|
|
41
|
+
return {
|
|
42
|
+
concept,
|
|
43
|
+
status: "exposed",
|
|
44
|
+
attemptCount: 0,
|
|
45
|
+
latestDate: coveredOn.get(concept) ?? null,
|
|
46
|
+
latestTask: null,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
const latest = attempts[attempts.length - 1];
|
|
50
|
+
let status;
|
|
51
|
+
if (!isUnassistedSuccess(latest.attempt)) {
|
|
52
|
+
status = "practicing";
|
|
53
|
+
}
|
|
54
|
+
else if ((latest.attempt.kind === "retrieval" || latest.attempt.kind === "transfer") &&
|
|
55
|
+
attempts.some((a) => a.date < latest.date && isUnassistedSuccess(a.attempt))) {
|
|
56
|
+
status = "retained";
|
|
57
|
+
}
|
|
58
|
+
else {
|
|
59
|
+
status = "demonstrated";
|
|
60
|
+
}
|
|
61
|
+
return {
|
|
62
|
+
concept,
|
|
63
|
+
status,
|
|
64
|
+
attemptCount: attempts.length,
|
|
65
|
+
latestDate: latest.date,
|
|
66
|
+
latestTask: latest.attempt.task,
|
|
67
|
+
};
|
|
68
|
+
});
|
|
69
|
+
}
|
|
70
|
+
//# sourceMappingURL=summary.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"summary.js","sourceRoot":"","sources":["../../../src/core/learner/summary.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAqBH,SAAS,mBAAmB,CAAC,CAAU;IACrC,OAAO,CAAC,CAAC,MAAM,KAAK,SAAS,IAAI,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC;AACrD,CAAC;AAED,MAAM,UAAU,iBAAiB,CAAC,MAAoC;IACpE,MAAM,SAAS,GAAG,IAAI,GAAG,EAAkB,CAAC;IAC5C,MAAM,iBAAiB,GAAG,IAAI,GAAG,EAA0B,CAAC;IAE5D,KAAK,MAAM,KAAK,IAAI,MAAM,EAAE,CAAC;QAC3B,KAAK,MAAM,OAAO,IAAI,KAAK,CAAC,OAAO;YAAE,SAAS,CAAC,GAAG,CAAC,OAAO,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;QACxE,KAAK,MAAM,OAAO,IAAI,KAAK,CAAC,QAAQ,EAAE,CAAC;YACrC,IAAI,IAAI,GAAG,iBAAiB,CAAC,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC;YAClD,IAAI,CAAC,IAAI,EAAE,CAAC;gBACV,IAAI,GAAG,EAAE,CAAC;gBACV,iBAAiB,CAAC,GAAG,CAAC,OAAO,CAAC,OAAO,EAAE,IAAI,CAAC,CAAC;YAC/C,CAAC;YACD,IAAI,CAAC,IAAI,CAAC,EAAE,OAAO,EAAE,IAAI,EAAE,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;YACzC,IAAI,CAAC,SAAS,CAAC,GAAG,CAAC,OAAO,CAAC,OAAO,CAAC;gBAAE,SAAS,CAAC,GAAG,CAAC,OAAO,CAAC,OAAO,EAAE,KAAK,CAAC,IAAI,CAAC,CAAC;QAClF,CAAC;IACH,CAAC;IAED,MAAM,QAAQ,GAAG,CAAC,GAAG,IAAI,GAAG,CAAC,CAAC,GAAG,SAAS,CAAC,IAAI,EAAE,EAAE,GAAG,iBAAiB,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IACzF,OAAO,QAAQ,CAAC,GAAG,CAAC,CAAC,OAAO,EAAE,EAAE;QAC9B,MAAM,QAAQ,GAAG,iBAAiB,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,EAAE,CAAC;QACtD,IAAI,QAAQ,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;YAC1B,OAAO;gBACL,OAAO;gBACP,MAAM,EAAE,SAAkB;gBAC1B,YAAY,EAAE,CAAC;gBACf,UAAU,EAAE,SAAS,CAAC,GAAG,CAAC,OAAO,CAAC,IAAI,IAAI;gBAC1C,UAAU,EAAE,IAAI;aACjB,CAAC;QACJ,CAAC;QACD,MAAM,MAAM,GAAG,QAAQ,CAAC,QAAQ,CAAC,MAAM,GAAG,CAAC,CAAE,CAAC;QAC9C,IAAI,MAAqB,CAAC;QAC1B,IAAI,CAAC,mBAAmB,CAAC,MAAM,CAAC,OAAO,CAAC,EAAE,CAAC;YACzC,MAAM,GAAG,YAAY,CAAC;QACxB,CAAC;aAAM,IACL,CAAC,MAAM,CAAC,OAAO,CAAC,IAAI,KAAK,WAAW,IAAI,MAAM,CAAC,OAAO,CAAC,IAAI,KAAK,UAAU,CAAC;YAC3E,QAAQ,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,GAAG,MAAM,CAAC,IAAI,IAAI,mBAAmB,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,EAC5E,CAAC;YACD,MAAM,GAAG,UAAU,CAAC;QACtB,CAAC;aAAM,CAAC;YACN,MAAM,GAAG,cAAc,CAAC;QAC1B,CAAC;QACD,OAAO;YACL,OAAO;YACP,MAAM;YACN,YAAY,EAAE,QAAQ,CAAC,MAAM;YAC7B,UAAU,EAAE,MAAM,CAAC,IAAI;YACvB,UAAU,EAAE,MAAM,CAAC,OAAO,CAAC,IAAI;SAChC,CAAC;IACJ,CAAC,CAAC,CAAC;AACL,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Learner evidence validation — port of the Gnos evidence contract
|
|
3
|
+
* (`skills/learner-tracking/scripts/learner_state.py`, MIT, attribution kept).
|
|
4
|
+
*
|
|
5
|
+
* Shape rules only: slug/concept formats, required fields, enum membership,
|
|
6
|
+
* real calendar dates that are never in the future. Cross-row rules
|
|
7
|
+
* (fingerprint match, idempotent retry) live in the store; orchestration in
|
|
8
|
+
* service.ts. All failures throw `Error`; the MCP layer (M3) maps them to
|
|
9
|
+
* tool errors.
|
|
10
|
+
*/
|
|
11
|
+
import type { EnrollmentInput, LearnerInit, LearningEventInput } from "../../ports/learner-store.js";
|
|
12
|
+
export declare function assertValidSlug(value: unknown, label: string): asserts value is string;
|
|
13
|
+
export declare function assertValidConceptId(value: unknown): asserts value is string;
|
|
14
|
+
export declare function assertNonempty(value: unknown, label: string): asserts value is string;
|
|
15
|
+
export declare function assertValidDate(value: unknown, label?: string): asserts value is string;
|
|
16
|
+
export declare function assertValidLearnerInit(input: LearnerInit): void;
|
|
17
|
+
export declare function assertValidEnrollmentInput(input: EnrollmentInput): void;
|
|
18
|
+
export declare function assertValidEventInput(event: LearningEventInput): void;
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Learner evidence validation — port of the Gnos evidence contract
|
|
3
|
+
* (`skills/learner-tracking/scripts/learner_state.py`, MIT, attribution kept).
|
|
4
|
+
*
|
|
5
|
+
* Shape rules only: slug/concept formats, required fields, enum membership,
|
|
6
|
+
* real calendar dates that are never in the future. Cross-row rules
|
|
7
|
+
* (fingerprint match, idempotent retry) live in the store; orchestration in
|
|
8
|
+
* service.ts. All failures throw `Error`; the MCP layer (M3) maps them to
|
|
9
|
+
* tool errors.
|
|
10
|
+
*/
|
|
11
|
+
const SLUG_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/;
|
|
12
|
+
const CONCEPT_PATTERN = /^[a-z][a-z0-9-]*\.[a-z0-9]+(?:[.-][a-z0-9]+)*$/;
|
|
13
|
+
const DATE_PATTERN = /^(\d{4})-(\d{2})-(\d{2})$/;
|
|
14
|
+
const ATTEMPT_RESULTS = ["correct", "partial", "incorrect"];
|
|
15
|
+
const ATTEMPT_HELPS = ["none", "hint", "worked-example"];
|
|
16
|
+
const ATTEMPT_KINDS = ["application", "retrieval", "transfer"];
|
|
17
|
+
export function assertValidSlug(value, label) {
|
|
18
|
+
if (typeof value !== "string" || value.length > 80 || !SLUG_PATTERN.test(value)) {
|
|
19
|
+
throw new Error(`learner validation: ${label} must be a lowercase slug of at most 80 characters`);
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
export function assertValidConceptId(value) {
|
|
23
|
+
if (typeof value !== "string" || !CONCEPT_PATTERN.test(value)) {
|
|
24
|
+
throw new Error(`learner validation: invalid concept ID: ${JSON.stringify(value)}`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export function assertNonempty(value, label) {
|
|
28
|
+
if (typeof value !== "string" || value.trim().length === 0) {
|
|
29
|
+
throw new Error(`learner validation: ${label} must be nonempty text`);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
export function assertValidDate(value, label = "event.date") {
|
|
33
|
+
if (typeof value !== "string") {
|
|
34
|
+
throw new Error(`learner validation: ${label} must be YYYY-MM-DD`);
|
|
35
|
+
}
|
|
36
|
+
const match = DATE_PATTERN.exec(value);
|
|
37
|
+
if (!match)
|
|
38
|
+
throw new Error(`learner validation: ${label} must be YYYY-MM-DD`);
|
|
39
|
+
const year = Number(match[1]);
|
|
40
|
+
const month = Number(match[2]);
|
|
41
|
+
const day = Number(match[3]);
|
|
42
|
+
const dt = new Date(Date.UTC(year, month - 1, day));
|
|
43
|
+
if (dt.getUTCFullYear() !== year || dt.getUTCMonth() !== month - 1 || dt.getUTCDate() !== day) {
|
|
44
|
+
throw new Error(`learner validation: ${label} is not a real calendar date: ${value}`);
|
|
45
|
+
}
|
|
46
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
47
|
+
if (value > today) {
|
|
48
|
+
throw new Error(`learner validation: ${label} cannot be in the future: ${value}`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
export function assertValidLearnerInit(input) {
|
|
52
|
+
assertValidSlug(input.id, "learner.id");
|
|
53
|
+
assertNonempty(input.label, "learner.label");
|
|
54
|
+
}
|
|
55
|
+
export function assertValidEnrollmentInput(input) {
|
|
56
|
+
assertValidSlug(input.learnerId, "enrollment.learnerId");
|
|
57
|
+
assertValidSlug(input.courseId, "enrollment.courseId");
|
|
58
|
+
assertNonempty(input.workstreamId, "enrollment.workstreamId");
|
|
59
|
+
assertNonempty(input.planRef, "enrollment.planRef");
|
|
60
|
+
assertNonempty(input.planFingerprint, "enrollment.planFingerprint");
|
|
61
|
+
if (!Number.isInteger(input.planRevision) || input.planRevision < 1) {
|
|
62
|
+
throw new Error("learner validation: enrollment.planRevision must be a positive integer");
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
export function assertValidEventInput(event) {
|
|
66
|
+
assertValidSlug(event.id, "event.id");
|
|
67
|
+
assertValidDate(event.date);
|
|
68
|
+
if (!Array.isArray(event.covered)) {
|
|
69
|
+
throw new Error("learner validation: event.covered must be a list");
|
|
70
|
+
}
|
|
71
|
+
for (const concept of event.covered)
|
|
72
|
+
assertValidConceptId(concept);
|
|
73
|
+
assertNonempty(event.nextStep, "event.nextStep");
|
|
74
|
+
if (typeof event.interpretation !== "string") {
|
|
75
|
+
throw new Error("learner validation: event.interpretation must be text (may be empty)");
|
|
76
|
+
}
|
|
77
|
+
if (!Array.isArray(event.attempts)) {
|
|
78
|
+
throw new Error("learner validation: event.attempts must be a list");
|
|
79
|
+
}
|
|
80
|
+
for (const attempt of event.attempts) {
|
|
81
|
+
assertValidConceptId(attempt.concept);
|
|
82
|
+
assertNonempty(attempt.task, "attempt.task");
|
|
83
|
+
assertNonempty(attempt.response, "attempt.response");
|
|
84
|
+
if (!ATTEMPT_RESULTS.includes(attempt.result)) {
|
|
85
|
+
throw new Error(`learner validation: attempt.result must be one of ${ATTEMPT_RESULTS.join(", ")}`);
|
|
86
|
+
}
|
|
87
|
+
if (!ATTEMPT_HELPS.includes(attempt.help)) {
|
|
88
|
+
throw new Error(`learner validation: attempt.help must be one of ${ATTEMPT_HELPS.join(", ")}`);
|
|
89
|
+
}
|
|
90
|
+
if (!ATTEMPT_KINDS.includes(attempt.kind)) {
|
|
91
|
+
throw new Error(`learner validation: attempt.kind must be one of ${ATTEMPT_KINDS.join(", ")}`);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
//# sourceMappingURL=validate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"validate.js","sourceRoot":"","sources":["../../../src/core/learner/validate.ts"],"names":[],"mappings":"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"}
|
|
@@ -31,8 +31,25 @@ export interface ReDerivationPair {
|
|
|
31
31
|
export interface ReDerivationReport {
|
|
32
32
|
readonly rate: number;
|
|
33
33
|
readonly pairs: ReadonlyArray<ReDerivationPair>;
|
|
34
|
-
/**
|
|
34
|
+
/**
|
|
35
|
+
* Denominator of the legacy pair-space rate: entity-sharing session pairs
|
|
36
|
+
* where BOTH sides carry at least one decision. A pair with a decisionless
|
|
37
|
+
* side cannot be a re-derivation by construction, and counting it inflated
|
|
38
|
+
* this figure roughly 3x - measured 2026-08-04 on the live 90-day window,
|
|
39
|
+
* 1,266,809 of 1,905,497 eligible pairs (66.5%) had a decisionless side.
|
|
40
|
+
*/
|
|
35
41
|
readonly eligible: number;
|
|
42
|
+
/** Sessions in the window carrying at least one decision. Linear in corpus size. */
|
|
43
|
+
readonly decisionBearingSessions: number;
|
|
44
|
+
/** Distinct later-side sessions appearing in `pairs`. Counted once each. */
|
|
45
|
+
readonly reDerivingSessions: number;
|
|
46
|
+
/**
|
|
47
|
+
* reDerivingSessions / decisionBearingSessions. This is the trendable metric:
|
|
48
|
+
* the pair-space `rate` divides by a denominator that grows quadratically with
|
|
49
|
+
* the corpus, so it mechanically falls the more the product is used, which is
|
|
50
|
+
* backwards for a number whose job is to show improvement.
|
|
51
|
+
*/
|
|
52
|
+
readonly sessionRate: number;
|
|
36
53
|
}
|
|
37
54
|
interface SqliteLike {
|
|
38
55
|
prepare(sql: string): {
|
|
@@ -42,7 +59,19 @@ interface SqliteLike {
|
|
|
42
59
|
/**
|
|
43
60
|
* SQLite-backed deps for the CLI readout. Reads sessions started within the
|
|
44
61
|
* window along with their entity links and decision markers, plus all edges.
|
|
62
|
+
* `tenantId` scopes the sessions SELECT via tenantClause — the pairs this
|
|
63
|
+
* produces get persisted to a tenant-derived cache file (see
|
|
64
|
+
* `@core/storage/sqlite-outcome-store.js`), so an un-tenanted scan here would
|
|
65
|
+
* leak every tenant's sessions into whichever tenant's file reads it back.
|
|
45
66
|
*/
|
|
46
|
-
export declare function sqliteReDerivationDeps(db: SqliteLike): ReDerivationDeps;
|
|
67
|
+
export declare function sqliteReDerivationDeps(db: SqliteLike, tenantId: string): ReDerivationDeps;
|
|
68
|
+
/**
|
|
69
|
+
* Parses the on-disk cache the 24h corpus-monitor job writes to
|
|
70
|
+
* (`~/.nlm/re-derivation-pairs.json`, see `src/cli/nlm.ts`) and the SQLite
|
|
71
|
+
* outcome adapters read back (`@core/storage/sqlite-outcome-store.js`).
|
|
72
|
+
* The file is a plain `ReDerivationPair[]` - anything else (corrupt JSON,
|
|
73
|
+
* a non-array shape) is treated as no pairs rather than thrown.
|
|
74
|
+
*/
|
|
75
|
+
export declare function parseReDerivationPairsFile(raw: string): ReadonlyArray<ReDerivationPair>;
|
|
47
76
|
export declare function computeReDerivationRate(deps: ReDerivationDeps, windowDays: number): Promise<ReDerivationReport>;
|
|
48
77
|
export {};
|
|
@@ -7,6 +7,7 @@
|
|
|
7
7
|
* entity, sit more than GAP_DAYS apart, have decision text overlapping at or
|
|
8
8
|
* above JACCARD_FLOOR, and have no `continues`/`supersedes` edge linking them.
|
|
9
9
|
*/
|
|
10
|
+
import { tenantClause } from "../tenancy/tenant-clause.js";
|
|
10
11
|
const JACCARD_FLOOR = 0.5;
|
|
11
12
|
const GAP_DAYS = 7;
|
|
12
13
|
const MS_PER_DAY = 86_400_000;
|
|
@@ -28,16 +29,21 @@ function jaccard(a, b) {
|
|
|
28
29
|
/**
|
|
29
30
|
* SQLite-backed deps for the CLI readout. Reads sessions started within the
|
|
30
31
|
* window along with their entity links and decision markers, plus all edges.
|
|
32
|
+
* `tenantId` scopes the sessions SELECT via tenantClause — the pairs this
|
|
33
|
+
* produces get persisted to a tenant-derived cache file (see
|
|
34
|
+
* `@core/storage/sqlite-outcome-store.js`), so an un-tenanted scan here would
|
|
35
|
+
* leak every tenant's sessions into whichever tenant's file reads it back.
|
|
31
36
|
*/
|
|
32
|
-
export function sqliteReDerivationDeps(db) {
|
|
37
|
+
export function sqliteReDerivationDeps(db, tenantId) {
|
|
38
|
+
const tc = tenantClause(tenantId);
|
|
33
39
|
return {
|
|
34
40
|
async listSessionsWithin(windowDays) {
|
|
35
41
|
const rows = db
|
|
36
42
|
.prepare(`SELECT id, started_at AS startedAt
|
|
37
43
|
FROM sessions
|
|
38
|
-
WHERE started_at >= datetime('now', ?)
|
|
44
|
+
WHERE started_at >= datetime('now', ?) AND ${tc.sql}
|
|
39
45
|
ORDER BY started_at ASC`)
|
|
40
|
-
.all(`-${windowDays} days
|
|
46
|
+
.all(`-${windowDays} days`, tc.param);
|
|
41
47
|
const entStmt = db.prepare("SELECT entity_canonical AS e FROM session_entities WHERE session_id = ?");
|
|
42
48
|
const decStmt = db.prepare("SELECT text AS t FROM markers WHERE session_id = ? AND kind = 'decision' ORDER BY position ASC");
|
|
43
49
|
return rows.map((r) => ({
|
|
@@ -54,6 +60,17 @@ export function sqliteReDerivationDeps(db) {
|
|
|
54
60
|
},
|
|
55
61
|
};
|
|
56
62
|
}
|
|
63
|
+
/**
|
|
64
|
+
* Parses the on-disk cache the 24h corpus-monitor job writes to
|
|
65
|
+
* (`~/.nlm/re-derivation-pairs.json`, see `src/cli/nlm.ts`) and the SQLite
|
|
66
|
+
* outcome adapters read back (`@core/storage/sqlite-outcome-store.js`).
|
|
67
|
+
* The file is a plain `ReDerivationPair[]` - anything else (corrupt JSON,
|
|
68
|
+
* a non-array shape) is treated as no pairs rather than thrown.
|
|
69
|
+
*/
|
|
70
|
+
export function parseReDerivationPairsFile(raw) {
|
|
71
|
+
const parsed = JSON.parse(raw);
|
|
72
|
+
return Array.isArray(parsed) ? parsed : [];
|
|
73
|
+
}
|
|
57
74
|
export async function computeReDerivationRate(deps, windowDays) {
|
|
58
75
|
const sessions = await deps.listSessionsWithin(windowDays);
|
|
59
76
|
const edges = await deps.listEdges();
|
|
@@ -61,11 +78,16 @@ export async function computeReDerivationRate(deps, windowDays) {
|
|
|
61
78
|
.filter((e) => e.kind === "continues" || e.kind === "supersedes")
|
|
62
79
|
.map((e) => [e.from_session, e.to_session].sort().join("|")));
|
|
63
80
|
const pairs = [];
|
|
81
|
+
const reDeriving = new Set();
|
|
64
82
|
let eligible = 0;
|
|
65
83
|
for (let i = 0; i < sessions.length; i++) {
|
|
66
84
|
for (let j = i + 1; j < sessions.length; j++) {
|
|
67
85
|
const s = sessions[i];
|
|
68
86
|
const t = sessions[j];
|
|
87
|
+
// A decisionless side can never be a re-derivation, so such a pair does
|
|
88
|
+
// not belong in the denominator at all.
|
|
89
|
+
if (s.decisions.length === 0 || t.decisions.length === 0)
|
|
90
|
+
continue;
|
|
69
91
|
const shared = s.entities.filter((e) => t.entities.includes(e));
|
|
70
92
|
if (shared.length === 0)
|
|
71
93
|
continue;
|
|
@@ -79,9 +101,19 @@ export async function computeReDerivationRate(deps, windowDays) {
|
|
|
79
101
|
const jac = jaccard(s.decisions, t.decisions);
|
|
80
102
|
if (jac >= JACCARD_FLOOR) {
|
|
81
103
|
pairs.push({ a: s.id, b: t.id, sharedEntities: shared, jaccard: round2(jac) });
|
|
104
|
+
const later = new Date(t.startedAt).getTime() >= new Date(s.startedAt).getTime() ? t.id : s.id;
|
|
105
|
+
reDeriving.add(later);
|
|
82
106
|
}
|
|
83
107
|
}
|
|
84
108
|
}
|
|
85
|
-
|
|
109
|
+
const decisionBearingSessions = sessions.filter((s) => s.decisions.length > 0).length;
|
|
110
|
+
return {
|
|
111
|
+
rate: eligible ? pairs.length / eligible : 0,
|
|
112
|
+
pairs,
|
|
113
|
+
eligible,
|
|
114
|
+
decisionBearingSessions,
|
|
115
|
+
reDerivingSessions: reDeriving.size,
|
|
116
|
+
sessionRate: decisionBearingSessions ? reDeriving.size / decisionBearingSessions : 0,
|
|
117
|
+
};
|
|
86
118
|
}
|
|
87
119
|
//# sourceMappingURL=re-derivation.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"re-derivation.js","sourceRoot":"","sources":["../../../src/core/metrics/re-derivation.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;
|
|
1
|
+
{"version":3,"file":"re-derivation.js","sourceRoot":"","sources":["../../../src/core/metrics/re-derivation.ts"],"names":[],"mappings":"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"}
|
|
@@ -30,4 +30,4 @@ export interface OutcomeCoverage {
|
|
|
30
30
|
readonly reDerivedLater: number;
|
|
31
31
|
readonly unobserved: number;
|
|
32
32
|
}
|
|
33
|
-
export declare function computeOutcomeCoverage(input: OutcomeCoverageInput): Promise<OutcomeCoverage>;
|
|
33
|
+
export declare function computeOutcomeCoverage(tenantId: string, input: OutcomeCoverageInput): Promise<OutcomeCoverage>;
|
|
@@ -11,10 +11,10 @@
|
|
|
11
11
|
* the precedence logic itself is never duplicated, only its I/O is batched.
|
|
12
12
|
*/
|
|
13
13
|
import { deriveOutcome } from "./rollup.js";
|
|
14
|
-
export async function computeOutcomeCoverage(input) {
|
|
14
|
+
export async function computeOutcomeCoverage(tenantId, input) {
|
|
15
15
|
const counts = { held: 0, overturned: 0, builtUpon: 0, reDerivedLater: 0, unobserved: 0 };
|
|
16
16
|
for (const session of input.sessions) {
|
|
17
|
-
const verdict = await deriveOutcome(session.id, {
|
|
17
|
+
const verdict = await deriveOutcome(tenantId, session.id, {
|
|
18
18
|
sessions: { getById: async () => session },
|
|
19
19
|
signals: { listForSession: async () => input.signalsBySession.get(session.id) ?? [] },
|
|
20
20
|
edges: { listForSession: async () => input.edgesBySession.get(session.id) ?? [] },
|