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
|
@@ -11,24 +11,96 @@
|
|
|
11
11
|
*/
|
|
12
12
|
import { pathToFileURL } from "node:url";
|
|
13
13
|
import { classifyPrompt } from "../core/hook/gate.js";
|
|
14
|
+
import { recentConversationContext, topicalWordCount } from "./recent-context.js";
|
|
14
15
|
import { appendHookLog } from "../core/hook/hook-log.js";
|
|
15
16
|
import { loadSurfaced, recordSurfaced } from "../core/hook/memo.js";
|
|
16
17
|
import { formatPointerBlock } from "../core/hook/pointer-block.js";
|
|
17
18
|
import { selectHits } from "../core/hook/select.js";
|
|
18
19
|
import { autoloadEnv } from "../llm/env-autoload.js";
|
|
19
20
|
import { recallOverHttp } from "./recall-over-http.js";
|
|
21
|
+
import { parseScoreFloor, parseRelativeFloor } from "./score-floor.js";
|
|
22
|
+
import { makeOllamaGate, parseRecallGateMode } from "./recall-gate.js";
|
|
23
|
+
import { readStdin, hookModeFromEnv } from "./hook-helpers.js";
|
|
20
24
|
// Keyword recall returns raw BM25 scores (unbounded, not the 0..1 hybrid
|
|
21
25
|
// scale). FTS5 MATCH already gates relevance — only lexically-matching
|
|
22
|
-
// sessions come back — so the floor
|
|
23
|
-
//
|
|
24
|
-
|
|
26
|
+
// sessions come back — so the default floor is 0. NLM_RECALL_SCORE_FLOOR lets
|
|
27
|
+
// an operator raise it once the shadow log's surfaced-vs-cited score
|
|
28
|
+
// distribution (nlm precision --verbose) justifies a real cutoff.
|
|
29
|
+
// parseScoreFloor guards against a bad env value: a non-numeric / non-finite /
|
|
30
|
+
// negative input falls back to 0 instead of silently deny-all'ing recall
|
|
31
|
+
// (matchScore >= NaN is always false in select.ts).
|
|
32
|
+
const SCORE_THRESHOLD = parseScoreFloor(process.env["NLM_RECALL_SCORE_FLOOR"]);
|
|
33
|
+
// Portable per-fire noise floor: drop tail hits below this fraction of the
|
|
34
|
+
// fire's median score. Default 0.9 (calibrated, #284) — trims weak tail recalls
|
|
35
|
+
// while keeping ~97% of cited ones. Only the per-message keyword path runs this;
|
|
36
|
+
// the session-start hybrid path is left unfiltered (unmeasured).
|
|
37
|
+
const RELATIVE_FLOOR = parseRelativeFloor(process.env["NLM_RECALL_REL_FLOOR"], 0.9);
|
|
25
38
|
const PER_FIRE_CAP = 3;
|
|
26
39
|
const PER_CONVERSATION_CAP = 10;
|
|
27
40
|
const PROMPT_PREVIEW_CHARS = 200;
|
|
41
|
+
export function parseHookDeadline(raw, defaultMs = 4000) {
|
|
42
|
+
if (raw === undefined)
|
|
43
|
+
return defaultMs;
|
|
44
|
+
const n = Number(raw);
|
|
45
|
+
return Number.isFinite(n) && n > 0 ? n : defaultMs;
|
|
46
|
+
}
|
|
47
|
+
// #396: the outer deadline wraps recall + gate + formatting and must exceed
|
|
48
|
+
// RECALL_TIMEOUT_MS (default 4000ms in recall-over-http.ts) with headroom.
|
|
49
|
+
// Invariant: HOOK_DEADLINE_MS > RECALL_TIMEOUT_MS at defaults. If the outer
|
|
50
|
+
// equals the inner, the deadline fires before post-recall work can complete.
|
|
51
|
+
export const HOOK_DEADLINE_MS = parseHookDeadline(process.env["NLM_HOOK_DEADLINE_MS"], 6000);
|
|
52
|
+
async function withDeadline(p, ms, fallback) {
|
|
53
|
+
if (ms <= 0)
|
|
54
|
+
return fallback;
|
|
55
|
+
let timer;
|
|
56
|
+
const timeout = new Promise((resolve) => { timer = setTimeout(() => resolve(fallback), ms); });
|
|
57
|
+
try {
|
|
58
|
+
return await Promise.race([p, timeout]);
|
|
59
|
+
}
|
|
60
|
+
finally {
|
|
61
|
+
clearTimeout(timer);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
28
64
|
export function hookRuntimeFromEnv(env = process.env) {
|
|
29
65
|
const raw = env["NLM_HOOK_RUNTIME"]?.trim();
|
|
30
66
|
return raw ? raw : "claude-code";
|
|
31
67
|
}
|
|
68
|
+
/**
|
|
69
|
+
* Whether the per-prompt ambient recall hook should run at all.
|
|
70
|
+
*
|
|
71
|
+
* Pull-first posture (default since #392, 2026-07-03): fresh installs run
|
|
72
|
+
* per-prompt ambient recall OFF and agents pull memory on demand via the
|
|
73
|
+
* recall MCP tools. Measured basis (U1, locked judge): pulls 72.4% useful
|
|
74
|
+
* vs ambient injection 18.2%/7.9%. NLM_HOOK_PROMPT_RECALL=on opts back in;
|
|
75
|
+
* any other already-set value keeps its pre-flip meaning (non-"off" = on)
|
|
76
|
+
* so existing installs are untouched. Independent of NLM_HOOK_MODE, which
|
|
77
|
+
* governs the once-per-session passive layer (session-start hook), which
|
|
78
|
+
* stays on.
|
|
79
|
+
*/
|
|
80
|
+
export function promptRecallEnabled(env = process.env) {
|
|
81
|
+
const raw = env["NLM_HOOK_PROMPT_RECALL"]?.trim();
|
|
82
|
+
if (raw === undefined || raw === "")
|
|
83
|
+
return false;
|
|
84
|
+
return raw.toLowerCase() !== "off";
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* The recall query. Default is the bare prompt (today's behavior). When the
|
|
88
|
+
* context-recall flag is on AND the prompt is thin (few content words — the
|
|
89
|
+
* measured off-topic failure band) AND a transcript is available, prepend the
|
|
90
|
+
* recent conversation turns so the query carries the topic the thin prompt
|
|
91
|
+
* lacks. Flag-gated + thin-only + fallback => never worse than today.
|
|
92
|
+
*/
|
|
93
|
+
export function buildRecallQuery(input, env = process.env) {
|
|
94
|
+
if (env["NLM_HOOK_CONTEXT_RECALL"] !== "1")
|
|
95
|
+
return input.prompt;
|
|
96
|
+
if (!input.transcriptPath)
|
|
97
|
+
return input.prompt;
|
|
98
|
+
const minWords = Number.parseInt(env["NLM_HOOK_CONTEXT_MIN_WORDS"] ?? "3", 10);
|
|
99
|
+
if (topicalWordCount(input.prompt) >= minWords)
|
|
100
|
+
return input.prompt;
|
|
101
|
+
const context = recentConversationContext(input.transcriptPath);
|
|
102
|
+
return context ? `${context} ${input.prompt}` : input.prompt;
|
|
103
|
+
}
|
|
32
104
|
function normalizeRecall(raw) {
|
|
33
105
|
if (Array.isArray(raw))
|
|
34
106
|
return { hits: raw, facts: [] };
|
|
@@ -38,7 +110,7 @@ function normalizeRecall(raw) {
|
|
|
38
110
|
export async function runHook(input, deps) {
|
|
39
111
|
const gate = classifyPrompt(input.prompt);
|
|
40
112
|
const preview = input.prompt.slice(0, PROMPT_PREVIEW_CHARS);
|
|
41
|
-
if (gate === "generative") {
|
|
113
|
+
if (gate === "generative" || gate === "skip") {
|
|
42
114
|
appendHookLog({
|
|
43
115
|
ts: new Date().toISOString(),
|
|
44
116
|
conversationId: input.conversationId,
|
|
@@ -51,9 +123,10 @@ export async function runHook(input, deps) {
|
|
|
51
123
|
});
|
|
52
124
|
return "";
|
|
53
125
|
}
|
|
126
|
+
const deadline = Date.now() + (deps.deadlineMs ?? HOOK_DEADLINE_MS);
|
|
54
127
|
let fetched = { hits: [], facts: [] };
|
|
55
128
|
try {
|
|
56
|
-
fetched = normalizeRecall(await deps.recall(input.
|
|
129
|
+
fetched = normalizeRecall(await withDeadline(deps.recall(buildRecallQuery(input)), deadline - Date.now(), { hits: [], facts: [] }));
|
|
57
130
|
}
|
|
58
131
|
catch {
|
|
59
132
|
fetched = { hits: [], facts: [] };
|
|
@@ -64,10 +137,33 @@ export async function runHook(input, deps) {
|
|
|
64
137
|
hits,
|
|
65
138
|
surfaced,
|
|
66
139
|
scoreThreshold: SCORE_THRESHOLD,
|
|
140
|
+
relativeFloor: RELATIVE_FLOOR,
|
|
67
141
|
perFireCap: PER_FIRE_CAP,
|
|
68
142
|
perConversationCap: PER_CONVERSATION_CAP,
|
|
69
143
|
});
|
|
70
|
-
|
|
144
|
+
// Relevance gate: judge each selected candidate before injecting. In shadow
|
|
145
|
+
// it only records decisions; in live it drops the irrelevant ones. Gate the
|
|
146
|
+
// already-capped `selected` set (<=PER_FIRE_CAP), not the full hit list, to
|
|
147
|
+
// bound hot-path latency.
|
|
148
|
+
let gateDecisions;
|
|
149
|
+
let injected = selected;
|
|
150
|
+
if (deps.recallGate && selected.length > 0) {
|
|
151
|
+
const g = deps.recallGate;
|
|
152
|
+
const toGate = g.maxCandidates ? selected.slice(0, g.maxCandidates) : selected;
|
|
153
|
+
const remaining = deadline - Date.now();
|
|
154
|
+
const gateFallback = toGate.map((h) => ({ id: h.id, gate: "relevant" }));
|
|
155
|
+
if (remaining <= 0) {
|
|
156
|
+
gateDecisions = gateFallback;
|
|
157
|
+
}
|
|
158
|
+
else {
|
|
159
|
+
gateDecisions = await withDeadline(Promise.all(toGate.map(async (h) => ({ id: h.id, gate: await g.judge(input.prompt, `${h.label}\n${h.summary ?? ""}`) }))), remaining, gateFallback);
|
|
160
|
+
}
|
|
161
|
+
if (g.mode === "live") {
|
|
162
|
+
const drop = new Set(gateDecisions.filter((d) => d.gate === "irrelevant").map((d) => d.id));
|
|
163
|
+
injected = selected.filter((h) => !drop.has(h.id));
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
const block = formatPointerBlock(injected, fetched.facts, fetched.exemplars);
|
|
71
167
|
const estTokens = Math.ceil(block.length / 4);
|
|
72
168
|
appendHookLog({
|
|
73
169
|
ts: new Date().toISOString(),
|
|
@@ -75,25 +171,17 @@ export async function runHook(input, deps) {
|
|
|
75
171
|
promptPreview: preview,
|
|
76
172
|
gate,
|
|
77
173
|
hits: hits.map((h) => ({ id: h.id, score: h.matchScore })),
|
|
78
|
-
wouldInject:
|
|
174
|
+
wouldInject: injected.map((h) => h.id),
|
|
79
175
|
estTokens,
|
|
80
176
|
mode: deps.mode,
|
|
177
|
+
...(gateDecisions ? { gateDecisions } : {}),
|
|
81
178
|
});
|
|
82
|
-
if (deps.mode === "live" &&
|
|
83
|
-
recordSurfaced(input.conversationId,
|
|
179
|
+
if (deps.mode === "live" && injected.length > 0) {
|
|
180
|
+
recordSurfaced(input.conversationId, injected.map((h) => h.id));
|
|
84
181
|
return block;
|
|
85
182
|
}
|
|
86
183
|
return "";
|
|
87
184
|
}
|
|
88
|
-
function readStdin() {
|
|
89
|
-
return new Promise((resolve) => {
|
|
90
|
-
let data = "";
|
|
91
|
-
process.stdin.setEncoding("utf8");
|
|
92
|
-
process.stdin.on("data", (chunk) => (data += chunk));
|
|
93
|
-
process.stdin.on("end", () => resolve(data));
|
|
94
|
-
process.stdin.on("error", () => resolve(data));
|
|
95
|
-
});
|
|
96
|
-
}
|
|
97
185
|
async function main() {
|
|
98
186
|
try {
|
|
99
187
|
// Load ~/.nlm/.env so NLM_MCP_TOKEN is available before we hit /api/recall.
|
|
@@ -104,11 +192,27 @@ async function main() {
|
|
|
104
192
|
const payload = JSON.parse(raw);
|
|
105
193
|
const prompt = typeof payload.prompt === "string" ? payload.prompt : "";
|
|
106
194
|
const conversationId = typeof payload.session_id === "string" ? payload.session_id : "unknown";
|
|
195
|
+
const transcriptPath = typeof payload.transcript_path === "string" ? payload.transcript_path : undefined;
|
|
107
196
|
if (!prompt)
|
|
108
197
|
return;
|
|
109
|
-
|
|
198
|
+
// Option B (push -> pull): when per-prompt ambient recall is disabled, do
|
|
199
|
+
// zero hot-path work — no recall call, no gate, no injection. Memory is
|
|
200
|
+
// pulled on demand instead. The once-per-session passive layer
|
|
201
|
+
// (session-start hook) is unaffected.
|
|
202
|
+
if (!promptRecallEnabled())
|
|
203
|
+
return;
|
|
204
|
+
const mode = hookModeFromEnv();
|
|
110
205
|
const runtime = hookRuntimeFromEnv();
|
|
111
|
-
const
|
|
206
|
+
const gateMode = parseRecallGateMode();
|
|
207
|
+
const gateUrl = process.env["OLLAMA_URL"] ?? "http://127.0.0.1:11434";
|
|
208
|
+
// Cap how many candidates the gate judges to bound hot-path latency
|
|
209
|
+
// (~1 judge call/fire by default). Override with NLM_HOOK_RECALL_GATE_TOPN.
|
|
210
|
+
const gateTopN = Math.max(1, Number.parseInt(process.env["NLM_HOOK_RECALL_GATE_TOPN"] ?? "1", 10) || 1);
|
|
211
|
+
const out = await runHook({ prompt, conversationId, ...(transcriptPath ? { transcriptPath } : {}) }, {
|
|
212
|
+
mode,
|
|
213
|
+
recall: (q) => recallOverHttp(q, runtime, conversationId === "unknown" ? undefined : conversationId),
|
|
214
|
+
...(gateMode ? { recallGate: { mode: gateMode, judge: makeOllamaGate(gateUrl), maxCandidates: gateTopN } } : {}),
|
|
215
|
+
});
|
|
112
216
|
if (out)
|
|
113
217
|
process.stdout.write(out);
|
|
114
218
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"prompt-recall-hook.js","sourceRoot":"","sources":["../../src/hook/prompt-recall-hook.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AACpD,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AACvD,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAClE,OAAO,EAAE,kBAAkB,EAA0C,MAAM,6BAA6B,CAAC;AACzG,OAAO,EAAE,UAAU,EAAuB,MAAM,sBAAsB,CAAC;AACvE,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,EAAE,cAAc,EAAE,MAAM,uBAAuB,CAAC;
|
|
1
|
+
{"version":3,"file":"prompt-recall-hook.js","sourceRoot":"","sources":["../../src/hook/prompt-recall-hook.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Pre-injection relevance gate (recall-precision option A).
|
|
3
|
+
*
|
|
4
|
+
* A small local model predicts, from the prompt + a candidate's context alone
|
|
5
|
+
* (no agent response yet), whether injecting the candidate would help. The
|
|
6
|
+
* prompt is deliberately CONSERVATIVE: drop only the clearly cross-topic
|
|
7
|
+
* candidates, keep anything with a plausible connection. Measured on the
|
|
8
|
+
* frontier gold (gate-feasibility.ts): keeps 96% of informed injections while
|
|
9
|
+
* skipping 25% of off-topic. The asymmetry is intentional — dropping a useful
|
|
10
|
+
* memory forfeits recall's signature value, so the gate fails toward "relevant"
|
|
11
|
+
* (here and on any judge error).
|
|
12
|
+
*
|
|
13
|
+
* Runtime home so the hot-path hook has no eval-tooling dependency; the eval
|
|
14
|
+
* harness imports RECALL_GATE_SYSTEM from here so validated == shipped.
|
|
15
|
+
*/
|
|
16
|
+
import type { GateMode } from "./prompt-recall-hook.js";
|
|
17
|
+
export declare const RECALL_GATE_MODEL = "qwen3.5:4b";
|
|
18
|
+
export declare const RECALL_GATE_SYSTEM: string;
|
|
19
|
+
/** Read the gate mode from env. Absent / "off" / unknown => undefined (gate off). */
|
|
20
|
+
export declare function parseRecallGateMode(env?: NodeJS.ProcessEnv): GateMode | undefined;
|
|
21
|
+
/**
|
|
22
|
+
* A gate judge backed by an Ollama-compatible local endpoint. Fails toward
|
|
23
|
+
* "relevant" on any error so the gate can never drop a candidate due to an
|
|
24
|
+
* infra blip — consistent with the hook's fail-open contract.
|
|
25
|
+
*/
|
|
26
|
+
/** Bound the gate's hot-path cost. A cold model load can take tens of seconds;
|
|
27
|
+
* past this the gate gives up and keeps the candidate rather than block the prompt. */
|
|
28
|
+
export declare const GATE_TIMEOUT_MS = 4000;
|
|
29
|
+
export declare function makeOllamaGate(url: string, model?: string, timeoutMs?: number): (prompt: string, candidate: string) => Promise<"relevant" | "irrelevant">;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
import { fetchWithTimeout } from "./hook-helpers.js";
|
|
2
|
+
export const RECALL_GATE_MODEL = "qwen3.5:4b";
|
|
3
|
+
export const RECALL_GATE_SYSTEM = "You are a recall GATE protecting against off-topic memory injection. Given a USER PROMPT and a CANDIDATE prior-session context, answer irrelevant ONLY when the candidate is CLEARLY about a completely different topic, project, or task than the prompt (e.g. the prompt is about debugging a website and the candidate is about a trading pipeline). If there is ANY plausible topical connection, or you are at all unsure, answer relevant. Dropping a useful memory is worse than keeping a marginal one. You do NOT see the assistant's answer. " +
|
|
4
|
+
'Output {"gate":"relevant"|"irrelevant"}.';
|
|
5
|
+
const GATE_FORMAT = { type: "object", properties: { gate: { type: "string", enum: ["relevant", "irrelevant"] } }, required: ["gate"] };
|
|
6
|
+
const GATE_OPTS = { temperature: 0, top_p: 1, top_k: 0, presence_penalty: 0, frequency_penalty: 0 };
|
|
7
|
+
/** Read the gate mode from env. Absent / "off" / unknown => undefined (gate off). */
|
|
8
|
+
export function parseRecallGateMode(env = process.env) {
|
|
9
|
+
const v = env["NLM_HOOK_RECALL_GATE"]?.trim();
|
|
10
|
+
return v === "shadow" || v === "live" ? v : undefined;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* A gate judge backed by an Ollama-compatible local endpoint. Fails toward
|
|
14
|
+
* "relevant" on any error so the gate can never drop a candidate due to an
|
|
15
|
+
* infra blip — consistent with the hook's fail-open contract.
|
|
16
|
+
*/
|
|
17
|
+
/** Bound the gate's hot-path cost. A cold model load can take tens of seconds;
|
|
18
|
+
* past this the gate gives up and keeps the candidate rather than block the prompt. */
|
|
19
|
+
export const GATE_TIMEOUT_MS = 4000;
|
|
20
|
+
export function makeOllamaGate(url, model = RECALL_GATE_MODEL, timeoutMs = GATE_TIMEOUT_MS) {
|
|
21
|
+
return async (prompt, candidate) => {
|
|
22
|
+
try {
|
|
23
|
+
const r = await fetchWithTimeout(`${url}/api/chat`, {
|
|
24
|
+
method: "POST",
|
|
25
|
+
headers: { "Content-Type": "application/json" },
|
|
26
|
+
body: JSON.stringify({
|
|
27
|
+
model,
|
|
28
|
+
stream: false,
|
|
29
|
+
think: false,
|
|
30
|
+
// Keep the gate model resident between fires so only the first fire
|
|
31
|
+
// pays the cold-load; subsequent gates are ~1 judge call.
|
|
32
|
+
keep_alive: "15m",
|
|
33
|
+
format: GATE_FORMAT,
|
|
34
|
+
options: GATE_OPTS,
|
|
35
|
+
messages: [
|
|
36
|
+
{ role: "system", content: RECALL_GATE_SYSTEM },
|
|
37
|
+
{ role: "user", content: `USER PROMPT:\n${prompt}\n\nCANDIDATE CONTEXT:\n${candidate}` },
|
|
38
|
+
],
|
|
39
|
+
}),
|
|
40
|
+
}, timeoutMs);
|
|
41
|
+
const d = (await r.json());
|
|
42
|
+
const v = JSON.parse(d.message?.content ?? "{}").gate;
|
|
43
|
+
return v === "irrelevant" ? "irrelevant" : "relevant";
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
return "relevant";
|
|
47
|
+
}
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
//# sourceMappingURL=recall-gate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recall-gate.js","sourceRoot":"","sources":["../../src/hook/recall-gate.ts"],"names":[],"mappings":"AAgBA,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AAErD,MAAM,CAAC,MAAM,iBAAiB,GAAG,YAAY,CAAC;AAE9C,MAAM,CAAC,MAAM,kBAAkB,GAC7B,yhBAAyhB;IACzhB,0CAA0C,CAAC;AAE7C,MAAM,WAAW,GAAG,EAAE,IAAI,EAAE,QAAQ,EAAE,UAAU,EAAE,EAAE,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,CAAC,UAAU,EAAE,YAAY,CAAC,EAAE,EAAE,EAAE,QAAQ,EAAE,CAAC,MAAM,CAAC,EAAE,CAAC;AACvI,MAAM,SAAS,GAAG,EAAE,WAAW,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,gBAAgB,EAAE,CAAC,EAAE,iBAAiB,EAAE,CAAC,EAAE,CAAC;AAEpG,qFAAqF;AACrF,MAAM,UAAU,mBAAmB,CAAC,MAAyB,OAAO,CAAC,GAAG;IACtE,MAAM,CAAC,GAAG,GAAG,CAAC,sBAAsB,CAAC,EAAE,IAAI,EAAE,CAAC;IAC9C,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,KAAK,MAAM,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;AACxD,CAAC;AAED;;;;GAIG;AACH;uFACuF;AACvF,MAAM,CAAC,MAAM,eAAe,GAAG,IAAI,CAAC;AAEpC,MAAM,UAAU,cAAc,CAC5B,GAAW,EACX,QAAgB,iBAAiB,EACjC,YAAoB,eAAe;IAEnC,OAAO,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,EAAE;QACjC,IAAI,CAAC;YACH,MAAM,CAAC,GAAG,MAAM,gBAAgB,CAC9B,GAAG,GAAG,WAAW,EACjB;gBACE,MAAM,EAAE,MAAM;gBACd,OAAO,EAAE,EAAE,cAAc,EAAE,kBAAkB,EAAE;gBAC/C,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;oBACnB,KAAK;oBACL,MAAM,EAAE,KAAK;oBACb,KAAK,EAAE,KAAK;oBACZ,oEAAoE;oBACpE,0DAA0D;oBAC1D,UAAU,EAAE,KAAK;oBACjB,MAAM,EAAE,WAAW;oBACnB,OAAO,EAAE,SAAS;oBAClB,QAAQ,EAAE;wBACR,EAAE,IAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,kBAAkB,EAAE;wBAC/C,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,iBAAiB,MAAM,2BAA2B,SAAS,EAAE,EAAE;qBACzF;iBACF,CAAC;aACH,EACD,SAAS,CACV,CAAC;YACF,MAAM,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,IAAI,EAAE,CAAuC,CAAC;YACjE,MAAM,CAAC,GAAI,IAAI,CAAC,KAAK,CAAC,CAAC,CAAC,OAAO,EAAE,OAAO,IAAI,IAAI,CAAuB,CAAC,IAAI,CAAC;YAC7E,OAAO,CAAC,KAAK,YAAY,CAAC,CAAC,CAAC,YAAY,CAAC,CAAC,CAAC,UAAU,CAAC;QACxD,CAAC;QAAC,MAAM,CAAC;YACP,OAAO,UAAU,CAAC;QACpB,CAAC;IACH,CAAC,CAAC;AACJ,CAAC"}
|
|
@@ -15,10 +15,11 @@
|
|
|
15
15
|
import type { RecallHitInput } from "../core/hook/select.js";
|
|
16
16
|
import type { PointerExemplar, PointerFact } from "../core/hook/pointer-block.js";
|
|
17
17
|
export declare const RECALL_LIMIT = 5;
|
|
18
|
-
export declare
|
|
18
|
+
export declare function parseRecallTimeout(raw: string | undefined): number;
|
|
19
|
+
export declare const RECALL_TIMEOUT_MS: number;
|
|
19
20
|
export interface RecallOverHttpResult {
|
|
20
21
|
readonly hits: ReadonlyArray<RecallHitInput>;
|
|
21
22
|
readonly facts: ReadonlyArray<PointerFact>;
|
|
22
23
|
readonly exemplars: ReadonlyArray<PointerExemplar>;
|
|
23
24
|
}
|
|
24
|
-
export declare function recallOverHttp(prompt: string, runtime?: string, conversationId?: string): Promise<RecallOverHttpResult>;
|
|
25
|
+
export declare function recallOverHttp(prompt: string, runtime?: string, conversationId?: string, mode?: "keyword" | "hybrid"): Promise<RecallOverHttpResult>;
|
|
@@ -14,26 +14,39 @@
|
|
|
14
14
|
*/
|
|
15
15
|
import { hookAuthHeaders } from "./hook-auth.js";
|
|
16
16
|
import { extractRecallQuery } from "../core/hook/query-extract.js";
|
|
17
|
+
import { fetchWithTimeout } from "./hook-helpers.js";
|
|
18
|
+
import { DEFAULT_NLM_PORT } from "../shared/net.js";
|
|
17
19
|
export const RECALL_LIMIT = 5;
|
|
18
|
-
|
|
19
|
-
|
|
20
|
+
// #396: the budget must exceed the daemon's worst-case sequential hybrid path
|
|
21
|
+
// (keyword ~839ms + embed capped at NLM_RECALL_EMBED_DEADLINE_MS default 2000ms
|
|
22
|
+
// + semantic ~50ms + response overhead ~100ms = ~2990ms floor). A budget at or
|
|
23
|
+
// below the daemon's embed cap re-creates the dead passive layer even after the
|
|
24
|
+
// daemon-side embed fix in 73406260: the hook gives up before the embed deadline
|
|
25
|
+
// fires and the daemon finishes building the response.
|
|
26
|
+
export function parseRecallTimeout(raw) {
|
|
27
|
+
if (raw === undefined)
|
|
28
|
+
return 4000;
|
|
29
|
+
const n = Number(raw);
|
|
30
|
+
return Number.isFinite(n) && n > 0 ? n : 4000;
|
|
31
|
+
}
|
|
32
|
+
export const RECALL_TIMEOUT_MS = parseRecallTimeout(process.env["NLM_HOOK_RECALL_TIMEOUT_MS"]);
|
|
33
|
+
export async function recallOverHttp(prompt, runtime, conversationId, mode = "keyword") {
|
|
20
34
|
const query = extractRecallQuery(prompt);
|
|
21
35
|
if (query === null)
|
|
22
36
|
return { hits: [], facts: [], exemplars: [] };
|
|
23
|
-
const portValue = process.env["NLM_PORT"] ??
|
|
24
|
-
const url =
|
|
25
|
-
|
|
37
|
+
const portValue = process.env["NLM_PORT"] ?? DEFAULT_NLM_PORT;
|
|
38
|
+
const url =
|
|
39
|
+
// 127.0.0.1, not localhost: each hook is a fresh process with no connection
|
|
40
|
+
// reuse, and Node resolves localhost to IPv6 ::1 first — a measured ~50-300ms
|
|
41
|
+
// per-fire connect penalty vs ~3ms on the explicit IPv4 loopback.
|
|
42
|
+
`http://127.0.0.1:${portValue}/api/recall` +
|
|
43
|
+
`?q=${encodeURIComponent(query)}&mode=${mode}&limit=${RECALL_LIMIT}&withFacts=true&withExemplars=true` +
|
|
26
44
|
(conversationId ? `&conversation_id=${encodeURIComponent(conversationId)}` : "");
|
|
27
|
-
const controller = new AbortController();
|
|
28
|
-
const timer = setTimeout(() => controller.abort(), RECALL_TIMEOUT_MS);
|
|
29
45
|
try {
|
|
30
46
|
const extra = { "x-recall-source": "hook" };
|
|
31
47
|
if (runtime)
|
|
32
48
|
extra["x-recall-runtime"] = runtime;
|
|
33
|
-
const res = await
|
|
34
|
-
headers: hookAuthHeaders(extra),
|
|
35
|
-
signal: controller.signal,
|
|
36
|
-
});
|
|
49
|
+
const res = await fetchWithTimeout(url, { headers: hookAuthHeaders(extra) }, RECALL_TIMEOUT_MS);
|
|
37
50
|
if (!res.ok)
|
|
38
51
|
return { hits: [], facts: [], exemplars: [] };
|
|
39
52
|
let body;
|
|
@@ -64,8 +77,8 @@ export async function recallOverHttp(prompt, runtime, conversationId) {
|
|
|
64
77
|
}));
|
|
65
78
|
return { hits, facts, exemplars };
|
|
66
79
|
}
|
|
67
|
-
|
|
68
|
-
|
|
80
|
+
catch {
|
|
81
|
+
return { hits: [], facts: [], exemplars: [] };
|
|
69
82
|
}
|
|
70
83
|
}
|
|
71
84
|
//# sourceMappingURL=recall-over-http.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"recall-over-http.js","sourceRoot":"","sources":["../../src/hook/recall-over-http.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAIH,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,EAAE,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;
|
|
1
|
+
{"version":3,"file":"recall-over-http.js","sourceRoot":"","sources":["../../src/hook/recall-over-http.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Recent-conversation context for the recall hook.
|
|
3
|
+
*
|
|
4
|
+
* Thin prompts ("what do you recommend?", "do it") carry no topic, so recalling
|
|
5
|
+
* on the bare prompt surfaces off-topic sessions (measured: 82% off-topic). When
|
|
6
|
+
* the prompt is thin we prepend the last few conversation turns — that is where
|
|
7
|
+
* the topic actually lives — to the recall query.
|
|
8
|
+
*
|
|
9
|
+
* Hot-path safe: TAIL-reads at most `maxBytes` of the transcript (not the whole
|
|
10
|
+
* file, which can be MBs in a long session), and only thin prompts call it.
|
|
11
|
+
* Fail-open: any I/O or parse error yields "" so the hook never breaks and the
|
|
12
|
+
* caller falls back to bare-prompt recall (today's behavior).
|
|
13
|
+
*
|
|
14
|
+
* Runtime-specific: only runtimes that expose a transcript path can use this
|
|
15
|
+
* (Claude Code does). Others pass no path and get "" → bare-prompt fallback.
|
|
16
|
+
*/
|
|
17
|
+
interface Opts {
|
|
18
|
+
readonly maxTurns?: number;
|
|
19
|
+
readonly maxBytes?: number;
|
|
20
|
+
readonly perTurnChars?: number;
|
|
21
|
+
}
|
|
22
|
+
/** Count topical (non-stopword) words — the thinness signal for context-recall. */
|
|
23
|
+
export declare function topicalWordCount(s: string): number;
|
|
24
|
+
export declare function recentConversationContext(transcriptPath: string, opts?: Opts): string;
|
|
25
|
+
export {};
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Recent-conversation context for the recall hook.
|
|
3
|
+
*
|
|
4
|
+
* Thin prompts ("what do you recommend?", "do it") carry no topic, so recalling
|
|
5
|
+
* on the bare prompt surfaces off-topic sessions (measured: 82% off-topic). When
|
|
6
|
+
* the prompt is thin we prepend the last few conversation turns — that is where
|
|
7
|
+
* the topic actually lives — to the recall query.
|
|
8
|
+
*
|
|
9
|
+
* Hot-path safe: TAIL-reads at most `maxBytes` of the transcript (not the whole
|
|
10
|
+
* file, which can be MBs in a long session), and only thin prompts call it.
|
|
11
|
+
* Fail-open: any I/O or parse error yields "" so the hook never breaks and the
|
|
12
|
+
* caller falls back to bare-prompt recall (today's behavior).
|
|
13
|
+
*
|
|
14
|
+
* Runtime-specific: only runtimes that expose a transcript path can use this
|
|
15
|
+
* (Claude Code does). Others pass no path and get "" → bare-prompt fallback.
|
|
16
|
+
*/
|
|
17
|
+
import { closeSync, existsSync, fstatSync, openSync, readSync } from "node:fs";
|
|
18
|
+
const DEFAULT_MAX_TURNS = 3;
|
|
19
|
+
const DEFAULT_MAX_BYTES = 64 * 1024;
|
|
20
|
+
const DEFAULT_PER_TURN_CHARS = 400;
|
|
21
|
+
// Conversational / function words that carry no recall topic. A prompt with few
|
|
22
|
+
// NON-stopword words is "thin" — that is the off-topic failure band ("what do you
|
|
23
|
+
// recommend", "do it now"), not a raw token count (which counts do/you/the).
|
|
24
|
+
const STOPWORDS = new Set([
|
|
25
|
+
"the", "a", "an", "and", "or", "but", "is", "are", "was", "were", "be", "been", "being",
|
|
26
|
+
"do", "does", "did", "doing", "you", "your", "i", "we", "our", "it", "its", "this", "that",
|
|
27
|
+
"these", "those", "what", "which", "how", "why", "when", "where", "who", "can", "could",
|
|
28
|
+
"would", "should", "will", "shall", "may", "might", "must", "please", "to", "of", "in", "on",
|
|
29
|
+
"for", "with", "so", "now", "then", "here", "there", "just", "also", "as", "at", "by", "if",
|
|
30
|
+
"my", "me", "us", "ok", "okay", "yes", "no", "not", "up", "out", "go", "get", "got", "make",
|
|
31
|
+
"made", "give", "tell", "want", "need", "think", "know", "let", "lets", "about", "from", "into",
|
|
32
|
+
]);
|
|
33
|
+
/** Count topical (non-stopword) words — the thinness signal for context-recall. */
|
|
34
|
+
export function topicalWordCount(s) {
|
|
35
|
+
const tokens = s.toLowerCase().match(/[a-z0-9]{2,}/g) ?? [];
|
|
36
|
+
return tokens.filter((t) => !STOPWORDS.has(t)).length;
|
|
37
|
+
}
|
|
38
|
+
function textOf(message) {
|
|
39
|
+
if (typeof message !== "object" || message === null)
|
|
40
|
+
return "";
|
|
41
|
+
const content = message.content;
|
|
42
|
+
if (typeof content === "string")
|
|
43
|
+
return content;
|
|
44
|
+
if (Array.isArray(content)) {
|
|
45
|
+
return content
|
|
46
|
+
.filter((b) => typeof b === "object" && b !== null && b.type === "text")
|
|
47
|
+
.map((b) => b.text)
|
|
48
|
+
.join(" ");
|
|
49
|
+
}
|
|
50
|
+
return "";
|
|
51
|
+
}
|
|
52
|
+
/** Read at most `maxBytes` from the end of the file. */
|
|
53
|
+
function tailRead(path, maxBytes) {
|
|
54
|
+
const fd = openSync(path, "r");
|
|
55
|
+
try {
|
|
56
|
+
const size = fstatSync(fd).size;
|
|
57
|
+
const start = size > maxBytes ? size - maxBytes : 0;
|
|
58
|
+
const len = size - start;
|
|
59
|
+
if (len <= 0)
|
|
60
|
+
return "";
|
|
61
|
+
const buf = Buffer.alloc(len);
|
|
62
|
+
readSync(fd, buf, 0, len, start);
|
|
63
|
+
return buf.toString("utf8");
|
|
64
|
+
}
|
|
65
|
+
finally {
|
|
66
|
+
closeSync(fd);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
export function recentConversationContext(transcriptPath, opts = {}) {
|
|
70
|
+
try {
|
|
71
|
+
if (!transcriptPath || !existsSync(transcriptPath))
|
|
72
|
+
return "";
|
|
73
|
+
const maxTurns = opts.maxTurns ?? DEFAULT_MAX_TURNS;
|
|
74
|
+
const perTurnChars = opts.perTurnChars ?? DEFAULT_PER_TURN_CHARS;
|
|
75
|
+
const lines = tailRead(transcriptPath, opts.maxBytes ?? DEFAULT_MAX_BYTES).split("\n");
|
|
76
|
+
const turns = [];
|
|
77
|
+
// Walk from the end; a tail read may truncate the first line mid-JSON, which
|
|
78
|
+
// JSON.parse rejects and we skip — that is the intended "drop partial leading
|
|
79
|
+
// line" behavior.
|
|
80
|
+
for (let i = lines.length - 1; i >= 0 && turns.length < maxTurns; i--) {
|
|
81
|
+
const line = lines[i].trim();
|
|
82
|
+
if (!line)
|
|
83
|
+
continue;
|
|
84
|
+
let evt;
|
|
85
|
+
try {
|
|
86
|
+
evt = JSON.parse(line);
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
if (evt.type !== "user" && evt.type !== "assistant")
|
|
92
|
+
continue;
|
|
93
|
+
const text = textOf(evt.message).trim();
|
|
94
|
+
if (!text)
|
|
95
|
+
continue;
|
|
96
|
+
turns.unshift(text.slice(0, perTurnChars));
|
|
97
|
+
}
|
|
98
|
+
return turns.join(" ").trim();
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
return "";
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
//# sourceMappingURL=recent-context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recent-context.js","sourceRoot":"","sources":["../../src/hook/recent-context.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,SAAS,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAE/E,MAAM,iBAAiB,GAAG,CAAC,CAAC;AAC5B,MAAM,iBAAiB,GAAG,EAAE,GAAG,IAAI,CAAC;AACpC,MAAM,sBAAsB,GAAG,GAAG,CAAC;AAQnC,gFAAgF;AAChF,kFAAkF;AAClF,6EAA6E;AAC7E,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC;IACxB,KAAK,EAAE,GAAG,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO;IACvF,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM;IAC1F,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO;IACvF,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI;IAC5F,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI;IAC3F,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM;IAC3F,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM;CAChG,CAAC,CAAC;AAEH,mFAAmF;AACnF,MAAM,UAAU,gBAAgB,CAAC,CAAS;IACxC,MAAM,MAAM,GAAG,CAAC,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,eAAe,CAAC,IAAI,EAAE,CAAC;IAC5D,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;AACxD,CAAC;AAED,SAAS,MAAM,CAAC,OAAgB;IAC9B,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,KAAK,IAAI;QAAE,OAAO,EAAE,CAAC;IAC/D,MAAM,OAAO,GAAI,OAAiC,CAAC,OAAO,CAAC;IAC3D,IAAI,OAAO,OAAO,KAAK,QAAQ;QAAE,OAAO,OAAO,CAAC;IAChD,IAAI,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QAC3B,OAAO,OAAO;aACX,MAAM,CACL,CAAC,CAAC,EAAuC,EAAE,CACzC,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,KAAK,IAAI,IAAK,CAAuB,CAAC,IAAI,KAAK,MAAM,CAClF;aACA,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;aAClB,IAAI,CAAC,GAAG,CAAC,CAAC;IACf,CAAC;IACD,OAAO,EAAE,CAAC;AACZ,CAAC;AAED,wDAAwD;AACxD,SAAS,QAAQ,CAAC,IAAY,EAAE,QAAgB;IAC9C,MAAM,EAAE,GAAG,QAAQ,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IAC/B,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,SAAS,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC;QAChC,MAAM,KAAK,GAAG,IAAI,GAAG,QAAQ,CAAC,CAAC,CAAC,IAAI,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;QACpD,MAAM,GAAG,GAAG,IAAI,GAAG,KAAK,CAAC;QACzB,IAAI,GAAG,IAAI,CAAC;YAAE,OAAO,EAAE,CAAC;QACxB,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC9B,QAAQ,CAAC,EAAE,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC;QACjC,OAAO,GAAG,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC9B,CAAC;YAAS,CAAC;QACT,SAAS,CAAC,EAAE,CAAC,CAAC;IAChB,CAAC;AACH,CAAC;AAED,MAAM,UAAU,yBAAyB,CAAC,cAAsB,EAAE,OAAa,EAAE;IAC/E,IAAI,CAAC;QACH,IAAI,CAAC,cAAc,IAAI,CAAC,UAAU,CAAC,cAAc,CAAC;YAAE,OAAO,EAAE,CAAC;QAC9D,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,IAAI,iBAAiB,CAAC;QACpD,MAAM,YAAY,GAAG,IAAI,CAAC,YAAY,IAAI,sBAAsB,CAAC;QACjE,MAAM,KAAK,GAAG,QAAQ,CAAC,cAAc,EAAE,IAAI,CAAC,QAAQ,IAAI,iBAAiB,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAEvF,MAAM,KAAK,GAAa,EAAE,CAAC;QAC3B,6EAA6E;QAC7E,8EAA8E;QAC9E,kBAAkB;QAClB,KAAK,IAAI,CAAC,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,KAAK,CAAC,MAAM,GAAG,QAAQ,EAAE,CAAC,EAAE,EAAE,CAAC;YACtE,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAE,CAAC,IAAI,EAAE,CAAC;YAC9B,IAAI,CAAC,IAAI;gBAAE,SAAS;YACpB,IAAI,GAA0C,CAAC;YAC/C,IAAI,CAAC;gBACH,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAA0C,CAAC;YAClE,CAAC;YAAC,MAAM,CAAC;gBACP,SAAS;YACX,CAAC;YACD,IAAI,GAAG,CAAC,IAAI,KAAK,MAAM,IAAI,GAAG,CAAC,IAAI,KAAK,WAAW;gBAAE,SAAS;YAC9D,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC;YACxC,IAAI,CAAC,IAAI;gBAAE,SAAS;YACpB,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,YAAY,CAAC,CAAC,CAAC;QAC7C,CAAC;QACD,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;IAChC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parses the NLM_RECALL_SCORE_FLOOR env knob into a safe BM25 score cutoff.
|
|
3
|
+
*
|
|
4
|
+
* A bad value (NaN, non-finite, negative) must never silently flip recall into
|
|
5
|
+
* admit-all or deny-all. The unguarded `Number(env ?? "0")` returns NaN for a
|
|
6
|
+
* non-numeric value, and `matchScore >= NaN` is always false in select.ts —
|
|
7
|
+
* that would silently deny-all. This guard collapses every invalid input back
|
|
8
|
+
* to the safe default of 0 (no cutoff, FTS5 MATCH still gates relevance).
|
|
9
|
+
*
|
|
10
|
+
* Default stays 0: the calibrated keyword-mode floor (2.0, per the score-floor
|
|
11
|
+
* diagnostic on raw BM25 scores) is NOT a safe global default — the
|
|
12
|
+
* session-start hook recalls in hybrid mode whose matchScore is normalized to
|
|
13
|
+
* 0..1, so a 2.0 floor would deny-all there. The calibrated value is pending a
|
|
14
|
+
* per-mode application and ships as an opt-in env override only.
|
|
15
|
+
*/
|
|
16
|
+
export declare function parseScoreFloor(raw: string | undefined): number;
|
|
17
|
+
/**
|
|
18
|
+
* Parses NLM_RECALL_REL_FLOOR into a fire-median-relative cutoff: drop hits
|
|
19
|
+
* scoring below this fraction of the fire's median score. Unlike the absolute
|
|
20
|
+
* floor above, this is scale-invariant (a ratio), so the SAME value works on
|
|
21
|
+
* raw BM25 (keyword) or normalized (hybrid) scores and ports across installs.
|
|
22
|
+
* Invalid input collapses to the supplied default; 0 disables. Calibrated to
|
|
23
|
+
* 0.9 on the keyword/per-message path (scripts/eval/floor-calibration.ts, #284):
|
|
24
|
+
* keeps ~97% of cited recalls, trims weak tail noise. Set to 0.8 for zero
|
|
25
|
+
* measured gold loss, or 0 to disable.
|
|
26
|
+
*/
|
|
27
|
+
export declare function parseRelativeFloor(raw: string | undefined, fallback: number): number;
|