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