nlm-memory 0.20.1 → 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 +69 -12
- package/assets/replay-eval-dark.svg +26 -0
- package/assets/replay-eval-light.svg +26 -0
- package/dist/cli/digest.js +69 -4
- package/dist/cli/digest.js.map +1 -1
- package/dist/cli/nlm.d.ts +14 -0
- package/dist/cli/nlm.js +556 -71
- 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-backfill.d.ts +34 -0
- package/dist/cli/scope-backfill.js +237 -0
- package/dist/cli/scope-backfill.js.map +1 -0
- package/dist/cli/scope-coverage.d.ts +44 -0
- package/dist/cli/scope-coverage.js +208 -0
- package/dist/cli/scope-coverage.js.map +1 -0
- 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/claude-code.d.ts +14 -0
- package/dist/core/adapters/claude-code.js +30 -0
- package/dist/core/adapters/claude-code.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 +46 -13
- package/dist/core/dataset/build-dataset.js.map +1 -1
- package/dist/core/digest/compose.d.ts +11 -3
- package/dist/core/digest/compose.js +70 -17
- package/dist/core/digest/compose.js.map +1 -1
- package/dist/core/digest/hook-injection.d.ts +43 -0
- package/dist/core/digest/hook-injection.js +87 -0
- package/dist/core/digest/hook-injection.js.map +1 -0
- 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 +2 -1
- package/dist/core/exemplars/capture-from-session.js +4 -4
- 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/extract-exemplar.d.ts +2 -0
- package/dist/core/exemplars/extract-exemplar.js +7 -1
- package/dist/core/exemplars/extract-exemplar.js.map +1 -1
- package/dist/core/exemplars/ingest-exemplar.d.ts +1 -0
- package/dist/core/exemplars/ingest-exemplar.js +1 -0
- package/dist/core/exemplars/ingest-exemplar.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/claude-settings.d.ts +1 -1
- package/dist/core/hook/claude-settings.js +0 -1
- package/dist/core/hook/claude-settings.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/backfill-derivables.d.ts +92 -0
- package/dist/core/ingest/backfill-derivables.js +175 -0
- package/dist/core/ingest/backfill-derivables.js.map +1 -0
- package/dist/core/ingest/ingest-session.d.ts +1 -1
- package/dist/core/ingest/ingest-session.js +23 -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 +8 -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 +7 -2
- package/dist/core/ingest/reprocess.js.map +1 -1
- package/dist/core/ingest/transcript-derivables.d.ts +27 -0
- package/dist/core/ingest/transcript-derivables.js +138 -0
- package/dist/core/ingest/transcript-derivables.js.map +1 -0
- 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 +33 -0
- package/dist/core/outcome/coverage.js +46 -0
- package/dist/core/outcome/coverage.js.map +1 -0
- package/dist/core/outcome/rollup.d.ts +33 -0
- package/dist/core/outcome/rollup.js +113 -0
- package/dist/core/outcome/rollup.js.map +1 -0
- 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 +7 -4
- package/dist/core/recall/hook-recall-log.js +14 -8
- 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 +13 -12
- package/dist/core/recall-facts/fact-recall-service.js.map +1 -1
- package/dist/core/scheduler/scan-once.js +15 -12
- 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 +45 -10
- package/dist/core/scheduler/scheduler.js.map +1 -1
- package/dist/core/scope/alias-map.d.ts +10 -0
- package/dist/core/scope/alias-map.js +41 -0
- package/dist/core/scope/alias-map.js.map +1 -0
- package/dist/core/scope/derive-scope.d.ts +8 -0
- package/dist/core/scope/derive-scope.js +54 -0
- package/dist/core/scope/derive-scope.js.map +1 -0
- package/dist/core/scope/scope-clause.d.ts +16 -0
- package/dist/core/scope/scope-clause.js +21 -0
- package/dist/core/scope/scope-clause.js.map +1 -0
- package/dist/core/scope/stamp-flag.d.ts +1 -0
- package/dist/core/scope/stamp-flag.js +4 -0
- package/dist/core/scope/stamp-flag.js.map +1 -0
- package/dist/core/signals/code-signal.d.ts +1 -0
- package/dist/core/signals/code-signal.js +1 -0
- package/dist/core/signals/code-signal.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/ingest-signal.d.ts +1 -1
- package/dist/core/signals/ingest-signal.js +3 -1
- package/dist/core/signals/ingest-signal.js.map +1 -1
- package/dist/core/signals/stamp-scope.d.ts +18 -0
- package/dist/core/signals/stamp-scope.js +28 -0
- package/dist/core/signals/stamp-scope.js.map +1 -0
- 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 +62 -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 +25 -21
- package/dist/core/storage/pg-session-store.js +186 -114
- 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 +26 -13
- 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 +17 -11
- package/dist/core/storage/pg-workstream-store.js +47 -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 +86 -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 +175 -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 +104 -0
- package/dist/core/storage/sqlite-outcome-store.js +211 -0
- package/dist/core/storage/sqlite-outcome-store.js.map +1 -0
- package/dist/core/storage/sqlite-session-store.d.ts +50 -21
- package/dist/core/storage/sqlite-session-store.js +233 -149
- 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 +29 -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 +17 -11
- package/dist/core/storage/sqlite-workstream-store.js +53 -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 +21 -14
- package/dist/core/storage/supersedence-log.js.map +1 -1
- package/dist/core/telemetry/probe-filter.d.ts +35 -0
- package/dist/core/telemetry/probe-filter.js +56 -0
- package/dist/core/telemetry/probe-filter.js.map +1 -0
- 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/update-check/check.d.ts +6 -0
- package/dist/core/update-check/check.js +35 -0
- package/dist/core/update-check/check.js.map +1 -1
- 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 +3 -2
- package/dist/core/workstream/bind.js +29 -10
- package/dist/core/workstream/bind.js.map +1 -1
- package/dist/core/workstream/model.d.ts +1 -0
- package/dist/core/workstream/model.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 +8 -5
- package/dist/hook/pre-compact-hook.js.map +1 -1
- package/dist/hook/prompt-recall-hook.d.ts +7 -3
- package/dist/hook/prompt-recall-hook.js +35 -20
- package/dist/hook/prompt-recall-hook.js.map +1 -1
- package/dist/hook/recall-over-http.d.ts +28 -3
- package/dist/hook/recall-over-http.js +58 -12
- 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 +57 -16
- package/dist/hook/session-start-hook.js.map +1 -1
- package/dist/hook/stop-hook.js +11 -8
- package/dist/hook/stop-hook.js.map +1 -1
- package/dist/http/app.d.ts +47 -3
- package/dist/http/app.js +379 -132
- 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 +94 -15
- package/dist/mcp/server.js +402 -79
- 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 +85 -0
- package/dist/ports/outcome.js +11 -0
- package/dist/ports/outcome.js.map +1 -0
- package/dist/ports/session-store.d.ts +20 -20
- 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 +20 -12
- package/dist/shared/net.d.ts +3 -0
- package/dist/shared/net.js +4 -0
- package/dist/shared/net.js.map +1 -0
- package/dist/shared/types.d.ts +55 -0
- package/dist/shared/types.js +4 -1
- 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/029_project_scope.sql +21 -0
- package/migrations/032_session_derivables.sql +11 -0
- 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/030_project_scope.sql +23 -0
- package/migrations/pg/031_widen_session_edges_kind.sql +11 -0
- package/migrations/pg/032_session_derivables.sql +17 -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 +258 -59
- package/package.json +10 -5
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/plugin/scripts/prompt-recall-hook.mjs +256 -58
- package/plugin/scripts/session-start-hook.mjs +283 -55
- package/plugin/scripts/stop-hook.mjs +199 -38
- package/dist/hook/subagent-start-hook.d.ts +0 -28
- package/dist/hook/subagent-start-hook.js +0 -81
- package/dist/hook/subagent-start-hook.js.map +0 -1
- package/dist/ui/assets/index-CMpQkbQy.js +0 -11
package/dist/cli/nlm.js
CHANGED
|
@@ -22,12 +22,13 @@
|
|
|
22
22
|
* nlm disconnect claude-code — remove MCP block from ~/.mcp.json
|
|
23
23
|
* nlm disconnect codex — remove Codex plugin
|
|
24
24
|
* nlm digest — print a daily-activity digest (or --telegram to post)
|
|
25
|
+
* nlm reconcile-facts — retire live facts contradicting an authorities file
|
|
25
26
|
* nlm init - print (or write) the agent recall contract snippet
|
|
26
27
|
*/
|
|
27
28
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
28
29
|
import { dirname, resolve, join } from "node:path";
|
|
29
30
|
import { homedir } from "node:os";
|
|
30
|
-
import { mkdirSync, readFileSync, writeFileSync, existsSync, rmSync, copyFileSync, realpathSync, appendFileSync } from "node:fs";
|
|
31
|
+
import { mkdirSync, readFileSync, writeFileSync, existsSync, rmSync, copyFileSync, realpathSync, appendFileSync, renameSync } from "node:fs";
|
|
31
32
|
import { execFileSync, spawn, spawnSync } from "node:child_process";
|
|
32
33
|
import { Command } from "commander";
|
|
33
34
|
import pkg from "../../package.json" with { type: "json" };
|
|
@@ -39,6 +40,7 @@ import { ProviderRegistry } from "../core/providers/provider-registry.js";
|
|
|
39
40
|
import { SqliteStorage } from "../core/storage/sqlite-storage.js";
|
|
40
41
|
import { PgStorage } from "../core/storage/pg-storage.js";
|
|
41
42
|
import { applyPendingRestore, stageRestore } from "../core/storage/db-restore.js";
|
|
43
|
+
import { applyRetirements, findContradictions, formatReport, loadAuthorities, } from "../core/facts/reconcile-facts.js";
|
|
42
44
|
import { listBackupDates, resolveBackup, runRollingBackup } from "../core/storage/backup-rotation.js";
|
|
43
45
|
import { createApp } from "../http/app.js";
|
|
44
46
|
import { createMcpServer, listMergeSuggestionsHandler, mergeWorkstreamsHandler, rebindSessionHandler, recallWorkstreamHandler, renameWorkstreamHandler, retireWorkstreamHandler } from "../mcp/server.js";
|
|
@@ -50,29 +52,47 @@ import { resolveEmbedderInfo } from "../llm/embedder-info.js";
|
|
|
50
52
|
import { autoloadEnv } from "../llm/env-autoload.js";
|
|
51
53
|
import { buildEmbedder as _buildEmbedder } from "../llm/build-embedder.js";
|
|
52
54
|
import { addHook, buildHookCommand, removeHook } from "../core/hook/claude-settings.js";
|
|
53
|
-
import { codexBinaryAvailable, connectCodex, disconnectCodex, repairCodex, pluginScriptsDir, } from "../install/codex.js";
|
|
54
|
-
import { connectClaudeCode, disconnectClaudeCode, installClaudeCodeHooks, mcpConfigPath } from "../install/claude-code.js";
|
|
55
|
+
import { codexBinaryAvailable, codexHasEffectiveMcpServer, connectCodex, disconnectCodex, repairCodex, pluginScriptsDir, } from "../install/codex.js";
|
|
56
|
+
import { connectClaudeCode, disconnectClaudeCode, installClaudeCodeHooks, mcpConfigPath, nvmPinnedHookWarning } from "../install/claude-code.js";
|
|
55
57
|
import { evaluateInstallHealth, evaluateModelHealth, evaluateRecallSmoke } from "../install/health.js";
|
|
56
58
|
import { codexConfigPath } from "../install/codex.js";
|
|
57
59
|
import { hardenNlmDirPermissions } from "../install/nlm-dir-perms.js";
|
|
58
60
|
import { embeddingModelPresent, ensureMcpToken, ollamaModelPresent } from "../install/ollama.js";
|
|
59
61
|
import { connectCursor, disconnectCursor } from "../install/cursor.js";
|
|
62
|
+
import { connectLocalSource } from "../install/local-source.js";
|
|
63
|
+
import { defaultDbPath as openCodeDefaultDbPath } from "../core/adapters/opencode.js";
|
|
64
|
+
import { defaultSessionsPath as museDefaultSessionsPath } from "../core/adapters/muse.js";
|
|
60
65
|
import { describeRemove, describeUpsert, installCursorRules, installOpencodeRules, installWindsurfRules, uninstallCursorRules, uninstallOpencodeRules, uninstallWindsurfRules, } from "../install/rules-install.js";
|
|
61
66
|
import { runSupersedeCommand } from "./supersede.js";
|
|
62
67
|
import { runInitCommand } from "./init.js";
|
|
68
|
+
import { DEFAULT_TEAM_ID } from "../core/tenancy/default-team.js";
|
|
69
|
+
import { hashTeamToken } from "../core/tenancy/team-auth.js";
|
|
70
|
+
import { runScopeBackfill, formatBackfillResult } from "./scope-backfill.js";
|
|
71
|
+
import { runScopeCoverage, formatCoverageResult } from "./scope-coverage.js";
|
|
72
|
+
import { loadAliasMap } from "../core/scope/alias-map.js";
|
|
63
73
|
import { getUpdateStatus } from "../core/update-check/check.js";
|
|
74
|
+
import { checkDriftAndAlert, checkEmbedderAndAlert } from "../core/alerts/check-and-alert.js";
|
|
75
|
+
import { fireAlert } from "../core/alerts/fire-alert.js";
|
|
76
|
+
import { buildJobAlertEvent } from "../core/alerts/job-alert.js";
|
|
77
|
+
import { buildWikiDriftEvent, buildWikiFailureEvent } from "../core/alerts/wiki-alert.js";
|
|
78
|
+
import { projectWiki } from "../core/wiki/project.js";
|
|
79
|
+
import { FsWikiWriter } from "../core/adapters/fs-wiki-writer.js";
|
|
80
|
+
import { JobSupervisor } from "../core/jobs/job-supervisor.js";
|
|
81
|
+
import { realClock, createReprocessSpawnChild } from "./reprocess-job-adapters.js";
|
|
64
82
|
import { connectHermes, disconnectHermes, hermesConfigPath } from "../install/hermes.js";
|
|
65
83
|
import { connectHermesAgent, disconnectHermesAgent, hermesAgentPluginDir } from "../install/hermes-agent.js";
|
|
66
84
|
import { connectWindsurf, disconnectWindsurf } from "../install/windsurf.js";
|
|
67
|
-
import { connectPi, disconnectPi, piSettingsPath } from "../install/pi.js";
|
|
85
|
+
import { connectPi, connectPiMcp, disconnectPi, piSettingsPath } from "../install/pi.js";
|
|
68
86
|
import { runSetup } from "../install/setup.js";
|
|
69
87
|
import { runParity } from "./classify-parity.js";
|
|
70
88
|
import { reembedCorpus } from "../core/embedding/embed-backfill.js";
|
|
71
89
|
import { reembedCorpusPg } from "../core/embedding/pg-embed-backfill.js";
|
|
72
90
|
import { backfillExemplarEmbeddings } from "../core/exemplars/embed-backfill.js";
|
|
91
|
+
import { backfillFactEmbeddings } from "../core/facts/embed-backfill.js";
|
|
73
92
|
import { warmCodeEmbedder } from "../core/exemplars/warm-embedder.js";
|
|
74
|
-
import { markWarm } from "../core/health/warmup-state.js";
|
|
93
|
+
import { markWarm, markWarmFailure } from "../core/health/warmup-state.js";
|
|
75
94
|
import { setLaneHealth } from "../core/health/embedding-lane-state.js";
|
|
95
|
+
import { retryUntilWarm } from "../core/health/warmup-retry.js";
|
|
76
96
|
import { setCorpusSnapshot, corpusSnapshot } from "../core/health/corpus-state.js";
|
|
77
97
|
import { computeCorpusStats, sqliteCorpusStatsDeps, parseCorpusThresholds, thresholdState } from "../core/metrics/corpus-stats.js";
|
|
78
98
|
import { computeReDerivationRate, sqliteReDerivationDeps } from "../core/metrics/re-derivation.js";
|
|
@@ -94,6 +114,7 @@ import { runChecksOnSqlite, runChecksOnPg, applyFixOnSqlite, applyFixOnPg } from
|
|
|
94
114
|
import { normalizeLabel } from "../core/workstream/model.js";
|
|
95
115
|
import { resolveWorkstreamId } from "../core/workstream/resolve.js";
|
|
96
116
|
import { suggestMerges } from "../core/entities/dedup-suggest.js";
|
|
117
|
+
import { DEFAULT_NLM_PORT } from "../shared/net.js";
|
|
97
118
|
const __filename = fileURLToPath(import.meta.url);
|
|
98
119
|
const __dirname = dirname(__filename);
|
|
99
120
|
const MIGRATIONS_DIR = resolve(__dirname, "../../migrations");
|
|
@@ -101,7 +122,7 @@ const CLASSIFIER_FIXTURES_DIR = resolve(__dirname, "../../fixtures/classifier-go
|
|
|
101
122
|
const PG_MIGRATIONS_DIR = join(fileURLToPath(new URL(".", import.meta.url)), "../../migrations/pg");
|
|
102
123
|
const UI_DIST = resolve(__dirname, "../../dist/ui");
|
|
103
124
|
const DEFAULT_DB_PATH = resolve(homedir(), ".nlm/canonical.sqlite");
|
|
104
|
-
const DEFAULT_PORT =
|
|
125
|
+
const DEFAULT_PORT = Number.parseInt(DEFAULT_NLM_PORT, 10);
|
|
105
126
|
function dbPath() {
|
|
106
127
|
return process.env["NLM_DB_PATH"] ?? DEFAULT_DB_PATH;
|
|
107
128
|
}
|
|
@@ -114,6 +135,10 @@ function port() {
|
|
|
114
135
|
return DEFAULT_PORT;
|
|
115
136
|
return n;
|
|
116
137
|
}
|
|
138
|
+
function positiveIntEnv(name, fallback) {
|
|
139
|
+
const parsed = Number.parseInt(process.env[name] ?? "", 10);
|
|
140
|
+
return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
|
|
141
|
+
}
|
|
117
142
|
function ollamaUrl() {
|
|
118
143
|
return process.env["NLM_OLLAMA_URL"] ?? "http://localhost:11434";
|
|
119
144
|
}
|
|
@@ -146,6 +171,28 @@ export function shouldAppendTrend(lastLineTs, now) {
|
|
|
146
171
|
return true;
|
|
147
172
|
return now - parsed >= TREND_INTERVAL_MS;
|
|
148
173
|
}
|
|
174
|
+
/**
|
|
175
|
+
* Recomputes `computeReDerivationRate` and overwrites the pairs cache file
|
|
176
|
+
* (#405) that `@core/storage/sqlite-outcome-store.js` reads instead of
|
|
177
|
+
* recomputing the same O(n^2) scan inline. Called every 24h corpus-monitor
|
|
178
|
+
* cycle (not gated by `shouldAppendTrend`, which only paces the separate
|
|
179
|
+
* append-only trend log) so the cache stays ~24h fresh while the monitor
|
|
180
|
+
* runs. Exported for direct testing and smoke runs against a corpus copy.
|
|
181
|
+
* `tenantId` scopes both the session scan (sqliteReDerivationDeps) and the
|
|
182
|
+
* cache file this write targets — two tenants must never compute or read
|
|
183
|
+
* each other's pairs.
|
|
184
|
+
*/
|
|
185
|
+
export async function persistReDerivationPairs(rawDb, tenantId, pairsPath, windowDays) {
|
|
186
|
+
const deps = sqliteReDerivationDeps(rawDb, tenantId);
|
|
187
|
+
const report = await computeReDerivationRate(deps, windowDays);
|
|
188
|
+
// Write-temp-then-rename: the outcome adapters read this file on every
|
|
189
|
+
// get_session, so a crash mid-write must never leave truncated JSON at
|
|
190
|
+
// the final path. rename is atomic within the same directory.
|
|
191
|
+
const tmpPath = `${pairsPath}.tmp-${process.pid}-${Date.now()}`;
|
|
192
|
+
writeFileSync(tmpPath, JSON.stringify(report.pairs), "utf8");
|
|
193
|
+
renameSync(tmpPath, pairsPath);
|
|
194
|
+
return report;
|
|
195
|
+
}
|
|
149
196
|
function loadTopicProvider() {
|
|
150
197
|
try {
|
|
151
198
|
const raw = readFileSync(join(homedir(), ".nlm", "work-topics.json"), "utf8");
|
|
@@ -233,14 +280,14 @@ async function reconcileEmbeddingLanes(storage, embedder, codeEmbedder) {
|
|
|
233
280
|
// probe failure; code lane stays unknown
|
|
234
281
|
}
|
|
235
282
|
}
|
|
236
|
-
async function buildAdapters(sources) {
|
|
283
|
+
async function buildAdapters(sources, tenantId) {
|
|
237
284
|
// Sources table is the source of truth. Each enabled row maps to one
|
|
238
285
|
// adapter via adapterFromSource(). Detection still gates registration —
|
|
239
286
|
// a row pointing at a missing dir won't poll. NLM_ADAPTERS keeps working
|
|
240
287
|
// as a name-based filter for forcing a subset during dev.
|
|
241
288
|
const explicit = process.env["NLM_ADAPTERS"];
|
|
242
289
|
const allowed = explicit ? new Set(explicit.split(",").map((s) => s.trim())) : null;
|
|
243
|
-
const rows = await sources.list();
|
|
290
|
+
const rows = await sources.list(tenantId);
|
|
244
291
|
const out = [];
|
|
245
292
|
for (const row of rows) {
|
|
246
293
|
if (!row.enabled)
|
|
@@ -286,13 +333,13 @@ async function buildStack() {
|
|
|
286
333
|
const signals = storage.signals;
|
|
287
334
|
const scope = installScope();
|
|
288
335
|
const sources = storage.sources;
|
|
289
|
-
await sources.seedDefaults();
|
|
336
|
+
await sources.seedDefaults(DEFAULT_TEAM_ID);
|
|
290
337
|
const providers = storage.providers;
|
|
291
338
|
// Provider seeding is SQLite-only: it bridges from the local DEEPSEEK_API_KEY
|
|
292
339
|
// env, which is wrong for a hosted multi-tenant PG. PgProviderRegistry has no
|
|
293
340
|
// seedDefaults (not on ProviderRegistryPort), so this narrows to the SQLite case.
|
|
294
341
|
if (providers instanceof ProviderRegistry)
|
|
295
|
-
await providers.seedDefaults();
|
|
342
|
+
await providers.seedDefaults(DEFAULT_TEAM_ID);
|
|
296
343
|
// Recall only uses embed(). Default embedder is local Ollama; NLM_EMBED_*
|
|
297
344
|
// can point it at any OpenAI-compatible endpoint. Classifier is wired
|
|
298
345
|
// separately for Phase D ingest.
|
|
@@ -303,11 +350,12 @@ async function buildStack() {
|
|
|
303
350
|
store,
|
|
304
351
|
llm: embedder,
|
|
305
352
|
factStore: facts,
|
|
353
|
+
learnerDeps: { learners: storage.learners, sessions: store },
|
|
306
354
|
exemplarStore: storage.exemplars,
|
|
307
355
|
codeEmbedder: buildCodeEmbedder(),
|
|
308
356
|
installScope: scope,
|
|
309
357
|
resolveWorkstreamMembers: async (idOrLabel) => {
|
|
310
|
-
const all = await wsStore.listAll();
|
|
358
|
+
const all = await wsStore.listAll(DEFAULT_TEAM_ID);
|
|
311
359
|
const byId = new Map(all.map((w) => [w.id, { id: w.id, mergedInto: w.mergedInto }]));
|
|
312
360
|
const target = all.find((w) => w.id === idOrLabel)
|
|
313
361
|
?? all.find((w) => normalizeLabel(w.label) === normalizeLabel(idOrLabel));
|
|
@@ -360,11 +408,32 @@ program
|
|
|
360
408
|
// Generate NLM_MCP_TOKEN if missing so /api/* gets Bearer-protected for
|
|
361
409
|
// non-browser callers. Idempotent: re-reads persisted token first.
|
|
362
410
|
autoloadEnv();
|
|
363
|
-
ensureMcpToken();
|
|
411
|
+
const mcpToken = ensureMcpToken();
|
|
364
412
|
const { storage, store, facts, signals, scope, sources, providers, recall, factRecall, embedder, classifier } = await buildStack();
|
|
413
|
+
// Local-mode continuity (program spec §3 M3): idempotently seed the
|
|
414
|
+
// existing NLM_MCP_TOKEN's hash for the default team, so an install that
|
|
415
|
+
// already has a token keeps authenticating through the same
|
|
416
|
+
// resolveTeamByToken path as any future per-team token. No-op after the
|
|
417
|
+
// first boot (ensureActive is insert-if-absent).
|
|
418
|
+
await storage.teamTokens.ensureActive(hashTeamToken(mcpToken), DEFAULT_TEAM_ID);
|
|
365
419
|
const { existsSync } = await import("node:fs");
|
|
366
420
|
const hasMcpToken = Boolean(process.env["NLM_MCP_TOKEN"]);
|
|
367
421
|
const codeEmb = buildCodeEmbedder();
|
|
422
|
+
// Job supervision: owns the daemon's own reprocess child, restarting it
|
|
423
|
+
// on a non-clean exit while work remains and reporting stalls — see
|
|
424
|
+
// core/jobs/job-supervisor.ts for why this exists (a detached
|
|
425
|
+
// `nlm reprocess` died on OOM and sat unobserved).
|
|
426
|
+
// onEvent -> fireAlert is fire-and-forget on purpose: fireAlert already
|
|
427
|
+
// never throws (best-effort delivery, see fire-alert.ts) and JobSupervisor
|
|
428
|
+
// itself catches/logs anything an onEvent callback throws, so this can't
|
|
429
|
+
// crash the daemon either way.
|
|
430
|
+
const jobSupervisor = new JobSupervisor({
|
|
431
|
+
spawnChild: createReprocessSpawnChild({ execPath: process.execPath, nlmScriptPath: __filename }),
|
|
432
|
+
clock: realClock,
|
|
433
|
+
onEvent: (event) => {
|
|
434
|
+
void fireAlert(buildJobAlertEvent(event));
|
|
435
|
+
},
|
|
436
|
+
});
|
|
368
437
|
const app = createApp({
|
|
369
438
|
recall,
|
|
370
439
|
store,
|
|
@@ -374,7 +443,9 @@ program
|
|
|
374
443
|
dbPath: dbPath(),
|
|
375
444
|
classifier,
|
|
376
445
|
sources,
|
|
446
|
+
jobSupervisor,
|
|
377
447
|
providers,
|
|
448
|
+
teamTokens: storage.teamTokens,
|
|
378
449
|
ingest: {
|
|
379
450
|
classifier,
|
|
380
451
|
embedder,
|
|
@@ -383,7 +454,11 @@ program
|
|
|
383
454
|
classifierDescriptor: { provider: classifier.provider, model: classifier.model },
|
|
384
455
|
},
|
|
385
456
|
signalStore: signals,
|
|
457
|
+
learnerRecall: { learners: storage.learners, sessions: store },
|
|
386
458
|
installScope: scope,
|
|
459
|
+
// Signal scope inheritance (NLM_SCOPE_STAMP): a signal with only a
|
|
460
|
+
// session_id inherits that session's stamped scope at ingest.
|
|
461
|
+
sessionScopeReader: store,
|
|
387
462
|
// Code-exemplar lane. The store + code embedder are always wired; the
|
|
388
463
|
// NLM_CODE_EXEMPLARS_ENABLED flag gates capture (POST /api/signal) and
|
|
389
464
|
// the /api/exemplar + /api/recall-code routes at request time.
|
|
@@ -406,21 +481,33 @@ program
|
|
|
406
481
|
exemplarStore: storage.exemplars,
|
|
407
482
|
codeEmbedder: codeEmb,
|
|
408
483
|
installScope: scope,
|
|
484
|
+
signalStore: signals,
|
|
485
|
+
sessionScopeReader: store,
|
|
409
486
|
workDigest: { store, topicProvider: loadTopicProvider(), workstreams: storage.workstreams, ...workDigestEnv() },
|
|
410
487
|
workstreams: { store: storage.workstreams, sessions: store, facts: facts, exemplars: storage.exemplars },
|
|
488
|
+
learners: { store: storage.learners, workstreams: storage.workstreams, sessions: store },
|
|
411
489
|
},
|
|
412
490
|
}
|
|
413
491
|
: {}),
|
|
414
492
|
});
|
|
415
493
|
warmCodeEmbedder(codeEmb);
|
|
416
494
|
void reconcileEmbeddingLanes(storage, embedder, codeEmb).catch(() => { });
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
495
|
+
// Retried, not one-shot. A single attempt with a swallowed catch latched
|
|
496
|
+
// this stage cold for the whole uptime whenever the backend was not yet
|
|
497
|
+
// servable, which is the normal case when the embedder host is started by
|
|
498
|
+
// hand after a reboot.
|
|
499
|
+
void retryUntilWarm({
|
|
500
|
+
attempt: () => embedder.embed("warmup init", "query"),
|
|
501
|
+
onSuccess: () => {
|
|
502
|
+
markWarm("textEmbedder");
|
|
503
|
+
setLaneHealth("prose", "ok");
|
|
504
|
+
},
|
|
505
|
+
onFailure: (reason, attempt) => markWarmFailure("textEmbedder", reason, attempt),
|
|
506
|
+
sleep: (ms) => new Promise((resolve) => setTimeout(resolve, ms).unref?.()),
|
|
507
|
+
});
|
|
421
508
|
setImmediate(() => {
|
|
422
509
|
void recall
|
|
423
|
-
.search({ query: "warmup init", mode: "keyword", limit: 1 })
|
|
510
|
+
.search(DEFAULT_TEAM_ID, { query: "warmup init", mode: "keyword", limit: 1 })
|
|
424
511
|
.then(() => markWarm("fts5"))
|
|
425
512
|
.catch(() => { });
|
|
426
513
|
});
|
|
@@ -483,17 +570,36 @@ program
|
|
|
483
570
|
const SIGNAL_PRUNE_INTERVAL_MS = 6 * 60 * 60_000;
|
|
484
571
|
const signalPruneTimer = setInterval(() => {
|
|
485
572
|
const cutoff = new Date(Date.now() - SIGNAL_RETENTION_DAYS * 86_400_000).toISOString();
|
|
486
|
-
void signals.pruneOlderThan(cutoff).catch(() => { });
|
|
573
|
+
void signals.pruneOlderThan(DEFAULT_TEAM_ID, cutoff).catch(() => { });
|
|
487
574
|
}, SIGNAL_PRUNE_INTERVAL_MS);
|
|
488
575
|
signalPruneTimer.unref();
|
|
489
|
-
//
|
|
490
|
-
//
|
|
576
|
+
// NLM_ALERT_WEBHOOK self-reporting: version-drift + embedder-cold
|
|
577
|
+
// transitions POST to an operator-owned webhook. No-op (zero network
|
|
578
|
+
// calls) when the env var is unset — see core/alerts/fire-alert.ts.
|
|
579
|
+
const ALERT_DRIFT_CHECK_INTERVAL_MS = 60 * 60_000;
|
|
580
|
+
const alertDriftTimer = setInterval(() => {
|
|
581
|
+
void checkDriftAndAlert({ currentVersion: pkg.version }).catch(() => {
|
|
582
|
+
// Best-effort self-reporting — a failed check never blocks the daemon.
|
|
583
|
+
});
|
|
584
|
+
}, ALERT_DRIFT_CHECK_INTERVAL_MS);
|
|
585
|
+
alertDriftTimer.unref();
|
|
586
|
+
const ALERT_EMBEDDER_CHECK_INTERVAL_MS = 5 * 60_000;
|
|
587
|
+
const alertEmbedderTimer = setInterval(() => {
|
|
588
|
+
void checkEmbedderAndAlert().catch(() => {
|
|
589
|
+
// Best-effort self-reporting — a failed check never blocks the daemon.
|
|
590
|
+
});
|
|
591
|
+
}, ALERT_EMBEDDER_CHECK_INTERVAL_MS);
|
|
592
|
+
alertEmbedderTimer.unref();
|
|
593
|
+
// Corpus monitor: 24h stats job + pairs-cache refresh + weekly trend log.
|
|
594
|
+
// SQLite only. First run is delayed ~60s so warmup finishes first; then
|
|
595
|
+
// repeats every 24h.
|
|
491
596
|
if (!(storage instanceof PgStorage)) {
|
|
492
597
|
const CORPUS_MONITOR_INTERVAL_MS = 24 * 60 * 60 * 1000;
|
|
493
598
|
const CORPUS_MONITOR_INITIAL_DELAY_MS = 60 * 1000;
|
|
494
599
|
const nlmDataDir = join(homedir(), ".nlm");
|
|
495
600
|
const corpusStatsPath = join(nlmDataDir, "corpus-stats.jsonl");
|
|
496
601
|
const rederivTrendPath = join(nlmDataDir, "re-derivation-trend.jsonl");
|
|
602
|
+
const rederivPairsPath = join(nlmDataDir, "re-derivation-pairs.json");
|
|
497
603
|
const runCorpusMonitor = async () => {
|
|
498
604
|
try {
|
|
499
605
|
const sqliteStorage = storage;
|
|
@@ -527,9 +633,14 @@ program
|
|
|
527
633
|
catch {
|
|
528
634
|
// file absent or unreadable; treat as no prior entry
|
|
529
635
|
}
|
|
636
|
+
// Pairs cache is refreshed every 24h cycle regardless of the trend
|
|
637
|
+
// gate below - the trend JSONL is an append-only history and stays
|
|
638
|
+
// on its existing weekly cadence, but the pairs cache is a single
|
|
639
|
+
// overwritten file so refreshing it daily costs nothing extra and
|
|
640
|
+
// keeps sqlite-outcome-store.ts's reads within a ~24h staleness
|
|
641
|
+
// bound (see #405).
|
|
642
|
+
const report = await persistReDerivationPairs(rawDb, DEFAULT_TEAM_ID, rederivPairsPath, 42);
|
|
530
643
|
if (shouldAppendTrend(lastTrendTs, now)) {
|
|
531
|
-
const rdDeps = sqliteReDerivationDeps(rawDb);
|
|
532
|
-
const report = await computeReDerivationRate(rdDeps, 42);
|
|
533
644
|
appendFileSync(rederivTrendPath, JSON.stringify({ ts, windowDays: 42, rate: report.rate, pairCount: report.pairs.length, eligible: report.eligible }) + "\n", "utf8");
|
|
534
645
|
}
|
|
535
646
|
}
|
|
@@ -540,6 +651,78 @@ program
|
|
|
540
651
|
const corpusMonitorTimer = setInterval(() => { void runCorpusMonitor(); }, CORPUS_MONITOR_INTERVAL_MS);
|
|
541
652
|
corpusMonitorTimer.unref();
|
|
542
653
|
setTimeout(() => { void runCorpusMonitor(); }, CORPUS_MONITOR_INITIAL_DELAY_MS).unref();
|
|
654
|
+
// Wiki projection: regenerate the generated markdown tree. SQLite only,
|
|
655
|
+
// same as the corpus monitor. Idempotent, so a run with no corpus change
|
|
656
|
+
// writes zero bytes. Failure leaves the previous tree untouched and
|
|
657
|
+
// self-reports on the operator's webhook if one is configured. Opt-in:
|
|
658
|
+
// nlm-memory is a published public package, so the projection only
|
|
659
|
+
// registers (timer, directory, log line) when an operator explicitly
|
|
660
|
+
// sets NLM_WIKI_DIR — an upgrade must never start writing ~500 markdown
|
|
661
|
+
// files under a stranger's home directory on its own.
|
|
662
|
+
const wikiDir = process.env["NLM_WIKI_DIR"];
|
|
663
|
+
if (wikiDir) {
|
|
664
|
+
const parsedWikiHours = Number.parseInt(process.env["NLM_WIKI_INTERVAL_HOURS"] ?? "6", 10);
|
|
665
|
+
const WIKI_INTERVAL_MS = (Number.isFinite(parsedWikiHours) && parsedWikiHours > 0 ? parsedWikiHours : 6) * 60 * 60_000;
|
|
666
|
+
const WIKI_INITIAL_DELAY_MS = 90 * 1000;
|
|
667
|
+
const wikiConfig = {
|
|
668
|
+
minFacts: positiveIntEnv("NLM_WIKI_MIN_FACTS", 3),
|
|
669
|
+
minSessions: positiveIntEnv("NLM_WIKI_MIN_SESSIONS", 3),
|
|
670
|
+
linkBase: process.env["NLM_WIKI_LINK_BASE"] ?? `http://127.0.0.1:${p}`,
|
|
671
|
+
};
|
|
672
|
+
const wikiTarget = join(wikiDir, "Memory");
|
|
673
|
+
const runWikiProjection = async () => {
|
|
674
|
+
try {
|
|
675
|
+
const writer = new FsWikiWriter(wikiTarget);
|
|
676
|
+
const today = new Date().toISOString().slice(0, 10);
|
|
677
|
+
const result = await projectWiki({ facts, writer }, DEFAULT_TEAM_ID, wikiConfig, today);
|
|
678
|
+
console.error(`[wiki] ${result.written} written, ${result.unchanged} unchanged, ${result.removed} removed`);
|
|
679
|
+
const drift = buildWikiDriftEvent(result);
|
|
680
|
+
if (drift)
|
|
681
|
+
await fireAlert(drift);
|
|
682
|
+
}
|
|
683
|
+
catch (e) {
|
|
684
|
+
console.error(`[wiki] error: ${e instanceof Error ? e.message : String(e)}`);
|
|
685
|
+
await fireAlert(buildWikiFailureEvent(e)).catch(() => {
|
|
686
|
+
// Self-reporting is best-effort; a webhook outage never escalates.
|
|
687
|
+
});
|
|
688
|
+
}
|
|
689
|
+
};
|
|
690
|
+
console.error(` wiki: enabled, projecting to ${wikiTarget}`);
|
|
691
|
+
const wikiTimer = setInterval(() => { void runWikiProjection(); }, WIKI_INTERVAL_MS);
|
|
692
|
+
wikiTimer.unref();
|
|
693
|
+
setTimeout(() => { void runWikiProjection(); }, WIKI_INITIAL_DELAY_MS).unref();
|
|
694
|
+
}
|
|
695
|
+
}
|
|
696
|
+
// Exemplar embed-backfill: repairs code_exemplars rows whose vector was
|
|
697
|
+
// dropped by a cold CodeRankEmbed on first capture (see
|
|
698
|
+
// core/exemplars/embed-backfill.ts). SQLite only, same constraint as the
|
|
699
|
+
// corpus monitor above — discovery reads code_exemplars_vec directly via
|
|
700
|
+
// dbPath(). Daily cadence: the drop is a rare cold-embedder race, not a
|
|
701
|
+
// steady leak, so this just keeps recall_code from silently degrading
|
|
702
|
+
// between operator-run `nlm embed-backfill --exemplars` passes.
|
|
703
|
+
if (!(storage instanceof PgStorage) && process.env["NLM_CODE_EXEMPLARS_ENABLED"] === "1") {
|
|
704
|
+
const EXEMPLAR_BACKFILL_INTERVAL_MS = 24 * 60 * 60 * 1000;
|
|
705
|
+
const EXEMPLAR_BACKFILL_INITIAL_DELAY_MS = 120 * 1000;
|
|
706
|
+
const runExemplarBackfill = async () => {
|
|
707
|
+
try {
|
|
708
|
+
const report = await backfillExemplarEmbeddings({
|
|
709
|
+
tenantId: DEFAULT_TEAM_ID,
|
|
710
|
+
dbPath: dbPath(),
|
|
711
|
+
embedder: buildCodeEmbedder(),
|
|
712
|
+
store: storage.exemplars,
|
|
713
|
+
});
|
|
714
|
+
if (report.succeeded > 0 || report.failed > 0) {
|
|
715
|
+
console.error(`[exemplar-backfill] ${report.succeeded}/${report.total} repaired (${report.failed} failed)`);
|
|
716
|
+
}
|
|
717
|
+
}
|
|
718
|
+
catch (e) {
|
|
719
|
+
console.error(`[exemplar-backfill] error: ${e instanceof Error ? e.message : String(e)}`);
|
|
720
|
+
}
|
|
721
|
+
};
|
|
722
|
+
const exemplarBackfillTimer = setInterval(() => { void runExemplarBackfill(); }, EXEMPLAR_BACKFILL_INTERVAL_MS);
|
|
723
|
+
exemplarBackfillTimer.unref();
|
|
724
|
+
setTimeout(() => { void runExemplarBackfill(); }, EXEMPLAR_BACKFILL_INITIAL_DELAY_MS).unref();
|
|
725
|
+
console.error(" exemplar-backfill: enabled, repair sweep every 24h");
|
|
543
726
|
}
|
|
544
727
|
// Memo sweep runs independently of the transcript scheduler — it's the
|
|
545
728
|
// backstop for SessionEnd hook unreliability (crashes, kill -9, IDE
|
|
@@ -549,7 +732,7 @@ program
|
|
|
549
732
|
memoSweep.start();
|
|
550
733
|
console.error(" memo sweep: dormant cleanup every 5m (threshold 24h)");
|
|
551
734
|
if (opts.scheduler !== false) {
|
|
552
|
-
const adapters = await buildAdapters(sources);
|
|
735
|
+
const adapters = await buildAdapters(sources, DEFAULT_TEAM_ID);
|
|
553
736
|
if (adapters.length === 0) {
|
|
554
737
|
console.error(" scheduler: no adapters detected (set NLM_ADAPTERS to force-enable)");
|
|
555
738
|
}
|
|
@@ -575,6 +758,10 @@ program
|
|
|
575
758
|
clearInterval(checkpointTimer);
|
|
576
759
|
scheduler.stop();
|
|
577
760
|
memoSweep.stop();
|
|
761
|
+
// Reaps an active reprocess child instead of leaving it orphaned
|
|
762
|
+
// when the daemon exits. stop() is idempotent and a no-op when no
|
|
763
|
+
// run is active.
|
|
764
|
+
jobSupervisor.stop();
|
|
578
765
|
await storage.close();
|
|
579
766
|
process.exit(0);
|
|
580
767
|
};
|
|
@@ -610,7 +797,7 @@ program
|
|
|
610
797
|
.action(async (query, opts) => {
|
|
611
798
|
const { storage, recall } = await buildStack();
|
|
612
799
|
try {
|
|
613
|
-
const result = await recall.search({
|
|
800
|
+
const result = await recall.search(DEFAULT_TEAM_ID, {
|
|
614
801
|
query,
|
|
615
802
|
mode: opts.mode,
|
|
616
803
|
limit: opts.limit,
|
|
@@ -644,7 +831,7 @@ program
|
|
|
644
831
|
.action(async (idOrLabel) => {
|
|
645
832
|
const { storage, store } = await buildStack();
|
|
646
833
|
try {
|
|
647
|
-
const r = await recallWorkstreamHandler({ recall: {}, store, workstreams: { store: storage.workstreams, sessions: store, facts: storage.facts, exemplars: storage.exemplars } }, { idOrLabel });
|
|
834
|
+
const r = await recallWorkstreamHandler({ recall: {}, store, workstreams: { store: storage.workstreams, sessions: store, facts: storage.facts, exemplars: storage.exemplars } }, DEFAULT_TEAM_ID, { idOrLabel });
|
|
648
835
|
process.stdout.write(r.content[0].text + "\n");
|
|
649
836
|
}
|
|
650
837
|
finally {
|
|
@@ -659,7 +846,7 @@ program
|
|
|
659
846
|
.action(async (sessionId, workstream) => {
|
|
660
847
|
const { storage, store } = await buildStack();
|
|
661
848
|
try {
|
|
662
|
-
const r = await rebindSessionHandler({ recall: {}, store, workstreams: { store: storage.workstreams, sessions: store, facts: storage.facts, exemplars: storage.exemplars } }, { sessionId, workstream });
|
|
849
|
+
const r = await rebindSessionHandler({ recall: {}, store, workstreams: { store: storage.workstreams, sessions: store, facts: storage.facts, exemplars: storage.exemplars } }, DEFAULT_TEAM_ID, { sessionId, workstream });
|
|
663
850
|
process.stdout.write(r.content[0].text + "\n");
|
|
664
851
|
}
|
|
665
852
|
finally {
|
|
@@ -674,7 +861,7 @@ program
|
|
|
674
861
|
.action(async (from, into) => {
|
|
675
862
|
const { storage, store } = await buildStack();
|
|
676
863
|
try {
|
|
677
|
-
const r = await mergeWorkstreamsHandler({ recall: {}, store, workstreams: { store: storage.workstreams, sessions: store, facts: storage.facts, exemplars: storage.exemplars } }, { from, into });
|
|
864
|
+
const r = await mergeWorkstreamsHandler({ recall: {}, store, workstreams: { store: storage.workstreams, sessions: store, facts: storage.facts, exemplars: storage.exemplars } }, DEFAULT_TEAM_ID, { from, into });
|
|
678
865
|
process.stdout.write(r.content[0].text + "\n");
|
|
679
866
|
}
|
|
680
867
|
finally {
|
|
@@ -689,7 +876,7 @@ program
|
|
|
689
876
|
.action(async (idOrLabel, label) => {
|
|
690
877
|
const { storage, store } = await buildStack();
|
|
691
878
|
try {
|
|
692
|
-
const r = await renameWorkstreamHandler({ recall: {}, store, workstreams: { store: storage.workstreams, sessions: store, facts: storage.facts, exemplars: storage.exemplars } }, { idOrLabel, label });
|
|
879
|
+
const r = await renameWorkstreamHandler({ recall: {}, store, workstreams: { store: storage.workstreams, sessions: store, facts: storage.facts, exemplars: storage.exemplars } }, DEFAULT_TEAM_ID, { idOrLabel, label });
|
|
693
880
|
process.stdout.write(r.content[0].text + "\n");
|
|
694
881
|
}
|
|
695
882
|
finally {
|
|
@@ -703,7 +890,7 @@ program
|
|
|
703
890
|
.action(async (idOrLabel) => {
|
|
704
891
|
const { storage, store } = await buildStack();
|
|
705
892
|
try {
|
|
706
|
-
const r = await retireWorkstreamHandler({ recall: {}, store, workstreams: { store: storage.workstreams, sessions: store, facts: storage.facts, exemplars: storage.exemplars } }, { idOrLabel });
|
|
893
|
+
const r = await retireWorkstreamHandler({ recall: {}, store, workstreams: { store: storage.workstreams, sessions: store, facts: storage.facts, exemplars: storage.exemplars } }, DEFAULT_TEAM_ID, { idOrLabel });
|
|
707
894
|
process.stdout.write(r.content[0].text + "\n");
|
|
708
895
|
}
|
|
709
896
|
finally {
|
|
@@ -723,7 +910,7 @@ program
|
|
|
723
910
|
}
|
|
724
911
|
const { storage, store } = await buildStack();
|
|
725
912
|
try {
|
|
726
|
-
const r = await listMergeSuggestionsHandler({ recall: {}, store, workstreams: { store: storage.workstreams, sessions: store, facts: storage.facts, exemplars: storage.exemplars } }, { minScore });
|
|
913
|
+
const r = await listMergeSuggestionsHandler({ recall: {}, store, workstreams: { store: storage.workstreams, sessions: store, facts: storage.facts, exemplars: storage.exemplars } }, DEFAULT_TEAM_ID, { minScore });
|
|
727
914
|
process.stdout.write(r.content[0].text + "\n");
|
|
728
915
|
}
|
|
729
916
|
finally {
|
|
@@ -768,7 +955,7 @@ program
|
|
|
768
955
|
const { runEval } = await import("../core/eval/run-eval.js");
|
|
769
956
|
const { storage, recall } = await buildStack();
|
|
770
957
|
try {
|
|
771
|
-
const report = await runEval({ recall }, queries, { mode: opts.mode, k: 5 });
|
|
958
|
+
const report = await runEval({ recall: { search: (q) => recall.search(DEFAULT_TEAM_ID, q) } }, queries, { mode: opts.mode, k: 5 });
|
|
772
959
|
if (opts.json) {
|
|
773
960
|
process.stdout.write(JSON.stringify(report, null, 2) + "\n");
|
|
774
961
|
return;
|
|
@@ -799,7 +986,7 @@ program
|
|
|
799
986
|
const { installScope: getScope } = await import("../core/signals/install-scope.js");
|
|
800
987
|
try {
|
|
801
988
|
const embedder = buildCodeEmbedder();
|
|
802
|
-
const result = await recallCode({
|
|
989
|
+
const result = await recallCode(DEFAULT_TEAM_ID, {
|
|
803
990
|
query,
|
|
804
991
|
installScope: getScope(),
|
|
805
992
|
...(opts.repo ? { repo: opts.repo } : {}),
|
|
@@ -880,7 +1067,7 @@ program
|
|
|
880
1067
|
.option("--json", "emit JSON instead of a human-readable table")
|
|
881
1068
|
.action(async (opts) => {
|
|
882
1069
|
const { missStats } = await import("../core/recall/miss-log.js");
|
|
883
|
-
const stats = await missStats(opts.days);
|
|
1070
|
+
const stats = await missStats(DEFAULT_TEAM_ID, opts.days);
|
|
884
1071
|
if (opts.json) {
|
|
885
1072
|
process.stdout.write(`${JSON.stringify(stats, null, 2)}\n`);
|
|
886
1073
|
return;
|
|
@@ -915,9 +1102,9 @@ program
|
|
|
915
1102
|
const { readQueryLog } = await import("../core/recall/query-log.js");
|
|
916
1103
|
const { readCitationLog } = await import("../core/recall/citation-log.js");
|
|
917
1104
|
const [recallEntries, queryEntries, citationEntries] = await Promise.all([
|
|
918
|
-
readHookRecallLog(opts.days),
|
|
919
|
-
readQueryLog(opts.days),
|
|
920
|
-
readCitationLog(opts.days),
|
|
1105
|
+
readHookRecallLog(DEFAULT_TEAM_ID, opts.days),
|
|
1106
|
+
readQueryLog(DEFAULT_TEAM_ID, opts.days),
|
|
1107
|
+
readCitationLog(DEFAULT_TEAM_ID, opts.days),
|
|
921
1108
|
]);
|
|
922
1109
|
const result = computePrecision(recallEntries, citationEntries);
|
|
923
1110
|
const { perSource, unmeasurable } = computePerSourcePrecision(queryEntries, citationEntries);
|
|
@@ -975,7 +1162,7 @@ program
|
|
|
975
1162
|
process.exitCode = 1;
|
|
976
1163
|
return;
|
|
977
1164
|
}
|
|
978
|
-
const deps = sqliteReDerivationDeps(store.rawDb());
|
|
1165
|
+
const deps = sqliteReDerivationDeps(store.rawDb(), DEFAULT_TEAM_ID);
|
|
979
1166
|
const report = await computeReDerivationRate(deps, opts.window);
|
|
980
1167
|
if (opts.json) {
|
|
981
1168
|
process.stdout.write(JSON.stringify(report, null, 2) + "\n");
|
|
@@ -1007,6 +1194,46 @@ program
|
|
|
1007
1194
|
yes: Boolean(opts.yes),
|
|
1008
1195
|
});
|
|
1009
1196
|
});
|
|
1197
|
+
program
|
|
1198
|
+
.command("reconcile-facts")
|
|
1199
|
+
.description("Find live facts that contradict an authorities file (dry run unless --apply)")
|
|
1200
|
+
.requiredOption("--authorities <file>", "JSON file of authoritative values and deleted files")
|
|
1201
|
+
.option("--apply", "retire the findings (backs up the DB first)")
|
|
1202
|
+
.option("-l, --limit <n>", "max facts changed per run", (v) => Number.parseInt(v, 10), 100)
|
|
1203
|
+
.option("--json", "emit the report as JSON")
|
|
1204
|
+
.action(async (opts) => {
|
|
1205
|
+
if (process.env["NLM_PG_URL"]) {
|
|
1206
|
+
console.error("nlm reconcile-facts: the Postgres backend is not supported yet");
|
|
1207
|
+
process.exit(1);
|
|
1208
|
+
}
|
|
1209
|
+
const storage = SqliteStorage.create({ dbPath: dbPath(), migrationsDir: MIGRATIONS_DIR });
|
|
1210
|
+
try {
|
|
1211
|
+
const db = storage.rawDb();
|
|
1212
|
+
const findings = findContradictions(db, loadAuthorities(opts.authorities));
|
|
1213
|
+
const limit = opts.limit;
|
|
1214
|
+
const applied = opts.apply ? await applyRetirements(db, dbPath(), findings, limit) : null;
|
|
1215
|
+
if (opts.json) {
|
|
1216
|
+
process.stdout.write(JSON.stringify({
|
|
1217
|
+
applied: Boolean(opts.apply),
|
|
1218
|
+
total: findings.length,
|
|
1219
|
+
findings,
|
|
1220
|
+
retired: applied?.retired.map((f) => f.factId) ?? [],
|
|
1221
|
+
backupPath: applied?.backupPath ?? null,
|
|
1222
|
+
}, null, 2) + "\n");
|
|
1223
|
+
return;
|
|
1224
|
+
}
|
|
1225
|
+
console.log(formatReport(findings, limit));
|
|
1226
|
+
if (applied) {
|
|
1227
|
+
console.log(`retired ${applied.retired.length} fact(s); backup: ${applied.backupPath ?? "none"}`);
|
|
1228
|
+
}
|
|
1229
|
+
else {
|
|
1230
|
+
console.log("dry run: nothing written (pass --apply to retire)");
|
|
1231
|
+
}
|
|
1232
|
+
}
|
|
1233
|
+
finally {
|
|
1234
|
+
await storage.close();
|
|
1235
|
+
}
|
|
1236
|
+
});
|
|
1010
1237
|
program
|
|
1011
1238
|
.command("classify-parity")
|
|
1012
1239
|
.description("Run TS classifier against ~/.nlm/canonical.sqlite and diff vs persisted Python output")
|
|
@@ -1033,13 +1260,39 @@ program
|
|
|
1033
1260
|
.option("-l, --limit <n>", "session cap (default: all)", (v) => Number.parseInt(v, 10))
|
|
1034
1261
|
.option("--state <path>", "resume state file (default ~/.nlm/embed_reembed.state)")
|
|
1035
1262
|
.option("--exemplars", "instead: embed code_exemplars rows missing a vector (repairs dropped capture embeds)")
|
|
1263
|
+
.option("--facts", "instead: embed active facts missing a vector (repairs dropped ingest embeds)")
|
|
1036
1264
|
.option("--dry-run", "report dim mismatch and what would be dropped without writing")
|
|
1037
1265
|
.option("-v, --verbose", "per-session progress on stderr")
|
|
1038
1266
|
.action(async (opts) => {
|
|
1267
|
+
if (opts.facts) {
|
|
1268
|
+
const { storage } = await buildStack();
|
|
1269
|
+
try {
|
|
1270
|
+
const report = await backfillFactEmbeddings({
|
|
1271
|
+
tenantId: DEFAULT_TEAM_ID,
|
|
1272
|
+
dbPath: dbPath(),
|
|
1273
|
+
embedder: buildEmbedder(),
|
|
1274
|
+
store: storage.facts,
|
|
1275
|
+
...(opts.limit ? { limit: opts.limit } : {}),
|
|
1276
|
+
...(opts.verbose
|
|
1277
|
+
? {
|
|
1278
|
+
onProgress: (i, n, id, status) => {
|
|
1279
|
+
process.stderr.write(` [${i}/${n}] ${id} ${status}\n`);
|
|
1280
|
+
},
|
|
1281
|
+
}
|
|
1282
|
+
: {}),
|
|
1283
|
+
});
|
|
1284
|
+
process.stdout.write(JSON.stringify(report, null, 2) + "\n");
|
|
1285
|
+
}
|
|
1286
|
+
finally {
|
|
1287
|
+
await storage.close();
|
|
1288
|
+
}
|
|
1289
|
+
return;
|
|
1290
|
+
}
|
|
1039
1291
|
if (opts.exemplars) {
|
|
1040
1292
|
const { storage } = await buildStack();
|
|
1041
1293
|
try {
|
|
1042
1294
|
const report = await backfillExemplarEmbeddings({
|
|
1295
|
+
tenantId: DEFAULT_TEAM_ID,
|
|
1043
1296
|
dbPath: dbPath(),
|
|
1044
1297
|
embedder: buildCodeEmbedder(),
|
|
1045
1298
|
store: storage.exemplars,
|
|
@@ -1111,7 +1364,7 @@ program
|
|
|
1111
1364
|
},
|
|
1112
1365
|
}
|
|
1113
1366
|
: {}),
|
|
1114
|
-
});
|
|
1367
|
+
}, DEFAULT_TEAM_ID);
|
|
1115
1368
|
process.stdout.write(JSON.stringify(report, null, 2) + "\n");
|
|
1116
1369
|
}
|
|
1117
1370
|
finally {
|
|
@@ -1132,7 +1385,7 @@ program
|
|
|
1132
1385
|
await stack.storage.close();
|
|
1133
1386
|
process.exit(1);
|
|
1134
1387
|
}
|
|
1135
|
-
const adapters = await buildAdapters(stack.sources);
|
|
1388
|
+
const adapters = await buildAdapters(stack.sources, DEFAULT_TEAM_ID);
|
|
1136
1389
|
const r = await reclassifyOversized({
|
|
1137
1390
|
db: stack.storage.rawDb(),
|
|
1138
1391
|
store: stack.storage.sessions,
|
|
@@ -1141,7 +1394,7 @@ program
|
|
|
1141
1394
|
classifier: stack.classifier,
|
|
1142
1395
|
classifierDescriptor: { provider: stack.classifier.provider, model: stack.classifier.model },
|
|
1143
1396
|
adapters,
|
|
1144
|
-
}, { ...(opts.limit ? { limit: opts.limit } : {}), dryRun: Boolean(opts.dryRun) });
|
|
1397
|
+
}, DEFAULT_TEAM_ID, { ...(opts.limit ? { limit: opts.limit } : {}), dryRun: Boolean(opts.dryRun) });
|
|
1145
1398
|
console.log(`reclassify-oversized: attempted=${r.attempted} ingested=${r.ingested} ` +
|
|
1146
1399
|
`lowConfidence=${r.skippedLowConfidence} missingFile=${r.missingFile} failed=${r.failed} ` +
|
|
1147
1400
|
`| gained entities=${r.entities} decisions=${r.decisions} facts=${r.facts}`);
|
|
@@ -1163,7 +1416,59 @@ program
|
|
|
1163
1416
|
.option("--dry-run", "print cohort report without writing")
|
|
1164
1417
|
.option("-v, --verbose", "per-session progress on stderr")
|
|
1165
1418
|
.option("--force-embed", "proceed even when the stored prose embedding lane mismatches the runtime embedder")
|
|
1419
|
+
.option("--daemon", "submit to the running daemon's job supervisor instead of running in this process (restarts on OOM/crash, reports stalls)")
|
|
1166
1420
|
.action(async (opts) => {
|
|
1421
|
+
if (opts.daemon) {
|
|
1422
|
+
// Same daemon-call auth pattern as `nlm ui`'s bootstrap-nonce POST:
|
|
1423
|
+
// autoload ~/.nlm/.env so a fresh shell still has NLM_MCP_TOKEN, then
|
|
1424
|
+
// send it as Authorization: Bearer (absent token = local-mode
|
|
1425
|
+
// fallback on the daemon side, never a hard requirement).
|
|
1426
|
+
autoloadEnv();
|
|
1427
|
+
const p = port();
|
|
1428
|
+
const token = process.env["NLM_MCP_TOKEN"];
|
|
1429
|
+
const args = [];
|
|
1430
|
+
if (opts.limit !== undefined)
|
|
1431
|
+
args.push("--limit", String(opts.limit));
|
|
1432
|
+
if (opts.minConfidence !== undefined)
|
|
1433
|
+
args.push("--min-confidence", String(opts.minConfidence));
|
|
1434
|
+
if (opts.onlyNull)
|
|
1435
|
+
args.push("--only-null");
|
|
1436
|
+
for (const tag of opts.excludeModel ?? [])
|
|
1437
|
+
args.push("--exclude-model", tag);
|
|
1438
|
+
if (opts.state)
|
|
1439
|
+
args.push("--state", opts.state);
|
|
1440
|
+
if (opts.dryRun)
|
|
1441
|
+
args.push("--dry-run");
|
|
1442
|
+
if (opts.forceEmbed)
|
|
1443
|
+
args.push("--force-embed");
|
|
1444
|
+
// -v is always added by the daemon's spawn adapter (job-supervisor.ts
|
|
1445
|
+
// parses reprocess's [i/n] progress lines from it) — a local
|
|
1446
|
+
// --verbose here would only affect this short-lived CLI invocation's
|
|
1447
|
+
// own stdout, which prints nothing itself, so it's not forwarded.
|
|
1448
|
+
try {
|
|
1449
|
+
const res = await fetch(`http://localhost:${p}/api/jobs/reprocess`, {
|
|
1450
|
+
method: "POST",
|
|
1451
|
+
headers: {
|
|
1452
|
+
"content-type": "application/json",
|
|
1453
|
+
...(token ? { authorization: `Bearer ${token}` } : {}),
|
|
1454
|
+
},
|
|
1455
|
+
body: JSON.stringify({ args }),
|
|
1456
|
+
});
|
|
1457
|
+
const body = await res.json().catch(() => ({}));
|
|
1458
|
+
if (res.status === 202) {
|
|
1459
|
+
console.log(`reprocess --daemon: submitted (${JSON.stringify(body)})`);
|
|
1460
|
+
process.exit(0);
|
|
1461
|
+
}
|
|
1462
|
+
console.error(`reprocess --daemon: daemon returned ${res.status}: ${JSON.stringify(body)}`);
|
|
1463
|
+
process.exit(1);
|
|
1464
|
+
}
|
|
1465
|
+
catch (e) {
|
|
1466
|
+
console.error(`reprocess --daemon: could not reach the daemon at localhost:${p}. Is it running?`);
|
|
1467
|
+
console.error(` ${e instanceof Error ? e.message : String(e)}`);
|
|
1468
|
+
process.exit(1);
|
|
1469
|
+
}
|
|
1470
|
+
return;
|
|
1471
|
+
}
|
|
1167
1472
|
const { reprocess: runReprocess } = await import("../core/ingest/reprocess.js");
|
|
1168
1473
|
const stack = await buildStack();
|
|
1169
1474
|
try {
|
|
@@ -1182,7 +1487,7 @@ program
|
|
|
1182
1487
|
classifierDescriptor: { provider: stack.classifier.provider, model: stack.classifier.model },
|
|
1183
1488
|
embeddingConfig: stack.storage.embeddingConfig,
|
|
1184
1489
|
embedderDescriptor: { provider: embedderInfo.provider, model: embedderInfo.model },
|
|
1185
|
-
}, {
|
|
1490
|
+
}, DEFAULT_TEAM_ID, {
|
|
1186
1491
|
...(opts.limit ? { limit: opts.limit } : {}),
|
|
1187
1492
|
...(opts.minConfidence !== undefined ? { minConfidence: opts.minConfidence } : {}),
|
|
1188
1493
|
...(opts.onlyNull ? { onlyNull: true } : {}),
|
|
@@ -1205,11 +1510,39 @@ program
|
|
|
1205
1510
|
await stack.storage.close();
|
|
1206
1511
|
}
|
|
1207
1512
|
});
|
|
1513
|
+
program
|
|
1514
|
+
.command("backfill-derivables")
|
|
1515
|
+
.description("Retroactive: stamp agent_persona + parent_session_id (#352 phase 2) onto sessions ingested\n" +
|
|
1516
|
+
"before the scheduler started stamping them at classify time. Idempotent (WHERE agent_persona IS NULL).")
|
|
1517
|
+
.option("--dry-run", "report counts without writing")
|
|
1518
|
+
.option("--with-transcript-scan", "also scan each candidate's transcript for primary_model/total_tokens/skill (slower)")
|
|
1519
|
+
.action(async (opts) => {
|
|
1520
|
+
const { backfillDerivables } = await import("../core/ingest/backfill-derivables.js");
|
|
1521
|
+
const stack = await buildStack();
|
|
1522
|
+
try {
|
|
1523
|
+
if (!(stack.storage instanceof SqliteStorage)) {
|
|
1524
|
+
console.error("backfill-derivables: only supported with SQLite storage (NLM_PG_URL must not be set)");
|
|
1525
|
+
await stack.storage.close();
|
|
1526
|
+
process.exit(1);
|
|
1527
|
+
}
|
|
1528
|
+
const report = await backfillDerivables(stack.storage.rawDb(), {
|
|
1529
|
+
dryRun: Boolean(opts.dryRun),
|
|
1530
|
+
withTranscriptScan: Boolean(opts.withTranscriptScan),
|
|
1531
|
+
});
|
|
1532
|
+
console.log(`backfill-derivables${opts.dryRun ? " (dry-run)" : ""}: ` +
|
|
1533
|
+
`updated=${report.updated} skipped(already-stamped)=${report.skippedAlreadyStamped} ` +
|
|
1534
|
+
`skipped(no-op)=${report.skippedNoop} subagent-candidates=${report.subagentCandidates} ` +
|
|
1535
|
+
`unknown-parent=${report.unknownParent} transcript-scanned=${report.transcriptScanned} total=${report.total}`);
|
|
1536
|
+
}
|
|
1537
|
+
finally {
|
|
1538
|
+
await stack.storage.close();
|
|
1539
|
+
}
|
|
1540
|
+
});
|
|
1208
1541
|
program
|
|
1209
1542
|
.command("mcp")
|
|
1210
1543
|
.description("Run as an MCP stdio server (for ~/.mcp.json)")
|
|
1211
1544
|
.action(async () => {
|
|
1212
|
-
const { recall, store, facts, factRecall, storage, scope } = await buildStack();
|
|
1545
|
+
const { recall, store, facts, factRecall, storage, scope, signals } = await buildStack();
|
|
1213
1546
|
const server = createMcpServer({
|
|
1214
1547
|
recall,
|
|
1215
1548
|
store,
|
|
@@ -1218,9 +1551,18 @@ program
|
|
|
1218
1551
|
exemplarStore: storage.exemplars,
|
|
1219
1552
|
codeEmbedder: buildCodeEmbedder(),
|
|
1220
1553
|
installScope: scope,
|
|
1554
|
+
signalStore: signals,
|
|
1555
|
+
sessionScopeReader: store,
|
|
1221
1556
|
workDigest: { store, topicProvider: loadTopicProvider(), workstreams: storage.workstreams, ...workDigestEnv() },
|
|
1222
1557
|
workstreams: { store: storage.workstreams, sessions: store, facts: facts, exemplars: storage.exemplars },
|
|
1223
|
-
|
|
1558
|
+
learners: { store: storage.learners, workstreams: storage.workstreams, sessions: store },
|
|
1559
|
+
// Tier-B outcome rollup (#352 phase 2) only has a SQLite adapter today;
|
|
1560
|
+
// PgStorage deployments skip get_session's `outcome` field until parity lands.
|
|
1561
|
+
...(storage instanceof SqliteStorage ? { outcomeDb: storage.rawDb() } : {}),
|
|
1562
|
+
},
|
|
1563
|
+
// stdio MCP transport carries no bearer token — it is local-mode only
|
|
1564
|
+
// by construction (program spec §3), bound to the default team.
|
|
1565
|
+
DEFAULT_TEAM_ID);
|
|
1224
1566
|
const transport = new StdioServerTransport();
|
|
1225
1567
|
await server.connect(transport);
|
|
1226
1568
|
});
|
|
@@ -1607,14 +1949,12 @@ const SESSION_START_HOOK_JS = resolve(__dirname, "../hook/session-start-hook.js"
|
|
|
1607
1949
|
const SESSION_END_HOOK_JS = resolve(__dirname, "../hook/session-end-hook.js");
|
|
1608
1950
|
const STOP_HOOK_JS = resolve(__dirname, "../hook/stop-hook.js");
|
|
1609
1951
|
const PRE_COMPACT_HOOK_JS = resolve(__dirname, "../hook/pre-compact-hook.js");
|
|
1610
|
-
const SUBAGENT_START_HOOK_JS = resolve(__dirname, "../hook/subagent-start-hook.js");
|
|
1611
1952
|
const ALL_HOOKS = [
|
|
1612
1953
|
{ event: "UserPromptSubmit", script: HOOK_JS, label: "recall" },
|
|
1613
1954
|
{ event: "SessionStart", script: SESSION_START_HOOK_JS, label: "session-start" },
|
|
1614
1955
|
{ event: "SessionEnd", script: SESSION_END_HOOK_JS, label: "session-end" },
|
|
1615
1956
|
{ event: "Stop", script: STOP_HOOK_JS, label: "stop" },
|
|
1616
1957
|
{ event: "PreCompact", script: PRE_COMPACT_HOOK_JS, label: "pre-compact" },
|
|
1617
|
-
{ event: "SubagentStart", script: SUBAGENT_START_HOOK_JS, label: "subagent-start" },
|
|
1618
1958
|
];
|
|
1619
1959
|
function claudeSettingsPath() {
|
|
1620
1960
|
return process.env["NLM_CLAUDE_SETTINGS"] ?? join(homedir(), ".claude", "settings.json");
|
|
@@ -1624,7 +1964,7 @@ const hook = program
|
|
|
1624
1964
|
.description("Manage the Claude Code NLM hooks");
|
|
1625
1965
|
hook
|
|
1626
1966
|
.command("install")
|
|
1627
|
-
.description("Add the NLM hooks (recall + session-end + stop) to ~/.claude/settings.json (live mode)")
|
|
1967
|
+
.description("Add the NLM hooks (recall + session-start + session-end + stop + pre-compact) to ~/.claude/settings.json (live mode)")
|
|
1628
1968
|
.action(() => {
|
|
1629
1969
|
const path = claudeSettingsPath();
|
|
1630
1970
|
const installed = [];
|
|
@@ -1650,6 +1990,9 @@ hook
|
|
|
1650
1990
|
console.error(" Session-end hook cleans up ~/.nlm/hook-state/<session>.json on session close.");
|
|
1651
1991
|
console.error(" To run silently for calibration (no injection): set NLM_HOOK_MODE=shadow in the command.");
|
|
1652
1992
|
console.error(" To remove: nlm hook uninstall");
|
|
1993
|
+
const nvmWarning = nvmPinnedHookWarning(process.execPath, homedir());
|
|
1994
|
+
if (nvmWarning)
|
|
1995
|
+
console.error(`nlm: ${nvmWarning}`);
|
|
1653
1996
|
});
|
|
1654
1997
|
hook
|
|
1655
1998
|
.command("uninstall")
|
|
@@ -1674,13 +2017,19 @@ connect
|
|
|
1674
2017
|
.option("--with-hooks", "additionally write absolute paths to ~/.codex/hooks.json (Codex Desktop fallback for openai/codex#16430)")
|
|
1675
2018
|
.option("--repair", "first strip a stale pre-rename nlm-memory-ts install (config block + plugin + marketplace), then connect")
|
|
1676
2019
|
.option("--dry-run", "print what would happen without invoking codex")
|
|
2020
|
+
.option("--stdio", "use the stdio transport instead of the shared HTTP endpoint (works with the daemon stopped)")
|
|
1677
2021
|
.action((opts) => {
|
|
1678
2022
|
if (!opts.dryRun && !codexBinaryAvailable()) {
|
|
1679
2023
|
console.error("nlm connect codex: `codex` binary not on PATH. Install via `npm i -g @openai/codex` or `brew install codex`.");
|
|
1680
2024
|
process.exit(1);
|
|
1681
2025
|
}
|
|
1682
2026
|
const source = opts.local ? REPO_ROOT : opts.source;
|
|
1683
|
-
const connectOpts = {
|
|
2027
|
+
const connectOpts = {
|
|
2028
|
+
source,
|
|
2029
|
+
withHooks: Boolean(opts.withHooks),
|
|
2030
|
+
dryRun: Boolean(opts.dryRun),
|
|
2031
|
+
transport: opts.stdio ? "stdio" : "http",
|
|
2032
|
+
};
|
|
1684
2033
|
const repair = opts.repair
|
|
1685
2034
|
? repairCodex(connectOpts, pluginScriptsDir(REPO_ROOT))
|
|
1686
2035
|
: null;
|
|
@@ -1733,17 +2082,25 @@ connect
|
|
|
1733
2082
|
.description("Write the nlm-memory MCP server block into ~/.mcp.json")
|
|
1734
2083
|
.option("--with-hooks", "also install Claude Code session hooks")
|
|
1735
2084
|
.option("--dry-run", "print what would happen without changing files")
|
|
2085
|
+
.option("--stdio", "use the stdio transport instead of the shared HTTP endpoint (works with the daemon stopped)")
|
|
1736
2086
|
.action((opts) => {
|
|
2087
|
+
// A fresh shell has no NLM_MCP_TOKEN exported even though ~/.nlm/.env
|
|
2088
|
+
// holds one; the HTTP block needs it. Mirror `nlm ui`'s lookup.
|
|
2089
|
+
autoloadEnv();
|
|
2090
|
+
const transport = opts.stdio ? "stdio" : "http";
|
|
1737
2091
|
if (opts.dryRun) {
|
|
1738
2092
|
console.error("nlm connect claude-code (dry run):");
|
|
1739
|
-
console.error(` write [mcpServers.nlm-memory] to ${mcpConfigPath()}`);
|
|
2093
|
+
console.error(` write [mcpServers.nlm-memory] (${transport}) to ${mcpConfigPath()}`);
|
|
1740
2094
|
if (opts.withHooks)
|
|
1741
2095
|
console.error(" install 6 Claude Code hooks");
|
|
1742
2096
|
return;
|
|
1743
2097
|
}
|
|
1744
|
-
const report = connectClaudeCode({ nlmBinPath: __filename, nodeExecPath: process.execPath });
|
|
2098
|
+
const report = connectClaudeCode({ nlmBinPath: __filename, nodeExecPath: process.execPath, transport });
|
|
1745
2099
|
const action = report.alreadyPresent ? "updated" : "written";
|
|
1746
|
-
console.error(`nlm: [mcpServers.nlm-memory] ${action} → ${report.mcpConfigPath}`);
|
|
2100
|
+
console.error(`nlm: [mcpServers.nlm-memory] ${action} (${report.transport}) → ${report.mcpConfigPath}`);
|
|
2101
|
+
if (report.migratedFromStdio) {
|
|
2102
|
+
console.error(" migrated from stdio → shared HTTP endpoint (one daemon-backed server for every session).");
|
|
2103
|
+
}
|
|
1747
2104
|
console.error(" Restart Claude Code to activate the MCP server.");
|
|
1748
2105
|
if (opts.withHooks) {
|
|
1749
2106
|
const path = claudeSettingsPath();
|
|
@@ -1760,20 +2117,31 @@ connect
|
|
|
1760
2117
|
process.exit(1);
|
|
1761
2118
|
}
|
|
1762
2119
|
console.error(`nlm: ${result.count} hooks installed → ${path}`);
|
|
2120
|
+
const nvmWarning = nvmPinnedHookWarning(process.execPath, homedir());
|
|
2121
|
+
if (nvmWarning)
|
|
2122
|
+
console.error(`nlm: ${nvmWarning}`);
|
|
1763
2123
|
}
|
|
1764
2124
|
});
|
|
1765
2125
|
connect
|
|
1766
2126
|
.command("hermes")
|
|
1767
2127
|
.description("Write the nlm-memory MCP server entry into ~/.hermes/config.yaml")
|
|
1768
2128
|
.option("--dry-run", "print what would happen without changing files")
|
|
2129
|
+
.option("--stdio", "use the stdio transport instead of the shared HTTP endpoint (works with the daemon stopped)")
|
|
1769
2130
|
.action((opts) => {
|
|
2131
|
+
// A fresh shell has no NLM_MCP_TOKEN exported even though ~/.nlm/.env
|
|
2132
|
+
// holds one; the HTTP block needs it. Mirror `nlm ui`'s lookup.
|
|
2133
|
+
autoloadEnv();
|
|
2134
|
+
const transport = opts.stdio ? "stdio" : "http";
|
|
1770
2135
|
if (opts.dryRun) {
|
|
1771
|
-
console.error(`nlm connect hermes (dry run): write [mcp_servers.nlm-memory] to ${hermesConfigPath()}`);
|
|
2136
|
+
console.error(`nlm connect hermes (dry run): write [mcp_servers.nlm-memory] (${transport}) to ${hermesConfigPath()}`);
|
|
1772
2137
|
return;
|
|
1773
2138
|
}
|
|
1774
|
-
const report = connectHermes({ nlmBinPath: __filename, nodeExecPath: process.execPath, dryRun: false });
|
|
2139
|
+
const report = connectHermes({ nlmBinPath: __filename, nodeExecPath: process.execPath, dryRun: false, transport });
|
|
1775
2140
|
const action = report.alreadyPresent ? "updated" : "written";
|
|
1776
|
-
console.error(`nlm: [mcp_servers.nlm-memory] ${action} → ${report.configPath}`);
|
|
2141
|
+
console.error(`nlm: [mcp_servers.nlm-memory] ${action} (${report.transport}) → ${report.configPath}`);
|
|
2142
|
+
if (report.migratedFromStdio) {
|
|
2143
|
+
console.error(" migrated from stdio → shared HTTP endpoint (one daemon-backed server for every session).");
|
|
2144
|
+
}
|
|
1777
2145
|
console.error(" Restart Hermes to activate the MCP server.");
|
|
1778
2146
|
});
|
|
1779
2147
|
connect
|
|
@@ -1808,7 +2176,7 @@ connect
|
|
|
1808
2176
|
const storage = SqliteStorage.create({ dbPath: dbPath(), migrationsDir: MIGRATIONS_DIR });
|
|
1809
2177
|
await storage.init();
|
|
1810
2178
|
try {
|
|
1811
|
-
const report = await connectCursor(storage.sources, {
|
|
2179
|
+
const report = await connectCursor(storage.sources, DEFAULT_TEAM_ID, {
|
|
1812
2180
|
...(opts.dbPath ? { dbPath: opts.dbPath } : {}),
|
|
1813
2181
|
dryRun: Boolean(opts.dryRun),
|
|
1814
2182
|
});
|
|
@@ -1840,7 +2208,7 @@ connect
|
|
|
1840
2208
|
const storage = SqliteStorage.create({ dbPath: dbPath(), migrationsDir: MIGRATIONS_DIR });
|
|
1841
2209
|
await storage.init();
|
|
1842
2210
|
try {
|
|
1843
|
-
const report = await connectWindsurf(storage.sources, {
|
|
2211
|
+
const report = await connectWindsurf(storage.sources, DEFAULT_TEAM_ID, {
|
|
1844
2212
|
...(opts.userDir ? { userDir: opts.userDir } : {}),
|
|
1845
2213
|
dryRun: Boolean(opts.dryRun),
|
|
1846
2214
|
});
|
|
@@ -1864,30 +2232,66 @@ connect
|
|
|
1864
2232
|
connect
|
|
1865
2233
|
.command("opencode")
|
|
1866
2234
|
.description("Register OpenCode as an nlm source (reads opencode.db directly) and optionally install rules nudge")
|
|
2235
|
+
.option("--db-path <path>", "override path to opencode.db")
|
|
1867
2236
|
.option("--with-rules", "also install global rules nudge at ~/.config/opencode/AGENTS.md")
|
|
1868
2237
|
.option("--dry-run", "print what would happen without changing files")
|
|
1869
|
-
.action((opts) => {
|
|
2238
|
+
.action(async (opts) => {
|
|
2239
|
+
const spec = {
|
|
2240
|
+
kind: "opencode",
|
|
2241
|
+
name: "OpenCode",
|
|
2242
|
+
runtimeLabel: "opencode/1.0",
|
|
2243
|
+
path: opts.dbPath ?? openCodeDefaultDbPath(),
|
|
2244
|
+
};
|
|
2245
|
+
await connectLocalSourceCommand(spec, Boolean(opts.dryRun));
|
|
1870
2246
|
if (opts.dryRun) {
|
|
1871
|
-
console.error("nlm connect opencode (dry run):");
|
|
1872
|
-
console.error(" OpenCode adapter is already wired via migrations/010_sources_opencode.sql — no source-registry mutation required");
|
|
1873
2247
|
if (opts.withRules)
|
|
1874
|
-
console.error(" install global rules nudge at ~/.config/opencode/AGENTS.md");
|
|
2248
|
+
console.error(" also install global rules nudge at ~/.config/opencode/AGENTS.md");
|
|
1875
2249
|
return;
|
|
1876
2250
|
}
|
|
1877
|
-
console.error("nlm: OpenCode source already registered (see migration 010). No source-registry changes needed.");
|
|
1878
2251
|
if (opts.withRules) {
|
|
1879
2252
|
const rules = installOpencodeRules();
|
|
1880
2253
|
console.error(` ${describeUpsert("OpenCode", rules)}`);
|
|
1881
2254
|
}
|
|
1882
|
-
else {
|
|
1883
|
-
console.error(" Pass --with-rules to install the recall nudge at ~/.config/opencode/AGENTS.md");
|
|
1884
|
-
}
|
|
1885
2255
|
});
|
|
2256
|
+
connect
|
|
2257
|
+
.command("muse")
|
|
2258
|
+
.description("Register Muse as an nlm source (reads its session store directly — no files installed)")
|
|
2259
|
+
.option("--sessions-path <path>", "override path to the Muse sessions directory")
|
|
2260
|
+
.option("--dry-run", "print what would happen without changing files")
|
|
2261
|
+
.action(async (opts) => {
|
|
2262
|
+
await connectLocalSourceCommand({
|
|
2263
|
+
kind: "muse",
|
|
2264
|
+
name: "Muse",
|
|
2265
|
+
runtimeLabel: "muse/1.0",
|
|
2266
|
+
path: opts.sessionsPath ?? museDefaultSessionsPath(),
|
|
2267
|
+
}, Boolean(opts.dryRun));
|
|
2268
|
+
});
|
|
2269
|
+
async function connectLocalSourceCommand(spec, dryRun) {
|
|
2270
|
+
const storage = SqliteStorage.create({ dbPath: dbPath(), migrationsDir: MIGRATIONS_DIR });
|
|
2271
|
+
await storage.init();
|
|
2272
|
+
try {
|
|
2273
|
+
const report = await connectLocalSource(storage.sources, DEFAULT_TEAM_ID, spec, { dryRun });
|
|
2274
|
+
const missing = report.exists ? "" : " (not found yet)";
|
|
2275
|
+
if (dryRun) {
|
|
2276
|
+
console.error(`nlm connect ${spec.kind} (dry run): register source at ${report.path}${missing}`);
|
|
2277
|
+
return;
|
|
2278
|
+
}
|
|
2279
|
+
console.error(`nlm: ${spec.name} source ${report.action} → ${report.path}${missing}`);
|
|
2280
|
+
console.error(" restart the daemon to start scanning it");
|
|
2281
|
+
}
|
|
2282
|
+
finally {
|
|
2283
|
+
await storage.close();
|
|
2284
|
+
}
|
|
2285
|
+
}
|
|
1886
2286
|
connect
|
|
1887
2287
|
.command("pi")
|
|
1888
|
-
.description("Register the nlm-memory prompt-recall extension
|
|
2288
|
+
.description("Register the nlm-memory prompt-recall extension + MCP server for pi")
|
|
1889
2289
|
.option("--dry-run", "print what would happen without changing files")
|
|
2290
|
+
.option("--stdio", "use the stdio transport instead of the shared HTTP endpoint (works with the daemon stopped)")
|
|
1890
2291
|
.action((opts) => {
|
|
2292
|
+
// The HTTP block needs NLM_MCP_TOKEN, which a fresh shell has not exported.
|
|
2293
|
+
autoloadEnv();
|
|
2294
|
+
const transport = opts.stdio ? "stdio" : "http";
|
|
1891
2295
|
const pluginDir = join(REPO_ROOT, "nlm");
|
|
1892
2296
|
const report = connectPi({ pluginDir, dryRun: Boolean(opts.dryRun) });
|
|
1893
2297
|
if (opts.dryRun) {
|
|
@@ -1902,6 +2306,19 @@ connect
|
|
|
1902
2306
|
console.error(`nlm: pi extension registered → ${report.settingsPath}`);
|
|
1903
2307
|
console.error(` Packages entry: ${report.pluginDir}`);
|
|
1904
2308
|
}
|
|
2309
|
+
// Two different files: the extension supplies the hook, mcp.json supplies
|
|
2310
|
+
// the callable tools. pi core has no MCP concept; pi-mcp-adapter reads it.
|
|
2311
|
+
const mcp = connectPiMcp({
|
|
2312
|
+
transport,
|
|
2313
|
+
nlmBinPath: __filename,
|
|
2314
|
+
nodeExecPath: process.execPath,
|
|
2315
|
+
dryRun: Boolean(opts.dryRun),
|
|
2316
|
+
});
|
|
2317
|
+
const mcpVerb = opts.dryRun ? "would write" : (mcp.alreadyPresent ? "updated" : "written");
|
|
2318
|
+
console.error(`nlm: [mcpServers.nlm-memory] ${mcpVerb} (${mcp.transport}) → ${mcp.mcpConfigPath}`);
|
|
2319
|
+
if (mcp.migratedFromStdio) {
|
|
2320
|
+
console.error(" migrated from stdio → shared HTTP endpoint (one daemon-backed server for every session).");
|
|
2321
|
+
}
|
|
1905
2322
|
console.error(" Restart pi to activate the prompt-recall hook.");
|
|
1906
2323
|
console.error(" Set NLM_HOOK_MODE=live in ~/.nlm/.env to flip from shadow → live.");
|
|
1907
2324
|
});
|
|
@@ -1913,6 +2330,7 @@ disconnect
|
|
|
1913
2330
|
.description("Remove the nlm-memory plugin + marketplace from Codex")
|
|
1914
2331
|
.option("--with-hooks", "also strip our entries from ~/.codex/hooks.json")
|
|
1915
2332
|
.option("--dry-run", "print what would happen without invoking codex")
|
|
2333
|
+
.option("--stdio", "use the stdio transport instead of the shared HTTP endpoint (works with the daemon stopped)")
|
|
1916
2334
|
.action((opts) => {
|
|
1917
2335
|
if (!opts.dryRun && !codexBinaryAvailable()) {
|
|
1918
2336
|
console.error("nlm disconnect codex: `codex` binary not on PATH.");
|
|
@@ -2003,7 +2421,7 @@ disconnect
|
|
|
2003
2421
|
const storage = SqliteStorage.create({ dbPath: dbPath(), migrationsDir: MIGRATIONS_DIR });
|
|
2004
2422
|
await storage.init();
|
|
2005
2423
|
try {
|
|
2006
|
-
const report = await disconnectCursor(storage.sources, { dryRun: Boolean(opts.dryRun) });
|
|
2424
|
+
const report = await disconnectCursor(storage.sources, DEFAULT_TEAM_ID, { dryRun: Boolean(opts.dryRun) });
|
|
2007
2425
|
if (opts.dryRun) {
|
|
2008
2426
|
console.error("nlm disconnect cursor (dry run): disable Cursor source in registry");
|
|
2009
2427
|
if (opts.withRules)
|
|
@@ -2031,7 +2449,7 @@ disconnect
|
|
|
2031
2449
|
const storage = SqliteStorage.create({ dbPath: dbPath(), migrationsDir: MIGRATIONS_DIR });
|
|
2032
2450
|
await storage.init();
|
|
2033
2451
|
try {
|
|
2034
|
-
const report = await disconnectWindsurf(storage.sources, { dryRun: Boolean(opts.dryRun) });
|
|
2452
|
+
const report = await disconnectWindsurf(storage.sources, DEFAULT_TEAM_ID, { dryRun: Boolean(opts.dryRun) });
|
|
2035
2453
|
if (opts.dryRun) {
|
|
2036
2454
|
console.error("nlm disconnect windsurf (dry run): disable Windsurf source in registry");
|
|
2037
2455
|
if (opts.withRules)
|
|
@@ -2109,7 +2527,7 @@ program
|
|
|
2109
2527
|
const storage = await buildStorage(dbPath());
|
|
2110
2528
|
const scope = installScope();
|
|
2111
2529
|
const sinceTs = new Date(Date.now() - opts.days * 86_400_000).toISOString();
|
|
2112
|
-
const rows = await storage.signals.listForAggregation({ installScope: scope, sinceTs });
|
|
2530
|
+
const rows = await storage.signals.listForAggregation(DEFAULT_TEAM_ID, { installScope: scope, sinceTs });
|
|
2113
2531
|
const { aggregateFailureModes } = await import("../core/signals/aggregate.js");
|
|
2114
2532
|
const { recommendActions } = await import("../core/signals/recommend.js");
|
|
2115
2533
|
const modes = aggregateFailureModes(rows);
|
|
@@ -2130,7 +2548,7 @@ program
|
|
|
2130
2548
|
program
|
|
2131
2549
|
.command("digest")
|
|
2132
2550
|
.description("Compose a daily-activity digest from the running daemon (optionally post to Telegram)")
|
|
2133
|
-
.option("-p, --port <n>", "daemon port", (v) => Number.parseInt(v, 10), Number.parseInt(process.env["NLM_PORT"] ??
|
|
2551
|
+
.option("-p, --port <n>", "daemon port", (v) => Number.parseInt(v, 10), Number.parseInt(process.env["NLM_PORT"] ?? DEFAULT_NLM_PORT, 10))
|
|
2134
2552
|
.option("--telegram", "post to Telegram instead of printing to stdout (requires TELEGRAM_BOT_TOKEN + TELEGRAM_CHAT_ID)")
|
|
2135
2553
|
.action(async (opts) => {
|
|
2136
2554
|
autoloadEnv();
|
|
@@ -2156,7 +2574,7 @@ program
|
|
|
2156
2574
|
const date = resolveDigestDate(opts.date);
|
|
2157
2575
|
const { storage, store } = await buildStack();
|
|
2158
2576
|
try {
|
|
2159
|
-
const digest = await buildWorkDigest({ store, topicProvider: loadTopicProvider(), workstreams: storage.workstreams, ...workDigestEnv() }, date);
|
|
2577
|
+
const digest = await buildWorkDigest({ store, topicProvider: loadTopicProvider(), workstreams: storage.workstreams, ...workDigestEnv() }, DEFAULT_TEAM_ID, date);
|
|
2160
2578
|
console.log(composeWorkDigest(digest));
|
|
2161
2579
|
}
|
|
2162
2580
|
finally {
|
|
@@ -2206,6 +2624,13 @@ async function gatherInstallProbe() {
|
|
|
2206
2624
|
// trust entry is Codex's registry (not nlm's), so it must not trip this check.
|
|
2207
2625
|
codexStale = txt.includes("@nlm-memory-ts");
|
|
2208
2626
|
}
|
|
2627
|
+
// Codex plugins contribute MCP servers to the effective configuration
|
|
2628
|
+
// without writing a table into config.toml. Prefer Codex's own resolved
|
|
2629
|
+
// view so `nlm verify` does not report a healthy plugin install as unwired.
|
|
2630
|
+
if (!codexMcp && codexBinaryAvailable()) {
|
|
2631
|
+
codexPresent = true;
|
|
2632
|
+
codexMcp = codexHasEffectiveMcpServer("nlm-memory");
|
|
2633
|
+
}
|
|
2209
2634
|
const envPath = join(homedir(), ".nlm", ".env");
|
|
2210
2635
|
return {
|
|
2211
2636
|
daemon: { reachable, version, expectedVersion: pkg.version },
|
|
@@ -2518,7 +2943,7 @@ entitiesCmd
|
|
|
2518
2943
|
if (opts.applySafe) {
|
|
2519
2944
|
for (const s of safe) {
|
|
2520
2945
|
try {
|
|
2521
|
-
await storage.entities.merge(s.source, s.target);
|
|
2946
|
+
await storage.entities.merge(DEFAULT_TEAM_ID, s.source, s.target);
|
|
2522
2947
|
console.log(`merged: ${s.source} -> ${s.target}`);
|
|
2523
2948
|
merged++;
|
|
2524
2949
|
}
|
|
@@ -2542,7 +2967,7 @@ entitiesCmd
|
|
|
2542
2967
|
const answer = await ask(`merge ${s.source} -> ${s.target}? [y/N] `);
|
|
2543
2968
|
if (answer.trim().toLowerCase() === "y") {
|
|
2544
2969
|
try {
|
|
2545
|
-
await storage.entities.merge(s.source, s.target);
|
|
2970
|
+
await storage.entities.merge(DEFAULT_TEAM_ID, s.source, s.target);
|
|
2546
2971
|
console.log(`merged: ${s.source} -> ${s.target}`);
|
|
2547
2972
|
merged++;
|
|
2548
2973
|
}
|
|
@@ -2566,6 +2991,66 @@ entitiesCmd
|
|
|
2566
2991
|
if (failed > 0)
|
|
2567
2992
|
process.exit(1);
|
|
2568
2993
|
});
|
|
2994
|
+
const scopeCmd = program
|
|
2995
|
+
.command("scope")
|
|
2996
|
+
.description("Scope management commands");
|
|
2997
|
+
scopeCmd
|
|
2998
|
+
.command("backfill")
|
|
2999
|
+
.description("Derive and stamp scope on legacy sessions/facts/exemplars/signals/workstreams from stored transcript evidence")
|
|
3000
|
+
.option("--apply", "write changes (default: dry run, no writes)")
|
|
3001
|
+
.option("--db <path>", "SQLite DB file to backfill (default: canonical NLM DB)")
|
|
3002
|
+
.option("--json", "emit machine-readable JSON summary instead of human-readable output")
|
|
3003
|
+
.action(async (opts) => {
|
|
3004
|
+
const targetPath = opts.db ?? dbPath();
|
|
3005
|
+
const storage = SqliteStorage.create({ dbPath: targetPath, migrationsDir: MIGRATIONS_DIR });
|
|
3006
|
+
const aliasMap = loadAliasMap();
|
|
3007
|
+
try {
|
|
3008
|
+
const result = await runScopeBackfill({
|
|
3009
|
+
db: storage.rawDb(),
|
|
3010
|
+
apply: Boolean(opts.apply),
|
|
3011
|
+
aliasMap,
|
|
3012
|
+
});
|
|
3013
|
+
if (opts.json) {
|
|
3014
|
+
const out = {
|
|
3015
|
+
dryRun: result.dryRun,
|
|
3016
|
+
sessions: { total: result.sessions.total, byScope: result.sessions.byScope },
|
|
3017
|
+
facts: { total: result.facts.total, byScope: result.facts.byScope },
|
|
3018
|
+
exemplars: { total: result.exemplars.total, byScope: result.exemplars.byScope },
|
|
3019
|
+
signals: { total: result.signals.total, byScope: result.signals.byScope },
|
|
3020
|
+
workstreams: { total: result.workstreams.total, byScope: result.workstreams.byScope },
|
|
3021
|
+
skipped: result.skipped,
|
|
3022
|
+
};
|
|
3023
|
+
process.stdout.write(JSON.stringify(out, null, 2) + "\n");
|
|
3024
|
+
}
|
|
3025
|
+
else {
|
|
3026
|
+
formatBackfillResult(result, (s) => process.stdout.write(s));
|
|
3027
|
+
}
|
|
3028
|
+
}
|
|
3029
|
+
finally {
|
|
3030
|
+
await storage.close();
|
|
3031
|
+
}
|
|
3032
|
+
});
|
|
3033
|
+
scopeCmd
|
|
3034
|
+
.command("coverage")
|
|
3035
|
+
.description("Report per-table and recall-weighted scope coverage fractions")
|
|
3036
|
+
.option("--db <path>", "SQLite DB file (default: canonical NLM DB)")
|
|
3037
|
+
.option("--query-log <path>", "query log path for recall-weighted coverage")
|
|
3038
|
+
.option("--window <n>", "last N log entries for recall-weighted coverage (default 200)", (v) => Number.parseInt(v, 10), 200)
|
|
3039
|
+
.option("--json", "emit machine-readable JSON instead of human-readable output")
|
|
3040
|
+
.action(async (opts) => {
|
|
3041
|
+
const targetPath = opts.db ?? dbPath();
|
|
3042
|
+
const result = await runScopeCoverage({
|
|
3043
|
+
dbPath: targetPath,
|
|
3044
|
+
...(opts.queryLog ? { queryLogPath: opts.queryLog } : {}),
|
|
3045
|
+
window: opts.window,
|
|
3046
|
+
});
|
|
3047
|
+
if (opts.json) {
|
|
3048
|
+
process.stdout.write(JSON.stringify(result, null, 2) + "\n");
|
|
3049
|
+
}
|
|
3050
|
+
else {
|
|
3051
|
+
formatCoverageResult(result, (s) => process.stdout.write(s));
|
|
3052
|
+
}
|
|
3053
|
+
});
|
|
2569
3054
|
// Entrypoint guard: only parse argv when this file IS the executed script.
|
|
2570
3055
|
// realpathSync resolves the npm bin symlink so the global `nlm` shim still
|
|
2571
3056
|
// matches; without the guard, importing any helper from this module inside
|