nlm-memory 0.20.0 → 0.21.0
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 +19 -10
- package/dist/cli/digest.js +60 -1
- package/dist/cli/digest.js.map +1 -1
- package/dist/cli/init.d.ts +12 -0
- package/dist/cli/init.js +49 -0
- package/dist/cli/init.js.map +1 -0
- package/dist/cli/nlm.d.ts +13 -1
- package/dist/cli/nlm.js +872 -61
- package/dist/cli/nlm.js.map +1 -1
- 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 +205 -0
- package/dist/cli/scope-coverage.js.map +1 -0
- package/dist/core/actions/overlay.d.ts +20 -6
- package/dist/core/actions/overlay.js +32 -18
- 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 +36 -0
- package/dist/core/adapters/claude-code.js.map +1 -1
- package/dist/core/classifier/prompt.js +3 -3
- package/dist/core/dataset/build-dataset.js +1 -0
- package/dist/core/dataset/build-dataset.js.map +1 -1
- package/dist/core/digest/compose.d.ts +13 -3
- package/dist/core/digest/compose.js +23 -18
- 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 +79 -0
- package/dist/core/digest/hook-injection.js.map +1 -0
- package/dist/core/embedding/embed-backfill.d.ts +13 -2
- package/dist/core/embedding/embed-backfill.js +181 -43
- package/dist/core/embedding/embed-backfill.js.map +1 -1
- package/dist/core/embedding/embedding-config.d.ts +17 -0
- package/dist/core/embedding/embedding-config.js +12 -0
- package/dist/core/embedding/embedding-config.js.map +1 -0
- package/dist/core/embedding/pg-embed-backfill.d.ts +31 -0
- package/dist/core/embedding/pg-embed-backfill.js +264 -0
- package/dist/core/embedding/pg-embed-backfill.js.map +1 -0
- package/dist/core/entities/dedup-suggest.d.ts +25 -0
- package/dist/core/entities/dedup-suggest.js +107 -0
- package/dist/core/entities/dedup-suggest.js.map +1 -0
- package/dist/core/eval/classifier-fixture-eval.d.ts +29 -0
- package/dist/core/eval/classifier-fixture-eval.js +217 -0
- package/dist/core/eval/classifier-fixture-eval.js.map +1 -0
- package/dist/core/eval/extraction-scoring.d.ts +63 -0
- package/dist/core/eval/extraction-scoring.js +79 -0
- package/dist/core/eval/extraction-scoring.js.map +1 -0
- package/dist/core/eval/run-eval.d.ts +40 -0
- package/dist/core/eval/run-eval.js +34 -0
- package/dist/core/eval/run-eval.js.map +1 -0
- package/dist/core/exemplars/capture-from-session.d.ts +2 -0
- package/dist/core/exemplars/capture-from-session.js +7 -3
- package/dist/core/exemplars/capture-from-session.js.map +1 -1
- package/dist/core/exemplars/diff-parse.d.ts +23 -0
- package/dist/core/exemplars/diff-parse.js +73 -0
- package/dist/core/exemplars/diff-parse.js.map +1 -0
- package/dist/core/exemplars/embed-backfill.d.ts +36 -0
- package/dist/core/exemplars/embed-backfill.js +85 -0
- package/dist/core/exemplars/embed-backfill.js.map +1 -0
- package/dist/core/exemplars/embed-text.d.ts +15 -0
- package/dist/core/exemplars/embed-text.js +18 -0
- package/dist/core/exemplars/embed-text.js.map +1 -0
- package/dist/core/exemplars/extract-exemplar.d.ts +2 -0
- package/dist/core/exemplars/extract-exemplar.js +9 -66
- 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.js +4 -0
- package/dist/core/exemplars/recall-code.js.map +1 -1
- package/dist/core/exemplars/warm-embedder.d.ts +14 -0
- package/dist/core/exemplars/warm-embedder.js +18 -0
- package/dist/core/exemplars/warm-embedder.js.map +1 -0
- package/dist/core/health/corpus-state.d.ts +8 -0
- package/dist/core/health/corpus-state.js +11 -0
- package/dist/core/health/corpus-state.js.map +1 -0
- package/dist/core/health/embed-inflight.d.ts +9 -0
- package/dist/core/health/embed-inflight.js +30 -0
- package/dist/core/health/embed-inflight.js.map +1 -0
- package/dist/core/health/embedding-lane-state.d.ts +6 -0
- package/dist/core/health/embedding-lane-state.js +18 -0
- package/dist/core/health/embedding-lane-state.js.map +1 -0
- package/dist/core/health/warmup-state.d.ts +12 -0
- package/dist/core/health/warmup-state.js +12 -0
- package/dist/core/health/warmup-state.js.map +1 -0
- 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/gate.d.ts +13 -6
- package/dist/core/hook/gate.js +47 -6
- package/dist/core/hook/gate.js.map +1 -1
- package/dist/core/hook/hook-log.d.ts +5 -0
- package/dist/core/hook/hook-log.js.map +1 -1
- package/dist/core/hook/memo.d.ts +12 -0
- package/dist/core/hook/memo.js +41 -1
- package/dist/core/hook/memo.js.map +1 -1
- package/dist/core/hook/pointer-block.d.ts +1 -0
- package/dist/core/hook/pointer-block.js +23 -2
- package/dist/core/hook/pointer-block.js.map +1 -1
- package/dist/core/hook/query-extract.d.ts +2 -1
- package/dist/core/hook/query-extract.js +10 -1
- package/dist/core/hook/query-extract.js.map +1 -1
- package/dist/core/hook/resolve-conversation-by-query.d.ts +28 -0
- package/dist/core/hook/resolve-conversation-by-query.js +98 -0
- package/dist/core/hook/resolve-conversation-by-query.js.map +1 -0
- package/dist/core/hook/select.d.ts +8 -0
- package/dist/core/hook/select.js +9 -2
- package/dist/core/hook/select.js.map +1 -1
- package/dist/core/hook/transcript.js +16 -2
- package/dist/core/hook/transcript.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 +5 -0
- package/dist/core/ingest/ingest-session.js +23 -0
- package/dist/core/ingest/ingest-session.js.map +1 -1
- package/dist/core/ingest/reclassify-oversized.d.ts +5 -0
- package/dist/core/ingest/reclassify-oversized.js +9 -0
- package/dist/core/ingest/reclassify-oversized.js.map +1 -1
- package/dist/core/ingest/reprocess.d.ts +96 -0
- package/dist/core/ingest/reprocess.js +280 -0
- package/dist/core/ingest/reprocess.js.map +1 -0
- 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/integrity/check-invariants.d.ts +6 -1
- package/dist/core/integrity/check-invariants.js +209 -3
- package/dist/core/integrity/check-invariants.js.map +1 -1
- package/dist/core/metrics/corpus-stats.d.ts +49 -0
- package/dist/core/metrics/corpus-stats.js +111 -0
- package/dist/core/metrics/corpus-stats.js.map +1 -0
- package/dist/core/metrics/re-derivation.d.ts +48 -0
- package/dist/core/metrics/re-derivation.js +87 -0
- package/dist/core/metrics/re-derivation.js.map +1 -0
- 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/recall/citation-log.d.ts +8 -0
- package/dist/core/recall/citation-log.js +17 -0
- package/dist/core/recall/citation-log.js.map +1 -1
- package/dist/core/recall/hook-recall-log.d.ts +2 -2
- package/dist/core/recall/hook-recall-log.js +2 -2
- package/dist/core/recall/query-intent.d.ts +7 -0
- package/dist/core/recall/query-intent.js +25 -0
- package/dist/core/recall/query-intent.js.map +1 -0
- package/dist/core/recall/query-log.d.ts +4 -0
- package/dist/core/recall/query-log.js +4 -0
- package/dist/core/recall/query-log.js.map +1 -1
- package/dist/core/recall/recall-service.d.ts +37 -1
- package/dist/core/recall/recall-service.js +106 -63
- package/dist/core/recall/recall-service.js.map +1 -1
- package/dist/core/recall/related-exemplars.d.ts +1 -0
- package/dist/core/recall/related-exemplars.js +4 -1
- package/dist/core/recall/related-exemplars.js.map +1 -1
- package/dist/core/recall/related-facts.js +2 -2
- package/dist/core/recall/related-facts.js.map +1 -1
- package/dist/core/recall/reranker.d.ts +2 -1
- package/dist/core/recall/reranker.js +3 -3
- package/dist/core/recall/reranker.js.map +1 -1
- package/dist/core/recall-facts/fact-query-log.d.ts +7 -0
- package/dist/core/recall-facts/fact-query-log.js +2 -0
- package/dist/core/recall-facts/fact-query-log.js.map +1 -1
- package/dist/core/recall-facts/fact-recall-service.d.ts +9 -1
- package/dist/core/recall-facts/fact-recall-service.js +22 -8
- package/dist/core/recall-facts/fact-recall-service.js.map +1 -1
- package/dist/core/scheduler/scan-once.d.ts +6 -6
- package/dist/core/scheduler/scan-once.js +15 -49
- package/dist/core/scheduler/scan-once.js.map +1 -1
- package/dist/core/scheduler/scheduler.d.ts +10 -0
- package/dist/core/scheduler/scheduler.js +109 -13
- 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 +50 -0
- package/dist/core/signals/code-signal.js +54 -0
- package/dist/core/signals/code-signal.js.map +1 -0
- 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/storage/db-restore.js +3 -1
- package/dist/core/storage/db-restore.js.map +1 -1
- package/dist/core/storage/fact-batch.d.ts +9 -0
- package/dist/core/storage/fact-batch.js +14 -0
- package/dist/core/storage/fact-batch.js.map +1 -0
- package/dist/core/storage/pg-code-exemplar-store.d.ts +1 -0
- package/dist/core/storage/pg-code-exemplar-store.js +13 -2
- package/dist/core/storage/pg-code-exemplar-store.js.map +1 -1
- package/dist/core/storage/pg-embedding-config.d.ts +29 -0
- package/dist/core/storage/pg-embedding-config.js +53 -0
- package/dist/core/storage/pg-embedding-config.js.map +1 -0
- package/dist/core/storage/pg-entity-store.d.ts +12 -0
- package/dist/core/storage/pg-entity-store.js +79 -0
- package/dist/core/storage/pg-entity-store.js.map +1 -0
- package/dist/core/storage/pg-fact-ingest.d.ts +15 -0
- package/dist/core/storage/pg-fact-ingest.js +34 -0
- package/dist/core/storage/pg-fact-ingest.js.map +1 -0
- package/dist/core/storage/pg-fact-store.d.ts +3 -0
- package/dist/core/storage/pg-fact-store.js +26 -30
- package/dist/core/storage/pg-fact-store.js.map +1 -1
- package/dist/core/storage/pg-migrate.d.ts +18 -0
- package/dist/core/storage/pg-migrate.js +73 -0
- package/dist/core/storage/pg-migrate.js.map +1 -0
- package/dist/core/storage/pg-session-store.d.ts +11 -2
- package/dist/core/storage/pg-session-store.js +224 -96
- package/dist/core/storage/pg-session-store.js.map +1 -1
- package/dist/core/storage/pg-signal-store.js +4 -3
- package/dist/core/storage/pg-signal-store.js.map +1 -1
- package/dist/core/storage/pg-storage.d.ts +9 -7
- package/dist/core/storage/pg-storage.js +20 -50
- package/dist/core/storage/pg-storage.js.map +1 -1
- package/dist/core/storage/pg-workstream-store.d.ts +25 -0
- package/dist/core/storage/pg-workstream-store.js +97 -0
- package/dist/core/storage/pg-workstream-store.js.map +1 -0
- package/dist/core/storage/sqlite-code-exemplar-store.d.ts +1 -0
- package/dist/core/storage/sqlite-code-exemplar-store.js +15 -2
- package/dist/core/storage/sqlite-code-exemplar-store.js.map +1 -1
- package/dist/core/storage/sqlite-embedding-config.d.ts +10 -0
- package/dist/core/storage/sqlite-embedding-config.js +27 -0
- package/dist/core/storage/sqlite-embedding-config.js.map +1 -0
- package/dist/core/storage/sqlite-entity-store.d.ts +13 -0
- package/dist/core/storage/sqlite-entity-store.js +96 -0
- package/dist/core/storage/sqlite-entity-store.js.map +1 -0
- package/dist/core/storage/sqlite-fact-store.d.ts +15 -7
- package/dist/core/storage/sqlite-fact-store.js +81 -27
- package/dist/core/storage/sqlite-fact-store.js.map +1 -1
- package/dist/core/storage/sqlite-outcome-store.d.ts +89 -0
- package/dist/core/storage/sqlite-outcome-store.js +158 -0
- package/dist/core/storage/sqlite-outcome-store.js.map +1 -0
- package/dist/core/storage/sqlite-session-store.d.ts +42 -7
- package/dist/core/storage/sqlite-session-store.js +208 -95
- package/dist/core/storage/sqlite-session-store.js.map +1 -1
- package/dist/core/storage/sqlite-signal-store.js +4 -2
- package/dist/core/storage/sqlite-signal-store.js.map +1 -1
- package/dist/core/storage/sqlite-storage.d.ts +8 -8
- package/dist/core/storage/sqlite-storage.js +21 -32
- package/dist/core/storage/sqlite-storage.js.map +1 -1
- package/dist/core/storage/sqlite-workstream-store.d.ts +25 -0
- package/dist/core/storage/sqlite-workstream-store.js +99 -0
- package/dist/core/storage/sqlite-workstream-store.js.map +1 -0
- package/dist/core/storage/supersedence-log.js +2 -0
- 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/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/work-digest/active-spans.d.ts +8 -0
- package/dist/core/work-digest/active-spans.js +29 -0
- package/dist/core/work-digest/active-spans.js.map +1 -0
- package/dist/core/work-digest/attribute.d.ts +15 -0
- package/dist/core/work-digest/attribute.js +76 -0
- package/dist/core/work-digest/attribute.js.map +1 -0
- package/dist/core/work-digest/build-work-digest.d.ts +12 -0
- package/dist/core/work-digest/build-work-digest.js +69 -0
- package/dist/core/work-digest/build-work-digest.js.map +1 -0
- package/dist/core/work-digest/compose-work-digest.d.ts +3 -0
- package/dist/core/work-digest/compose-work-digest.js +52 -0
- package/dist/core/work-digest/compose-work-digest.js.map +1 -0
- package/dist/core/work-digest/merge-active.d.ts +10 -0
- package/dist/core/work-digest/merge-active.js +22 -0
- package/dist/core/work-digest/merge-active.js.map +1 -0
- package/dist/core/work-digest/read-transcript-timestamps.d.ts +7 -0
- package/dist/core/work-digest/read-transcript-timestamps.js +40 -0
- package/dist/core/work-digest/read-transcript-timestamps.js.map +1 -0
- package/dist/core/work-digest/topics.d.ts +18 -0
- package/dist/core/work-digest/topics.js +26 -0
- package/dist/core/work-digest/topics.js.map +1 -0
- package/dist/core/work-digest/types.d.ts +45 -0
- package/dist/core/work-digest/types.js +2 -0
- package/dist/core/work-digest/types.js.map +1 -0
- package/dist/core/workstream/bind.d.ts +26 -0
- package/dist/core/workstream/bind.js +42 -0
- package/dist/core/workstream/bind.js.map +1 -0
- package/dist/core/workstream/compose-recall.d.ts +2 -0
- package/dist/core/workstream/compose-recall.js +39 -0
- package/dist/core/workstream/compose-recall.js.map +1 -0
- package/dist/core/workstream/merge-suggest.d.ts +19 -0
- package/dist/core/workstream/merge-suggest.js +61 -0
- package/dist/core/workstream/merge-suggest.js.map +1 -0
- package/dist/core/workstream/model.d.ts +21 -0
- package/dist/core/workstream/model.js +9 -0
- package/dist/core/workstream/model.js.map +1 -0
- package/dist/core/workstream/name-match.d.ts +8 -0
- package/dist/core/workstream/name-match.js +18 -0
- package/dist/core/workstream/name-match.js.map +1 -0
- package/dist/core/workstream/resolve.d.ts +10 -0
- package/dist/core/workstream/resolve.js +21 -0
- package/dist/core/workstream/resolve.js.map +1 -0
- package/dist/core/workstream/rollup.d.ts +12 -0
- package/dist/core/workstream/rollup.js +17 -0
- package/dist/core/workstream/rollup.js.map +1 -0
- package/dist/core/workstream/work-topics.d.ts +10 -0
- package/dist/core/workstream/work-topics.js +55 -0
- package/dist/core/workstream/work-topics.js.map +1 -0
- package/dist/hook/hook-helpers.d.ts +4 -0
- package/dist/hook/hook-helpers.js +36 -0
- package/dist/hook/hook-helpers.js.map +1 -0
- package/dist/hook/pre-compact-hook.d.ts +23 -0
- package/dist/hook/pre-compact-hook.js +67 -0
- package/dist/hook/pre-compact-hook.js.map +1 -0
- package/dist/hook/prompt-recall-hook.d.ts +38 -0
- package/dist/hook/prompt-recall-hook.js +124 -20
- package/dist/hook/prompt-recall-hook.js.map +1 -1
- package/dist/hook/recall-gate.d.ts +29 -0
- package/dist/hook/recall-gate.js +50 -0
- package/dist/hook/recall-gate.js.map +1 -0
- package/dist/hook/recall-over-http.d.ts +3 -2
- package/dist/hook/recall-over-http.js +26 -13
- package/dist/hook/recall-over-http.js.map +1 -1
- package/dist/hook/recent-context.d.ts +25 -0
- package/dist/hook/recent-context.js +104 -0
- package/dist/hook/recent-context.js.map +1 -0
- package/dist/hook/score-floor.d.ts +27 -0
- package/dist/hook/score-floor.js +42 -0
- package/dist/hook/score-floor.js.map +1 -0
- package/dist/hook/session-end-hook.js +8 -29
- package/dist/hook/session-end-hook.js.map +1 -1
- package/dist/hook/session-start-hook.js +26 -60
- package/dist/hook/session-start-hook.js.map +1 -1
- package/dist/hook/stop-hook.js +24 -60
- package/dist/hook/stop-hook.js.map +1 -1
- package/dist/http/app.d.ts +6 -0
- package/dist/http/app.js +52 -44
- package/dist/http/app.js.map +1 -1
- package/dist/install/setup.js +2 -0
- package/dist/install/setup.js.map +1 -1
- package/dist/llm/build-classifier.d.ts +22 -0
- package/dist/llm/build-classifier.js +42 -0
- package/dist/llm/build-classifier.js.map +1 -0
- package/dist/llm/build-embedder.d.ts +11 -0
- package/dist/llm/build-embedder.js +42 -0
- package/dist/llm/build-embedder.js.map +1 -0
- package/dist/llm/bundled-embedder-client.d.ts +37 -0
- package/dist/llm/bundled-embedder-client.js +90 -0
- package/dist/llm/bundled-embedder-client.js.map +1 -0
- package/dist/llm/classifier-box.d.ts +13 -5
- package/dist/llm/classifier-box.js +28 -6
- package/dist/llm/classifier-box.js.map +1 -1
- package/dist/llm/classifier-egress.d.ts +18 -0
- package/dist/llm/classifier-egress.js +59 -0
- package/dist/llm/classifier-egress.js.map +1 -0
- package/dist/llm/client-shared.d.ts +4 -0
- package/dist/llm/client-shared.js +39 -0
- package/dist/llm/client-shared.js.map +1 -0
- package/dist/llm/deepseek-client.d.ts +20 -0
- package/dist/llm/deepseek-client.js +59 -45
- package/dist/llm/deepseek-client.js.map +1 -1
- package/dist/llm/embedder-info.d.ts +15 -0
- package/dist/llm/embedder-info.js +27 -0
- package/dist/llm/embedder-info.js.map +1 -0
- package/dist/llm/model-quirks.d.ts +4 -0
- package/dist/llm/model-quirks.js +7 -0
- package/dist/llm/model-quirks.js.map +1 -0
- package/dist/llm/naming.d.ts +5 -0
- package/dist/llm/naming.js +20 -0
- package/dist/llm/naming.js.map +1 -0
- package/dist/llm/ollama-client.d.ts +7 -8
- package/dist/llm/ollama-client.js +59 -51
- package/dist/llm/ollama-client.js.map +1 -1
- package/dist/llm/ollama-code-embedder.d.ts +8 -3
- package/dist/llm/ollama-code-embedder.js +14 -24
- package/dist/llm/ollama-code-embedder.js.map +1 -1
- package/dist/llm/openai-code-embedder-client.d.ts +25 -0
- package/dist/llm/openai-code-embedder-client.js +52 -0
- package/dist/llm/openai-code-embedder-client.js.map +1 -0
- package/dist/llm/openai-embedder-client.d.ts +41 -0
- package/dist/llm/openai-embedder-client.js +83 -0
- package/dist/llm/openai-embedder-client.js.map +1 -0
- package/dist/mcp/server.d.ts +69 -2
- package/dist/mcp/server.js +404 -19
- package/dist/mcp/server.js.map +1 -1
- package/dist/ports/code-embedder.d.ts +6 -1
- package/dist/ports/code-exemplar-store.d.ts +5 -0
- package/dist/ports/entity-store.d.ts +35 -0
- package/dist/ports/entity-store.js +7 -0
- package/dist/ports/entity-store.js.map +1 -0
- package/dist/ports/fact-store.d.ts +0 -0
- package/dist/ports/fact-store.js.map +1 -1
- package/dist/ports/llm-client.d.ts +9 -1
- 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 +12 -6
- package/dist/ports/storage.d.ts +7 -30
- package/dist/ports/storage.js +3 -7
- package/dist/ports/storage.js.map +1 -1
- package/dist/ports/transcript-adapter.d.ts +2 -0
- package/dist/ports/workstream-store.d.ts +22 -0
- package/dist/ports/workstream-store.js +2 -0
- package/dist/ports/workstream-store.js.map +1 -0
- 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 +56 -2
- package/dist/shared/types.js +4 -1
- package/dist/shared/types.js.map +1 -1
- package/dist/ui/assets/{index-CMpQkbQy.js → index-CSaBtgXD.js} +1 -1
- package/dist/ui/index.html +1 -1
- package/fixtures/classifier-gold/README.md +94 -0
- package/fixtures/classifier-gold/reference.json +318 -0
- package/fixtures/classifier-gold/transcripts/01-bug-fix-null-pointer-api.txt +75 -0
- package/fixtures/classifier-gold/transcripts/02-bug-fix-race-condition-queue.txt +77 -0
- package/fixtures/classifier-gold/transcripts/03-bug-fix-jwt-clock-skew.txt +86 -0
- package/fixtures/classifier-gold/transcripts/04-feature-webhook-retry.txt +95 -0
- package/fixtures/classifier-gold/transcripts/05-feature-csv-export.txt +86 -0
- package/fixtures/classifier-gold/transcripts/06-feature-rate-limiting.txt +61 -0
- package/fixtures/classifier-gold/transcripts/07-refactor-db-abstraction.txt +72 -0
- package/fixtures/classifier-gold/transcripts/08-refactor-config-loading.txt +89 -0
- package/fixtures/classifier-gold/transcripts/09-ops-docker-staging.txt +94 -0
- package/fixtures/classifier-gold/transcripts/10-ops-nginx-ssl.txt +121 -0
- package/fixtures/classifier-gold/transcripts/11-ops-postgres-migration.txt +74 -0
- package/fixtures/classifier-gold/transcripts/12-ops-cron-debugging.txt +73 -0
- package/fixtures/classifier-gold/transcripts/13-research-vector-db-comparison.txt +59 -0
- package/fixtures/classifier-gold/transcripts/14-research-oauth-provider.txt +74 -0
- package/fixtures/classifier-gold/transcripts/15-writing-api-docs.txt +154 -0
- package/fixtures/classifier-gold/transcripts/16-meeting-sprint-planning.txt +69 -0
- package/fixtures/classifier-gold/transcripts/17-trivial-list-files.txt +14 -0
- package/fixtures/classifier-gold/transcripts/18-trivial-version-check.txt +21 -0
- package/fixtures/classifier-gold/transcripts/19-trivial-syntax-help.txt +29 -0
- package/fixtures/classifier-gold/transcripts/20-trivial-read-package-json.txt +41 -0
- package/migrations/000_initial_schema.sql +1 -1
- package/migrations/024_backfill_exemplar_repo_basename.sql +32 -0
- package/migrations/025_workstreams.sql +30 -0
- package/migrations/026_embedding_config.sql +12 -0
- package/migrations/027_drop_legacy_session_embeddings.sql +7 -0
- package/migrations/028_classifier_provenance.sql +5 -0
- package/migrations/029_project_scope.sql +21 -0
- package/migrations/032_session_derivables.sql +11 -0
- package/migrations/pg/001_initial.sql +34 -0
- package/migrations/pg/019_split_replaces.sql +16 -7
- package/migrations/pg/025_workstreams.sql +34 -0
- package/migrations/pg/026_embedding_config.sql +12 -0
- package/migrations/pg/028_classifier_provenance.sql +5 -0
- package/migrations/pg/029_entity_variants.sql +9 -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/nlm/index.js +405 -39
- package/package.json +12 -2
- package/plugin/.codex-plugin/plugin.json +2 -2
- package/plugin/scripts/prompt-recall-hook.mjs +413 -43
- package/plugin/scripts/session-start-hook.mjs +295 -61
- package/plugin/scripts/stop-hook.mjs +89 -66
- package/plugin-hermes-agent/__init__.py +2 -1
- package/templates/agent-contract/README.md +24 -0
- package/templates/agent-contract/claude-code.md +40 -0
- package/templates/agent-contract/generic.md +38 -0
- package/dist/core/embedding/embed-normalize.d.ts +0 -28
- package/dist/core/embedding/embed-normalize.js +0 -98
- package/dist/core/embedding/embed-normalize.js.map +0 -1
- package/dist/core/storage/pg-tx-context.d.ts +0 -52
- package/dist/core/storage/pg-tx-context.js +0 -149
- package/dist/core/storage/pg-tx-context.js.map +0 -1
package/dist/mcp/server.js
CHANGED
|
@@ -15,8 +15,22 @@ import { z } from "zod";
|
|
|
15
15
|
import { logQuery } from "../core/recall/query-log.js";
|
|
16
16
|
import { logFactQuery } from "../core/recall-facts/fact-query-log.js";
|
|
17
17
|
import { appendCitation } from "../core/recall/citation-log.js";
|
|
18
|
+
import { resolveConversationForSession } from "../core/hook/memo.js";
|
|
19
|
+
import { resolveConversationByQuery } from "../core/hook/resolve-conversation-by-query.js";
|
|
18
20
|
import { appendFactSupersedence, appendSupersedence, readSupersedenceLog } from "../core/storage/supersedence-log.js";
|
|
19
21
|
import { recallCode } from "../core/exemplars/recall-code.js";
|
|
22
|
+
import { buildWorkDigest } from "../core/work-digest/build-work-digest.js";
|
|
23
|
+
import { composeWorkDigest } from "../core/work-digest/compose-work-digest.js";
|
|
24
|
+
import { rollupWorkstream } from "../core/workstream/rollup.js";
|
|
25
|
+
import { composeWorkstreamRecall } from "../core/workstream/compose-recall.js";
|
|
26
|
+
import { normalizeLabel } from "../core/workstream/model.js";
|
|
27
|
+
import { resolveWorkstreamId } from "../core/workstream/resolve.js";
|
|
28
|
+
import { suggestMerges } from "../core/workstream/merge-suggest.js";
|
|
29
|
+
import { normalizeSignal } from "../core/signals/ingest-signal.js";
|
|
30
|
+
import { stampSignalScope } from "../core/signals/stamp-scope.js";
|
|
31
|
+
import { deriveOutcome } from "../core/outcome/rollup.js";
|
|
32
|
+
import { buildSqliteOutcomeDeps } from "../core/storage/sqlite-outcome-store.js";
|
|
33
|
+
import { SIGNAL_OUTCOMES } from "../shared/types.js";
|
|
20
34
|
const CHARACTER_LIMIT = 25_000;
|
|
21
35
|
const DEFAULT_LIMIT = 10;
|
|
22
36
|
const SERVER_NAME = "nlm-memory-mcp-server";
|
|
@@ -47,6 +61,9 @@ function truncate(data) {
|
|
|
47
61
|
function ok(data) {
|
|
48
62
|
return { content: [{ type: "text", text: truncate(data) }] };
|
|
49
63
|
}
|
|
64
|
+
function okText(text) {
|
|
65
|
+
return { content: [{ type: "text", text }] };
|
|
66
|
+
}
|
|
50
67
|
function err(error) {
|
|
51
68
|
const message = error instanceof Error ? error.message : String(error);
|
|
52
69
|
return {
|
|
@@ -63,12 +80,23 @@ function mcpRewriteDefault() {
|
|
|
63
80
|
return true;
|
|
64
81
|
return raw !== "0" && raw.toLowerCase() !== "false";
|
|
65
82
|
}
|
|
66
|
-
|
|
83
|
+
/**
|
|
84
|
+
* Normalize an MCP client's self-reported identity (from the initialize
|
|
85
|
+
* handshake's clientInfo) into a runtime attribution string for the recall
|
|
86
|
+
* query-log. Returns null when no usable name is present, so the log stays
|
|
87
|
+
* honest about unknown callers. Trim + lowercase keeps aggregation clean
|
|
88
|
+
* (claude-code, cursor, hermes) and matches the hook-side runtime convention.
|
|
89
|
+
*/
|
|
90
|
+
export function mcpRuntimeFromClient(client) {
|
|
91
|
+
const name = client?.name?.trim().toLowerCase();
|
|
92
|
+
return name ? name : null;
|
|
93
|
+
}
|
|
94
|
+
export async function recallSessionsHandler(deps, input, runtime = null) {
|
|
67
95
|
try {
|
|
68
96
|
const rewrite = input.rewrite ?? mcpRewriteDefault();
|
|
69
97
|
const query = {
|
|
70
98
|
query: input.query ?? "",
|
|
71
|
-
mode: input.mode ?? "
|
|
99
|
+
mode: input.mode ?? "keyword",
|
|
72
100
|
limit: input.limit ?? DEFAULT_LIMIT,
|
|
73
101
|
rewrite,
|
|
74
102
|
// Investigative surface: include superseded sessions, down-ranked and
|
|
@@ -77,21 +105,24 @@ export async function recallSessionsHandler(deps, input) {
|
|
|
77
105
|
includeSuperseded: true,
|
|
78
106
|
...(input.entity !== undefined ? { entity: input.entity } : {}),
|
|
79
107
|
...(input.kind !== undefined ? { kind: input.kind } : {}),
|
|
108
|
+
...(input.workstream !== undefined ? { workstream: input.workstream } : {}),
|
|
80
109
|
};
|
|
81
110
|
const result = await deps.recall.search(query);
|
|
111
|
+
const conversationId = resolveConversationByQuery(input.query ?? "") ?? undefined;
|
|
82
112
|
// Telemetry — the MCP path is the real agent-usage path; without this it
|
|
83
113
|
// is invisible to query_log.jsonl and the Recall page. Fire-and-forget,
|
|
84
114
|
// mirrors the HTTP /api/recall handler.
|
|
85
115
|
void logQuery({
|
|
86
116
|
source: "mcp",
|
|
87
|
-
runtime
|
|
117
|
+
runtime,
|
|
88
118
|
query: input.query ?? null,
|
|
89
119
|
entity: input.entity ?? null,
|
|
90
120
|
kind: input.kind ?? null,
|
|
91
|
-
mode: input.mode ?? "
|
|
121
|
+
mode: input.mode ?? "keyword",
|
|
92
122
|
limit: input.limit ?? DEFAULT_LIMIT,
|
|
93
123
|
nResults: result.total,
|
|
94
124
|
returnedIds: result.results.map((r) => r.id),
|
|
125
|
+
...(conversationId !== undefined ? { conversationId } : {}),
|
|
95
126
|
});
|
|
96
127
|
return ok(result);
|
|
97
128
|
}
|
|
@@ -99,6 +130,151 @@ export async function recallSessionsHandler(deps, input) {
|
|
|
99
130
|
return err(e);
|
|
100
131
|
}
|
|
101
132
|
}
|
|
133
|
+
export async function workSummaryHandler(deps, input) {
|
|
134
|
+
if (!deps.workDigest) {
|
|
135
|
+
return okText("work_summary is not available in this deployment.");
|
|
136
|
+
}
|
|
137
|
+
try {
|
|
138
|
+
const date = input.date ?? localToday();
|
|
139
|
+
const digest = await buildWorkDigest(deps.workDigest, date);
|
|
140
|
+
return okText(composeWorkDigest(digest));
|
|
141
|
+
}
|
|
142
|
+
catch (e) {
|
|
143
|
+
return err(e);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
export async function recallWorkstreamHandler(deps, input, runtime = null) {
|
|
147
|
+
if (!deps.workstreams)
|
|
148
|
+
return okText("recall_workstream is not available in this deployment.");
|
|
149
|
+
try {
|
|
150
|
+
const idOrLabel = (input.idOrLabel ?? "").trim();
|
|
151
|
+
if (!idOrLabel)
|
|
152
|
+
return okText("Provide a workstream id or label.");
|
|
153
|
+
const conversationId = resolveConversationByQuery(idOrLabel) ?? undefined;
|
|
154
|
+
const ws = deps.workstreams.store;
|
|
155
|
+
const found = (await ws.getById(idOrLabel)) ?? (await ws.findByNormalizedLabel(normalizeLabel(idOrLabel)));
|
|
156
|
+
if (!found)
|
|
157
|
+
return okText(`No workstream matches "${idOrLabel}".`);
|
|
158
|
+
const view = await rollupWorkstream({ workstreams: deps.workstreams.store, sessions: deps.workstreams.sessions, facts: deps.workstreams.facts, exemplars: deps.workstreams.exemplars }, found.id);
|
|
159
|
+
if (!view)
|
|
160
|
+
return okText(`No workstream matches "${idOrLabel}".`);
|
|
161
|
+
void logQuery({
|
|
162
|
+
source: "mcp",
|
|
163
|
+
runtime,
|
|
164
|
+
query: idOrLabel,
|
|
165
|
+
entity: null,
|
|
166
|
+
kind: null,
|
|
167
|
+
mode: "keyword",
|
|
168
|
+
limit: 1,
|
|
169
|
+
nResults: 1,
|
|
170
|
+
returnedIds: [found.id],
|
|
171
|
+
...(conversationId !== undefined ? { conversationId } : {}),
|
|
172
|
+
});
|
|
173
|
+
return okText(composeWorkstreamRecall(view));
|
|
174
|
+
}
|
|
175
|
+
catch (e) {
|
|
176
|
+
return err(e);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
/** idOrLabel -> live survivor workstream (merged_into resolved) | null. One source of truth for lifecycle handlers. */
|
|
180
|
+
async function resolveWorkstream(store, idOrLabel) {
|
|
181
|
+
const found = (await store.getById(idOrLabel)) ?? (await store.findByNormalizedLabel(normalizeLabel(idOrLabel)));
|
|
182
|
+
if (!found)
|
|
183
|
+
return null;
|
|
184
|
+
const all = await store.listAll();
|
|
185
|
+
const byId = new Map(all.map((w) => [w.id, { id: w.id, mergedInto: w.mergedInto }]));
|
|
186
|
+
const survivorId = resolveWorkstreamId(found.id, byId);
|
|
187
|
+
return survivorId === found.id ? found : ((await store.getById(survivorId)) ?? found);
|
|
188
|
+
}
|
|
189
|
+
export async function renameWorkstreamHandler(deps, input) {
|
|
190
|
+
if (!deps.workstreams)
|
|
191
|
+
return okText("rename_workstream is not available in this deployment.");
|
|
192
|
+
try {
|
|
193
|
+
const idOrLabel = (input.idOrLabel ?? "").trim();
|
|
194
|
+
const label = (input.label ?? "").trim();
|
|
195
|
+
if (!idOrLabel || !label)
|
|
196
|
+
return okText("Provide the workstream (id or label) and the new label.");
|
|
197
|
+
const ws = deps.workstreams.store;
|
|
198
|
+
const target = await resolveWorkstream(ws, idOrLabel);
|
|
199
|
+
if (!target)
|
|
200
|
+
return okText(`No workstream matches "${idOrLabel}".`);
|
|
201
|
+
const collision = await ws.findByNormalizedLabel(normalizeLabel(label));
|
|
202
|
+
if (collision && collision.id !== target.id) {
|
|
203
|
+
return okText(`Label "${label}" is already used by workstream "${collision.label}" (${collision.id}).`);
|
|
204
|
+
}
|
|
205
|
+
await ws.setLabel(target.id, label);
|
|
206
|
+
return okText(`Renamed workstream ${target.id}: "${target.label}" -> "${label}".`);
|
|
207
|
+
}
|
|
208
|
+
catch (e) {
|
|
209
|
+
return err(e);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
export async function retireWorkstreamHandler(deps, input) {
|
|
213
|
+
if (!deps.workstreams)
|
|
214
|
+
return okText("retire_workstream is not available in this deployment.");
|
|
215
|
+
try {
|
|
216
|
+
const idOrLabel = (input.idOrLabel ?? "").trim();
|
|
217
|
+
if (!idOrLabel)
|
|
218
|
+
return okText("Provide a workstream id or label.");
|
|
219
|
+
const target = await resolveWorkstream(deps.workstreams.store, idOrLabel);
|
|
220
|
+
if (!target)
|
|
221
|
+
return okText(`No workstream matches "${idOrLabel}".`);
|
|
222
|
+
await deps.workstreams.store.setStatus(target.id, "retired");
|
|
223
|
+
return okText(`Retired workstream "${target.label}" (${target.id}).`);
|
|
224
|
+
}
|
|
225
|
+
catch (e) {
|
|
226
|
+
return err(e);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
export async function mergeWorkstreamsHandler(deps, input) {
|
|
230
|
+
if (!deps.workstreams)
|
|
231
|
+
return okText("merge_workstreams is not available in this deployment.");
|
|
232
|
+
try {
|
|
233
|
+
const fromArg = (input.from ?? "").trim();
|
|
234
|
+
const intoArg = (input.into ?? "").trim();
|
|
235
|
+
if (!fromArg || !intoArg)
|
|
236
|
+
return okText("Provide both `from` and `into` (workstream id or label).");
|
|
237
|
+
const ws = deps.workstreams.store;
|
|
238
|
+
const from = await resolveWorkstream(ws, fromArg);
|
|
239
|
+
const into = await resolveWorkstream(ws, intoArg);
|
|
240
|
+
if (!from)
|
|
241
|
+
return okText(`No workstream matches "${fromArg}".`);
|
|
242
|
+
if (!into)
|
|
243
|
+
return okText(`No workstream matches "${intoArg}".`);
|
|
244
|
+
if (from.id === into.id)
|
|
245
|
+
return okText(`"${fromArg}" and "${intoArg}" resolve to the same workstream — nothing to merge.`);
|
|
246
|
+
await ws.merge(from.id, into.id);
|
|
247
|
+
return okText(`Merged "${from.label}" (${from.id}) into "${into.label}" (${into.id}).`);
|
|
248
|
+
}
|
|
249
|
+
catch (e) {
|
|
250
|
+
return err(e);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
export async function rebindSessionHandler(deps, input) {
|
|
254
|
+
if (!deps.workstreams)
|
|
255
|
+
return okText("rebind_session is not available in this deployment.");
|
|
256
|
+
try {
|
|
257
|
+
const sessionId = (input.sessionId ?? "").trim();
|
|
258
|
+
const wsArg = (input.workstream ?? "").trim();
|
|
259
|
+
if (!sessionId || !wsArg)
|
|
260
|
+
return okText("Provide both a sessionId and a workstream (id or label).");
|
|
261
|
+
const ws = await resolveWorkstream(deps.workstreams.store, wsArg);
|
|
262
|
+
if (!ws)
|
|
263
|
+
return okText(`No workstream matches "${wsArg}".`);
|
|
264
|
+
await deps.store.setWorkstreamBinding(sessionId, ws.id, "operator", null);
|
|
265
|
+
return okText(`Rebound session ${sessionId} -> workstream "${ws.label}" (${ws.id}).`);
|
|
266
|
+
}
|
|
267
|
+
catch (e) {
|
|
268
|
+
return err(e);
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
/** YYYY-MM-DD for "today" in the process timezone. */
|
|
272
|
+
function localToday() {
|
|
273
|
+
const d = new Date();
|
|
274
|
+
const mo = String(d.getMonth() + 1).padStart(2, "0");
|
|
275
|
+
const da = String(d.getDate()).padStart(2, "0");
|
|
276
|
+
return `${d.getFullYear()}-${mo}-${da}`;
|
|
277
|
+
}
|
|
102
278
|
export async function getSessionHandler(deps, input) {
|
|
103
279
|
try {
|
|
104
280
|
const session = await deps.store.getById(input.id);
|
|
@@ -135,13 +311,25 @@ export async function getSessionHandler(deps, input) {
|
|
|
135
311
|
};
|
|
136
312
|
})()
|
|
137
313
|
: null;
|
|
138
|
-
|
|
314
|
+
// Isolated: the outcome rollup is enrichment, not the payload. A throw
|
|
315
|
+
// here (SQLITE_BUSY, stale handle, unmigrated db) must degrade to
|
|
316
|
+
// no-outcome, never fail the whole get_session call.
|
|
317
|
+
let outcome;
|
|
318
|
+
if (deps.outcomeDb) {
|
|
319
|
+
try {
|
|
320
|
+
outcome = await deriveOutcome(session.id, await buildSqliteOutcomeDeps(deps.outcomeDb));
|
|
321
|
+
}
|
|
322
|
+
catch {
|
|
323
|
+
outcome = undefined;
|
|
324
|
+
}
|
|
325
|
+
}
|
|
326
|
+
return ok({ ...session, supersedes, supersededBy, ...(outcome ? { outcome } : {}) });
|
|
139
327
|
}
|
|
140
328
|
catch (e) {
|
|
141
329
|
return err(e);
|
|
142
330
|
}
|
|
143
331
|
}
|
|
144
|
-
export async function recallFactsHandler(deps, input) {
|
|
332
|
+
export async function recallFactsHandler(deps, input, runtime = null) {
|
|
145
333
|
if (!deps.factRecall) {
|
|
146
334
|
return err(new Error("fact recall not wired in this deployment"));
|
|
147
335
|
}
|
|
@@ -161,9 +349,11 @@ export async function recallFactsHandler(deps, input) {
|
|
|
161
349
|
: {}),
|
|
162
350
|
};
|
|
163
351
|
const result = await deps.factRecall.search(query);
|
|
352
|
+
const conversationId = resolveConversationByQuery(input.query ?? "") ?? undefined;
|
|
164
353
|
// Telemetry — see recallSessionsHandler. Fire-and-forget.
|
|
165
354
|
void logFactQuery({
|
|
166
355
|
source: "mcp",
|
|
356
|
+
runtime,
|
|
167
357
|
query: input.query ?? null,
|
|
168
358
|
subject: input.subject ?? null,
|
|
169
359
|
predicate: input.predicate ?? null,
|
|
@@ -172,6 +362,7 @@ export async function recallFactsHandler(deps, input) {
|
|
|
172
362
|
limit: input.limit ?? DEFAULT_LIMIT,
|
|
173
363
|
nResults: result.total,
|
|
174
364
|
returnedIds: result.results.map((r) => r.id),
|
|
365
|
+
...(conversationId !== undefined ? { conversationId } : {}),
|
|
175
366
|
});
|
|
176
367
|
return ok(result);
|
|
177
368
|
}
|
|
@@ -191,7 +382,7 @@ export async function getFactHistoryHandler(deps, input) {
|
|
|
191
382
|
return err(e);
|
|
192
383
|
}
|
|
193
384
|
}
|
|
194
|
-
const CITE_SESSION_DESCRIPTION = `Log that you used a previously-surfaced session in your response. Pass the session ID. This
|
|
385
|
+
const CITE_SESSION_DESCRIPTION = `Log that you used a previously-surfaced session in your response. Pass the session ID. This feeds the recall precision metric, which measures what fraction of surfaced sessions were actually useful. Call after writing your response, with one cite per surfaced session you actually drew from.`;
|
|
195
386
|
const RECALL_DESCRIPTION = `Search prior AI sessions across every runtime the user has connected (Claude Code,
|
|
196
387
|
Hermes, pi, Codex, Gemini, Aider). Local-first, fast (~200-400ms warm), idempotent,
|
|
197
388
|
safe to call eagerly. No rate limit; no cost per call.
|
|
@@ -232,7 +423,7 @@ Skip ONLY when the request is purely forward-looking with no plausible prior
|
|
|
232
423
|
context — drafting wholly new content, naming something new, brainstorming
|
|
233
424
|
greenfield ideas. When in doubt, call.
|
|
234
425
|
|
|
235
|
-
When you reference a returned session in your response, call \`cite_session(id)\` to log it so
|
|
426
|
+
When you reference a returned session in your response, call \`cite_session(id)\` to log it so NLM can measure which surfaced sessions are useful.
|
|
236
427
|
|
|
237
428
|
Args:
|
|
238
429
|
- query: keyword(s) to search. Token-weighted match against label, decisions,
|
|
@@ -240,8 +431,8 @@ Args:
|
|
|
240
431
|
- entity: filter to sessions tagged with this entity. Optional.
|
|
241
432
|
- kind: "decision" or "open" — restrict to sessions containing that marker
|
|
242
433
|
kind. Omit for any. Optional.
|
|
243
|
-
- mode:
|
|
244
|
-
|
|
434
|
+
- mode: Defaults to keyword (FTS5 BM25); hybrid and semantic are available.
|
|
435
|
+
Optional.
|
|
245
436
|
- limit: max results (1-100, default 10).`;
|
|
246
437
|
const GET_SESSION_DESCRIPTION = `Fetch one full session by its canonical ID, including the conversational body.
|
|
247
438
|
|
|
@@ -253,6 +444,10 @@ run, or any quote you intend to reference verbatim.
|
|
|
253
444
|
The recall_sessions digest is optimized for ranking and scanning; the full body
|
|
254
445
|
contains the actual conversation transcript that produced the decision.
|
|
255
446
|
|
|
447
|
+
When wired, the response includes an \`outcome\` object: whether the session's
|
|
448
|
+
decision held, was overturned, was built upon, was re-derived later, or
|
|
449
|
+
remains unobserved, plus the evidence tier/confidence behind that verdict.
|
|
450
|
+
|
|
256
451
|
Args:
|
|
257
452
|
- id: Canonical session ID returned by recall_sessions (e.g. "cc_abc123",
|
|
258
453
|
"sess_pgvector"). Pass the id field from the recall_sessions result.`;
|
|
@@ -281,12 +476,12 @@ Superseded facts are excluded by default; call get_fact_history to walk
|
|
|
281
476
|
the chain of how a value evolved ("when did X flip from Fastify to Hono?").
|
|
282
477
|
|
|
283
478
|
Examples:
|
|
284
|
-
recall_facts(subject="
|
|
285
|
-
→ the model alias currently exposed on the
|
|
479
|
+
recall_facts(subject="local-llm-host", predicate="model")
|
|
480
|
+
→ the model alias currently exposed on the local LLM endpoint
|
|
286
481
|
recall_facts(subject="nlm-memory-ts", predicate="framework")
|
|
287
482
|
→ the web framework picked for nlm-memory-ts
|
|
288
|
-
recall_facts(subject="
|
|
289
|
-
→ all known facts about the
|
|
483
|
+
recall_facts(subject="acme-client")
|
|
484
|
+
→ all known facts about the Acme engagement
|
|
290
485
|
recall_facts(query="routing", kind="decision")
|
|
291
486
|
→ recent decision-kind facts mentioning routing
|
|
292
487
|
|
|
@@ -416,7 +611,9 @@ export async function citeSessionHandler(input) {
|
|
|
416
611
|
}
|
|
417
612
|
try {
|
|
418
613
|
await appendCitation({
|
|
419
|
-
|
|
614
|
+
// Agents rarely pass conversation_id; resolve it server-side from the
|
|
615
|
+
// surfaced-memo so the citation joins to its hook fire (NLM #345).
|
|
616
|
+
conversationId: input.conversation_id ?? resolveConversationForSession(input.id) ?? "mcp_tool",
|
|
420
617
|
citedId: input.id,
|
|
421
618
|
kind: "tool_use",
|
|
422
619
|
...(input.reason !== undefined ? { responsePreview: input.reason } : {}),
|
|
@@ -427,6 +624,81 @@ export async function citeSessionHandler(input) {
|
|
|
427
624
|
return err(e);
|
|
428
625
|
}
|
|
429
626
|
}
|
|
627
|
+
const REPORT_OUTCOME_DESCRIPTION = `Record whether a session's decision held up or was overturned. THIS IS THE CONTRACT SURFACE for outcome signals: it writes the same append-only \`signals\` row that POST /api/signal writes (producer "mcp"), just over MCP instead of HTTP.
|
|
628
|
+
|
|
629
|
+
Expected callers are deterministic producers: hooks, CI pipelines, and automation platforms confirming a build/deploy/test result against a prior session. Agents rarely self-report their own outcome; outcome should come from independent evidence, not the agent's opinion of its own work.
|
|
630
|
+
|
|
631
|
+
Args:
|
|
632
|
+
- session_id or correlation_key: at least one required. session_id is the NLM session this outcome applies to. correlation_key is an external identifier (e.g. a CI run id) to use when no NLM session_id is known; it is recorded on the row for traceability but is not (yet) joined into the Tier-B outcome rollup.
|
|
633
|
+
- outcome: one of pass | fail | fix | exhausted, the same vocabulary POST /api/signal accepts.
|
|
634
|
+
- source_of_record: required. Identifies the deterministic system asserting this outcome (e.g. "ci:github-actions", "hook:post-tool-use", "n8n:deploy-workflow").
|
|
635
|
+
- detail: optional passthrough object for additional context.`;
|
|
636
|
+
/** The kind stamped on rows this tool writes. Loosely, this row represents an
|
|
637
|
+
* outcome *review* of a prior session's decision, and "review" is the closest
|
|
638
|
+
* fit among the existing SignalKind vocabulary (gate|eval|review|test); no
|
|
639
|
+
* literal "outcome" kind exists (see rollup.ts's own note on this same
|
|
640
|
+
* ambiguity). Tier-A correlation in rollup.ts keys on session_id only, so
|
|
641
|
+
* this choice does not affect verdict derivation. */
|
|
642
|
+
const REPORT_OUTCOME_KIND = "review";
|
|
643
|
+
export async function reportOutcomeHandler(deps, input) {
|
|
644
|
+
if (!input.session_id && !input.correlation_key) {
|
|
645
|
+
return err(new Error("session_id or correlation_key is required"));
|
|
646
|
+
}
|
|
647
|
+
if (!input.source_of_record || input.source_of_record.length === 0) {
|
|
648
|
+
return err(new Error("source_of_record is required"));
|
|
649
|
+
}
|
|
650
|
+
if (!deps.signalStore || deps.installScope === undefined) {
|
|
651
|
+
return err(new Error("signal store not available"));
|
|
652
|
+
}
|
|
653
|
+
try {
|
|
654
|
+
let signal = normalizeSignal({
|
|
655
|
+
kind: REPORT_OUTCOME_KIND,
|
|
656
|
+
producer: "mcp",
|
|
657
|
+
outcome: input.outcome,
|
|
658
|
+
session: input.session_id ?? null,
|
|
659
|
+
detail: {
|
|
660
|
+
...(input.detail ?? {}),
|
|
661
|
+
source_of_record: input.source_of_record,
|
|
662
|
+
...(input.correlation_key ? { correlation_key: input.correlation_key } : {}),
|
|
663
|
+
},
|
|
664
|
+
}, deps.installScope);
|
|
665
|
+
signal = await stampSignalScope(signal, { sessionScopeReader: deps.sessionScopeReader });
|
|
666
|
+
await deps.signalStore.insert(signal);
|
|
667
|
+
return ok({ recorded: true, id: signal.id, outcome: signal.outcome });
|
|
668
|
+
}
|
|
669
|
+
catch (e) {
|
|
670
|
+
return err(e);
|
|
671
|
+
}
|
|
672
|
+
}
|
|
673
|
+
export async function listMergeSuggestionsHandler(deps, input) {
|
|
674
|
+
if (!deps.workstreams)
|
|
675
|
+
return okText("list_merge_suggestions is not available in this deployment.");
|
|
676
|
+
try {
|
|
677
|
+
const minScore = typeof input.minScore === "number" ? input.minScore : 0.5;
|
|
678
|
+
const all = (await deps.workstreams.store.listAll()).filter((w) => w.status === "active");
|
|
679
|
+
if (all.length < 2)
|
|
680
|
+
return okText("Not enough active workstreams to suggest merges.");
|
|
681
|
+
const ids = all.map((w) => w.id);
|
|
682
|
+
const entMap = await deps.workstreams.store.entitiesFor(ids);
|
|
683
|
+
const items = await Promise.all(all.map(async (w) => ({
|
|
684
|
+
id: w.id, label: w.label,
|
|
685
|
+
entities: entMap.get(w.id) ?? [],
|
|
686
|
+
sessionIds: await deps.workstreams.sessions.listSessionIdsByWorkstreams([w.id]),
|
|
687
|
+
})));
|
|
688
|
+
const suggestions = suggestMerges(items, minScore);
|
|
689
|
+
if (suggestions.length === 0)
|
|
690
|
+
return okText(`No merge suggestions at or above score ${minScore}.`);
|
|
691
|
+
const lines = ["MERGE SUGGESTIONS:"];
|
|
692
|
+
for (const s of suggestions) {
|
|
693
|
+
lines.push(` - ${(s.score).toFixed(2)} "${s.aLabel}" (${s.aId}) ~ "${s.bLabel}" (${s.bId}) [entities ${s.sharedEntities}, sessions ${s.sharedSessions}, label ${(s.labelSimilarity).toFixed(2)}]`);
|
|
694
|
+
}
|
|
695
|
+
lines.push("", "Merge a pair with: merge_workstreams(from, into).");
|
|
696
|
+
return okText(lines.join("\n"));
|
|
697
|
+
}
|
|
698
|
+
catch (e) {
|
|
699
|
+
return err(e);
|
|
700
|
+
}
|
|
701
|
+
}
|
|
430
702
|
export function createMcpServer(deps) {
|
|
431
703
|
const server = new McpServer({
|
|
432
704
|
name: SERVER_NAME,
|
|
@@ -451,7 +723,7 @@ export function createMcpServer(deps) {
|
|
|
451
723
|
mode: z
|
|
452
724
|
.enum(["keyword", "semantic", "hybrid"])
|
|
453
725
|
.optional()
|
|
454
|
-
.describe("Search mode. Defaults to
|
|
726
|
+
.describe("Search mode. Defaults to keyword (FTS5 BM25); hybrid and semantic are available."),
|
|
455
727
|
limit: z
|
|
456
728
|
.number()
|
|
457
729
|
.int()
|
|
@@ -463,6 +735,10 @@ export function createMcpServer(deps) {
|
|
|
463
735
|
.boolean()
|
|
464
736
|
.optional()
|
|
465
737
|
.describe("If true, run a small LLM rewrite on the query before search — extracts entities and strips conversational filler. Set true for vague natural-language queries ('that pgvector thing'); set false for exact-token queries. Defaults to true server-side for MCP callers; the hot-path HTTP hook caller forces it off regardless."),
|
|
738
|
+
workstream: z
|
|
739
|
+
.string()
|
|
740
|
+
.optional()
|
|
741
|
+
.describe("Filter to sessions bound to this workstream (id or label; merge chains resolve)."),
|
|
466
742
|
},
|
|
467
743
|
annotations: {
|
|
468
744
|
readOnlyHint: true,
|
|
@@ -470,7 +746,7 @@ export function createMcpServer(deps) {
|
|
|
470
746
|
idempotentHint: true,
|
|
471
747
|
openWorldHint: true,
|
|
472
748
|
},
|
|
473
|
-
}, async (args) => recallSessionsHandler(deps, args));
|
|
749
|
+
}, async (args) => recallSessionsHandler(deps, args, mcpRuntimeFromClient(server.server.getClientVersion())));
|
|
474
750
|
server.registerTool("get_session", {
|
|
475
751
|
title: "Get Full NLM Session",
|
|
476
752
|
description: GET_SESSION_DESCRIPTION,
|
|
@@ -533,7 +809,7 @@ export function createMcpServer(deps) {
|
|
|
533
809
|
idempotentHint: true,
|
|
534
810
|
openWorldHint: true,
|
|
535
811
|
},
|
|
536
|
-
}, async (args) => recallFactsHandler(deps, args));
|
|
812
|
+
}, async (args) => recallFactsHandler(deps, args, mcpRuntimeFromClient(server.server.getClientVersion())));
|
|
537
813
|
server.registerTool("get_fact_history", {
|
|
538
814
|
title: "Get Fact Supersedence History",
|
|
539
815
|
description: GET_FACT_HISTORY_DESCRIPTION,
|
|
@@ -584,7 +860,7 @@ export function createMcpServer(deps) {
|
|
|
584
860
|
reason: z
|
|
585
861
|
.string()
|
|
586
862
|
.optional()
|
|
587
|
-
.describe("Why this session was useful. Optional but encouraged
|
|
863
|
+
.describe("Why this session was useful. Optional but encouraged; the reason is recorded with the citation for later review."),
|
|
588
864
|
},
|
|
589
865
|
annotations: {
|
|
590
866
|
readOnlyHint: false,
|
|
@@ -593,6 +869,39 @@ export function createMcpServer(deps) {
|
|
|
593
869
|
openWorldHint: false,
|
|
594
870
|
},
|
|
595
871
|
}, async (args) => citeSessionHandler(args));
|
|
872
|
+
if (deps.signalStore && deps.installScope !== undefined) {
|
|
873
|
+
server.registerTool("report_outcome", {
|
|
874
|
+
title: "Report Session Outcome",
|
|
875
|
+
description: REPORT_OUTCOME_DESCRIPTION,
|
|
876
|
+
inputSchema: {
|
|
877
|
+
session_id: z
|
|
878
|
+
.string()
|
|
879
|
+
.optional()
|
|
880
|
+
.describe("NLM session ID this outcome applies to. Required if correlation_key is omitted."),
|
|
881
|
+
correlation_key: z
|
|
882
|
+
.string()
|
|
883
|
+
.optional()
|
|
884
|
+
.describe("External correlation identifier (e.g. a CI run id) when no NLM session_id is known. Required if session_id is omitted."),
|
|
885
|
+
outcome: z
|
|
886
|
+
.enum(SIGNAL_OUTCOMES)
|
|
887
|
+
.describe("pass = held. fail | fix | exhausted = overturned."),
|
|
888
|
+
source_of_record: z
|
|
889
|
+
.string()
|
|
890
|
+
.min(1)
|
|
891
|
+
.describe("The deterministic system asserting this outcome (e.g. \"ci:github-actions\", \"hook:post-tool-use\")."),
|
|
892
|
+
detail: z
|
|
893
|
+
.record(z.string(), z.unknown())
|
|
894
|
+
.optional()
|
|
895
|
+
.describe("Optional passthrough context, stored on the signal row."),
|
|
896
|
+
},
|
|
897
|
+
annotations: {
|
|
898
|
+
readOnlyHint: false,
|
|
899
|
+
destructiveHint: false,
|
|
900
|
+
idempotentHint: true,
|
|
901
|
+
openWorldHint: false,
|
|
902
|
+
},
|
|
903
|
+
}, async (args) => reportOutcomeHandler(deps, args));
|
|
904
|
+
}
|
|
596
905
|
server.registerTool("mark_superseded", {
|
|
597
906
|
title: "Mark NLM Session Superseded",
|
|
598
907
|
description: MARK_SUPERSEDED_DESCRIPTION,
|
|
@@ -617,6 +926,65 @@ export function createMcpServer(deps) {
|
|
|
617
926
|
openWorldHint: false,
|
|
618
927
|
},
|
|
619
928
|
}, async (args) => markSupersededHandler(deps, args));
|
|
929
|
+
server.registerTool("work_summary", {
|
|
930
|
+
title: "Daily work summary",
|
|
931
|
+
description: "The operator's agent-assisted work recap for a day: where attention went (active time by topic), focus quality (context switches, longest block), and progress (decisions, open loops). Optional `date` (YYYY-MM-DD); defaults to today.",
|
|
932
|
+
inputSchema: {
|
|
933
|
+
date: z
|
|
934
|
+
.string()
|
|
935
|
+
.regex(/^\d{4}-\d{2}-\d{2}$/)
|
|
936
|
+
.optional()
|
|
937
|
+
.describe("Day to summarize, YYYY-MM-DD. Defaults to today."),
|
|
938
|
+
},
|
|
939
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
940
|
+
}, async (args) => workSummaryHandler(deps, args));
|
|
941
|
+
server.registerTool("recall_workstream", {
|
|
942
|
+
title: "Recall a workstream's accumulated context",
|
|
943
|
+
description: "Return the coherent project view for a workstream: its member sessions, current decisions and open loops, accumulated facts, and code exemplars. Accepts a workstream id or label; merge chains resolve to the live workstream.",
|
|
944
|
+
inputSchema: {
|
|
945
|
+
idOrLabel: z.string().describe("Workstream id (ws_...) or label (e.g. 'NLM')."),
|
|
946
|
+
},
|
|
947
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
948
|
+
}, async (args) => recallWorkstreamHandler(deps, args, mcpRuntimeFromClient(server.server.getClientVersion())));
|
|
949
|
+
server.registerTool("rebind_session", {
|
|
950
|
+
title: "Rebind a session to a different workstream",
|
|
951
|
+
description: "Move a session's primary workstream binding (operator correction). Sets binding_source=operator. The session's facts and exemplars roll up under the new workstream automatically (rollup is by session binding).",
|
|
952
|
+
inputSchema: {
|
|
953
|
+
sessionId: z.string().describe("Session id to rebind."),
|
|
954
|
+
workstream: z.string().describe("Target workstream id (ws_...) or label."),
|
|
955
|
+
},
|
|
956
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
957
|
+
}, async (args) => rebindSessionHandler(deps, args));
|
|
958
|
+
server.registerTool("merge_workstreams", {
|
|
959
|
+
title: "Merge one workstream into another",
|
|
960
|
+
description: "Supersede a duplicate workstream into the one to keep: sets merged_into, marks it merged, and unions its entity index. Sessions, facts, and exemplars resolve to the survivor automatically (no session rewrite). Accepts ids or labels; merge chains resolve.",
|
|
961
|
+
inputSchema: {
|
|
962
|
+
from: z.string().describe("Workstream to retire (the duplicate); id or label."),
|
|
963
|
+
into: z.string().describe("Workstream to keep (the survivor); id or label."),
|
|
964
|
+
},
|
|
965
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: false, openWorldHint: false },
|
|
966
|
+
}, async (args) => mergeWorkstreamsHandler(deps, args));
|
|
967
|
+
server.registerTool("rename_workstream", {
|
|
968
|
+
title: "Rename a workstream",
|
|
969
|
+
description: "Relabel a workstream. Refuses a label that collides with a different existing workstream's normalized label (prevents accidental duplicates). Accepts id or label.",
|
|
970
|
+
inputSchema: {
|
|
971
|
+
idOrLabel: z.string().describe("Workstream id or current label."),
|
|
972
|
+
label: z.string().describe("New label."),
|
|
973
|
+
},
|
|
974
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
975
|
+
}, async (args) => renameWorkstreamHandler(deps, args));
|
|
976
|
+
server.registerTool("retire_workstream", {
|
|
977
|
+
title: "Retire a workstream",
|
|
978
|
+
description: "Mark a workstream retired (status=retired). Operator cleanup for dead one-off workstreams. Reversible by re-setting status. Accepts id or label.",
|
|
979
|
+
inputSchema: { idOrLabel: z.string().describe("Workstream id or label.") },
|
|
980
|
+
annotations: { readOnlyHint: false, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
981
|
+
}, async (args) => retireWorkstreamHandler(deps, args));
|
|
982
|
+
server.registerTool("list_merge_suggestions", {
|
|
983
|
+
title: "Suggest duplicate workstreams to merge",
|
|
984
|
+
description: "Score active workstream pairs by shared entities, co-occurring sessions, and label similarity; list likely duplicates for one-click merge_workstreams. Computed on demand; read-only.",
|
|
985
|
+
inputSchema: { minScore: z.number().optional().describe("Minimum similarity score 0..1 (default 0.5).") },
|
|
986
|
+
annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },
|
|
987
|
+
}, async (args) => listMergeSuggestionsHandler(deps, args));
|
|
620
988
|
if (deps.exemplarStore &&
|
|
621
989
|
deps.installScope &&
|
|
622
990
|
process.env["NLM_CODE_EXEMPLARS_ENABLED"] === "1") {
|
|
@@ -666,6 +1034,23 @@ export function createMcpServer(deps) {
|
|
|
666
1034
|
k: args.k ?? 5,
|
|
667
1035
|
includeNegatives: args.include_negatives ?? true,
|
|
668
1036
|
}, exemplarStore, codeEmbedder, null);
|
|
1037
|
+
const conversationId = resolveConversationByQuery(args.query) ?? undefined;
|
|
1038
|
+
const allIds = [
|
|
1039
|
+
...result.positives.map((e) => e.id),
|
|
1040
|
+
...result.negatives.map((e) => e.id),
|
|
1041
|
+
];
|
|
1042
|
+
void logQuery({
|
|
1043
|
+
source: "mcp",
|
|
1044
|
+
runtime: mcpRuntimeFromClient(server.server.getClientVersion()),
|
|
1045
|
+
query: args.query,
|
|
1046
|
+
entity: null,
|
|
1047
|
+
kind: null,
|
|
1048
|
+
mode: "semantic",
|
|
1049
|
+
limit: args.k ?? 5,
|
|
1050
|
+
nResults: allIds.length,
|
|
1051
|
+
returnedIds: allIds,
|
|
1052
|
+
...(conversationId !== undefined ? { conversationId } : {}),
|
|
1053
|
+
});
|
|
669
1054
|
return { content: [{ type: "text", text: format(result) }] };
|
|
670
1055
|
});
|
|
671
1056
|
server.registerTool("supersede_exemplar", {
|