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
|
@@ -6,14 +6,192 @@ import { pathToFileURL } from "node:url";
|
|
|
6
6
|
// src/core/hook/gate.ts
|
|
7
7
|
var LEADING_FILLER = /^(please|can you|could you|would you|will you|i need you to|i'd like you to|i want you to|i would like you to|help me|let's|lets|hey|ok|okay)\b[\s,]*/i;
|
|
8
8
|
var GENERATIVE_OPENER = /^(write|draft|create|compose|generate|brainstorm|design|outline|sketch|invent|rename|come up with)\b/i;
|
|
9
|
+
var HARNESS_TAGS = [
|
|
10
|
+
"ide_selection",
|
|
11
|
+
"ide_opened_file",
|
|
12
|
+
"ide_closed_file",
|
|
13
|
+
"ide_diagnostics",
|
|
14
|
+
"ide_recently_modified_file",
|
|
15
|
+
"task-notification",
|
|
16
|
+
"system-reminder",
|
|
17
|
+
"command-name",
|
|
18
|
+
"command-message",
|
|
19
|
+
"command-args",
|
|
20
|
+
"local-command-stdout",
|
|
21
|
+
"local-command-stderr"
|
|
22
|
+
];
|
|
23
|
+
var HARNESS_BLOCK = new RegExp(
|
|
24
|
+
`<(${HARNESS_TAGS.join("|")})\\b[^>]*>[\\s\\S]*?(?:</\\1>|$)`,
|
|
25
|
+
"gi"
|
|
26
|
+
);
|
|
27
|
+
var ACK_ONLY = /^(yes|yep|yeah|sure|ok|okay|k|thanks|thank you|ty|done|nice|cool|great|perfect|continue|next|go|go ahead|do it|yes please|sounds good|good|got it|right|correct)\W*$/i;
|
|
28
|
+
function contentWordCount(s) {
|
|
29
|
+
const m = s.match(/[A-Za-z0-9]{2,}/g);
|
|
30
|
+
return m ? m.length : 0;
|
|
31
|
+
}
|
|
9
32
|
function classifyPrompt(prompt) {
|
|
10
|
-
let p = prompt.trim();
|
|
33
|
+
let p = prompt.replace(HARNESS_BLOCK, " ").replace(/\s+/g, " ").trim();
|
|
34
|
+
if (contentWordCount(p) === 0) return "skip";
|
|
35
|
+
if (ACK_ONLY.test(p)) return "skip";
|
|
11
36
|
for (let i = 0; i < 3 && LEADING_FILLER.test(p); i++) {
|
|
12
37
|
p = p.replace(LEADING_FILLER, "");
|
|
13
38
|
}
|
|
39
|
+
p = p.trim();
|
|
40
|
+
if (contentWordCount(p) === 0) return "skip";
|
|
14
41
|
return GENERATIVE_OPENER.test(p) ? "generative" : "evaluate";
|
|
15
42
|
}
|
|
16
43
|
|
|
44
|
+
// src/hook/recent-context.ts
|
|
45
|
+
import { closeSync, existsSync, fstatSync, openSync, readSync } from "node:fs";
|
|
46
|
+
var DEFAULT_MAX_TURNS = 3;
|
|
47
|
+
var DEFAULT_MAX_BYTES = 64 * 1024;
|
|
48
|
+
var DEFAULT_PER_TURN_CHARS = 400;
|
|
49
|
+
var STOPWORDS = /* @__PURE__ */ new Set([
|
|
50
|
+
"the",
|
|
51
|
+
"a",
|
|
52
|
+
"an",
|
|
53
|
+
"and",
|
|
54
|
+
"or",
|
|
55
|
+
"but",
|
|
56
|
+
"is",
|
|
57
|
+
"are",
|
|
58
|
+
"was",
|
|
59
|
+
"were",
|
|
60
|
+
"be",
|
|
61
|
+
"been",
|
|
62
|
+
"being",
|
|
63
|
+
"do",
|
|
64
|
+
"does",
|
|
65
|
+
"did",
|
|
66
|
+
"doing",
|
|
67
|
+
"you",
|
|
68
|
+
"your",
|
|
69
|
+
"i",
|
|
70
|
+
"we",
|
|
71
|
+
"our",
|
|
72
|
+
"it",
|
|
73
|
+
"its",
|
|
74
|
+
"this",
|
|
75
|
+
"that",
|
|
76
|
+
"these",
|
|
77
|
+
"those",
|
|
78
|
+
"what",
|
|
79
|
+
"which",
|
|
80
|
+
"how",
|
|
81
|
+
"why",
|
|
82
|
+
"when",
|
|
83
|
+
"where",
|
|
84
|
+
"who",
|
|
85
|
+
"can",
|
|
86
|
+
"could",
|
|
87
|
+
"would",
|
|
88
|
+
"should",
|
|
89
|
+
"will",
|
|
90
|
+
"shall",
|
|
91
|
+
"may",
|
|
92
|
+
"might",
|
|
93
|
+
"must",
|
|
94
|
+
"please",
|
|
95
|
+
"to",
|
|
96
|
+
"of",
|
|
97
|
+
"in",
|
|
98
|
+
"on",
|
|
99
|
+
"for",
|
|
100
|
+
"with",
|
|
101
|
+
"so",
|
|
102
|
+
"now",
|
|
103
|
+
"then",
|
|
104
|
+
"here",
|
|
105
|
+
"there",
|
|
106
|
+
"just",
|
|
107
|
+
"also",
|
|
108
|
+
"as",
|
|
109
|
+
"at",
|
|
110
|
+
"by",
|
|
111
|
+
"if",
|
|
112
|
+
"my",
|
|
113
|
+
"me",
|
|
114
|
+
"us",
|
|
115
|
+
"ok",
|
|
116
|
+
"okay",
|
|
117
|
+
"yes",
|
|
118
|
+
"no",
|
|
119
|
+
"not",
|
|
120
|
+
"up",
|
|
121
|
+
"out",
|
|
122
|
+
"go",
|
|
123
|
+
"get",
|
|
124
|
+
"got",
|
|
125
|
+
"make",
|
|
126
|
+
"made",
|
|
127
|
+
"give",
|
|
128
|
+
"tell",
|
|
129
|
+
"want",
|
|
130
|
+
"need",
|
|
131
|
+
"think",
|
|
132
|
+
"know",
|
|
133
|
+
"let",
|
|
134
|
+
"lets",
|
|
135
|
+
"about",
|
|
136
|
+
"from",
|
|
137
|
+
"into"
|
|
138
|
+
]);
|
|
139
|
+
function topicalWordCount(s) {
|
|
140
|
+
const tokens = s.toLowerCase().match(/[a-z0-9]{2,}/g) ?? [];
|
|
141
|
+
return tokens.filter((t) => !STOPWORDS.has(t)).length;
|
|
142
|
+
}
|
|
143
|
+
function textOf(message) {
|
|
144
|
+
if (typeof message !== "object" || message === null) return "";
|
|
145
|
+
const content = message.content;
|
|
146
|
+
if (typeof content === "string") return content;
|
|
147
|
+
if (Array.isArray(content)) {
|
|
148
|
+
return content.filter(
|
|
149
|
+
(b) => typeof b === "object" && b !== null && b.type === "text"
|
|
150
|
+
).map((b) => b.text).join(" ");
|
|
151
|
+
}
|
|
152
|
+
return "";
|
|
153
|
+
}
|
|
154
|
+
function tailRead(path, maxBytes) {
|
|
155
|
+
const fd = openSync(path, "r");
|
|
156
|
+
try {
|
|
157
|
+
const size = fstatSync(fd).size;
|
|
158
|
+
const start = size > maxBytes ? size - maxBytes : 0;
|
|
159
|
+
const len = size - start;
|
|
160
|
+
if (len <= 0) return "";
|
|
161
|
+
const buf = Buffer.alloc(len);
|
|
162
|
+
readSync(fd, buf, 0, len, start);
|
|
163
|
+
return buf.toString("utf8");
|
|
164
|
+
} finally {
|
|
165
|
+
closeSync(fd);
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
function recentConversationContext(transcriptPath, opts = {}) {
|
|
169
|
+
try {
|
|
170
|
+
if (!transcriptPath || !existsSync(transcriptPath)) return "";
|
|
171
|
+
const maxTurns = opts.maxTurns ?? DEFAULT_MAX_TURNS;
|
|
172
|
+
const perTurnChars = opts.perTurnChars ?? DEFAULT_PER_TURN_CHARS;
|
|
173
|
+
const lines = tailRead(transcriptPath, opts.maxBytes ?? DEFAULT_MAX_BYTES).split("\n");
|
|
174
|
+
const turns = [];
|
|
175
|
+
for (let i = lines.length - 1; i >= 0 && turns.length < maxTurns; i--) {
|
|
176
|
+
const line = lines[i].trim();
|
|
177
|
+
if (!line) continue;
|
|
178
|
+
let evt;
|
|
179
|
+
try {
|
|
180
|
+
evt = JSON.parse(line);
|
|
181
|
+
} catch {
|
|
182
|
+
continue;
|
|
183
|
+
}
|
|
184
|
+
if (evt.type !== "user" && evt.type !== "assistant") continue;
|
|
185
|
+
const text = textOf(evt.message).trim();
|
|
186
|
+
if (!text) continue;
|
|
187
|
+
turns.unshift(text.slice(0, perTurnChars));
|
|
188
|
+
}
|
|
189
|
+
return turns.join(" ").trim();
|
|
190
|
+
} catch {
|
|
191
|
+
return "";
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
17
195
|
// src/core/hook/hook-log.ts
|
|
18
196
|
import { appendFileSync, mkdirSync } from "node:fs";
|
|
19
197
|
import { homedir } from "node:os";
|
|
@@ -32,7 +210,15 @@ function appendHookLog(entry) {
|
|
|
32
210
|
}
|
|
33
211
|
|
|
34
212
|
// src/core/hook/memo.ts
|
|
35
|
-
import {
|
|
213
|
+
import {
|
|
214
|
+
existsSync as existsSync2,
|
|
215
|
+
mkdirSync as mkdirSync2,
|
|
216
|
+
readdirSync,
|
|
217
|
+
readFileSync,
|
|
218
|
+
rmSync,
|
|
219
|
+
statSync,
|
|
220
|
+
writeFileSync
|
|
221
|
+
} from "node:fs";
|
|
36
222
|
import { homedir as homedir2 } from "node:os";
|
|
37
223
|
import { join as join2 } from "node:path";
|
|
38
224
|
function stateDir() {
|
|
@@ -45,7 +231,7 @@ function memoPath(conversationId) {
|
|
|
45
231
|
function loadSurfaced(conversationId) {
|
|
46
232
|
try {
|
|
47
233
|
const path = memoPath(conversationId);
|
|
48
|
-
if (!
|
|
234
|
+
if (!existsSync2(path)) return /* @__PURE__ */ new Set();
|
|
49
235
|
const parsed = JSON.parse(readFileSync(path, "utf8"));
|
|
50
236
|
if (!Array.isArray(parsed)) return /* @__PURE__ */ new Set();
|
|
51
237
|
return new Set(parsed.filter((x) => typeof x === "string"));
|
|
@@ -64,6 +250,26 @@ function recordSurfaced(conversationId, ids) {
|
|
|
64
250
|
}
|
|
65
251
|
|
|
66
252
|
// src/core/hook/pointer-block.ts
|
|
253
|
+
function truncateSummary(s, max = 200) {
|
|
254
|
+
if (s.length <= max) return s;
|
|
255
|
+
const window = s.slice(0, max);
|
|
256
|
+
const last = window[window.length - 1];
|
|
257
|
+
if ((last === "." || last === "!" || last === "?") && window.length - 1 >= 60) {
|
|
258
|
+
return window;
|
|
259
|
+
}
|
|
260
|
+
for (let i = window.length - 2; i >= 60; i--) {
|
|
261
|
+
const c = window[i];
|
|
262
|
+
if ((c === "." || c === "!" || c === "?") && window[i + 1] === " ") {
|
|
263
|
+
return s.slice(0, i + 1);
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
for (let i = window.length - 1; i >= 60; i--) {
|
|
267
|
+
if (window[i] === " ") {
|
|
268
|
+
return s.slice(0, i) + " ...";
|
|
269
|
+
}
|
|
270
|
+
}
|
|
271
|
+
return window + " ...";
|
|
272
|
+
}
|
|
67
273
|
function formatPointerBlock(hits, facts = [], exemplars = []) {
|
|
68
274
|
if (hits.length === 0 && facts.length === 0 && exemplars.length === 0) return "";
|
|
69
275
|
const out = [];
|
|
@@ -72,7 +278,7 @@ function formatPointerBlock(hits, facts = [], exemplars = []) {
|
|
|
72
278
|
for (const h of hits) {
|
|
73
279
|
const datePart = h.startedAt.slice(0, 10);
|
|
74
280
|
if (h.summary) {
|
|
75
|
-
out.push(`- ${h.id} \xB7 ${h.label} (${datePart}) \u2014 ${h.summary
|
|
281
|
+
out.push(`- ${h.id} \xB7 ${h.label} (${datePart}) \u2014 ${truncateSummary(h.summary)}`);
|
|
76
282
|
} else {
|
|
77
283
|
out.push(`- ${h.id} \xB7 ${h.label} (${datePart})`);
|
|
78
284
|
}
|
|
@@ -91,7 +297,7 @@ function formatPointerBlock(hits, facts = [], exemplars = []) {
|
|
|
91
297
|
out.push("## Related code exemplars (nlm-memory)");
|
|
92
298
|
for (const e of exemplars) {
|
|
93
299
|
const langPart = e.lang ? `${e.lang} \xB7 ` : "";
|
|
94
|
-
out.push(`- [${e.outcome}] ${langPart}${e.repo} - ${e.taskContext
|
|
300
|
+
out.push(`- [${e.outcome}] ${langPart}${e.repo} - ${truncateSummary(e.taskContext)}`);
|
|
95
301
|
}
|
|
96
302
|
}
|
|
97
303
|
const tools = exemplars.length > 0 ? "NLM tools: recall_sessions (search), get_session (full transcript), recall_facts (prior decisions), get_fact_history (how a decision evolved), recall_code (pull the full code for a related exemplar)." : "NLM tools: recall_sessions (search), get_session (full transcript), recall_facts (prior decisions), get_fact_history (how a decision evolved).";
|
|
@@ -100,10 +306,16 @@ function formatPointerBlock(hits, facts = [], exemplars = []) {
|
|
|
100
306
|
}
|
|
101
307
|
|
|
102
308
|
// src/core/hook/select.ts
|
|
309
|
+
function medianScore(hits) {
|
|
310
|
+
if (hits.length === 0) return 0;
|
|
311
|
+
const sorted = hits.map((h) => h.matchScore).sort((a, b) => a - b);
|
|
312
|
+
return sorted[Math.floor(sorted.length / 2)] ?? 0;
|
|
313
|
+
}
|
|
103
314
|
function selectHits(params) {
|
|
104
|
-
const { hits, surfaced, scoreThreshold, perFireCap, perConversationCap } = params;
|
|
315
|
+
const { hits, surfaced, scoreThreshold, perFireCap, perConversationCap, relativeFloor = 0 } = params;
|
|
316
|
+
const relCut = relativeFloor > 0 ? relativeFloor * medianScore(hits) : 0;
|
|
105
317
|
const eligible = hits.filter(
|
|
106
|
-
(h) => h.matchScore >= scoreThreshold && !surfaced.has(h.id)
|
|
318
|
+
(h) => h.matchScore >= scoreThreshold && h.matchScore >= relCut && !surfaced.has(h.id)
|
|
107
319
|
);
|
|
108
320
|
const budget = Math.max(0, perConversationCap - surfaced.size);
|
|
109
321
|
const limit = Math.min(perFireCap, budget);
|
|
@@ -111,7 +323,7 @@ function selectHits(params) {
|
|
|
111
323
|
}
|
|
112
324
|
|
|
113
325
|
// src/llm/env-autoload.ts
|
|
114
|
-
import { readFileSync as readFileSync2, existsSync as
|
|
326
|
+
import { readFileSync as readFileSync2, existsSync as existsSync3 } from "node:fs";
|
|
115
327
|
import { homedir as homedir3 } from "node:os";
|
|
116
328
|
import { resolve } from "node:path";
|
|
117
329
|
var DEFAULT_SEARCH_PATHS = [
|
|
@@ -129,7 +341,7 @@ function autoloadEnv(extraPaths = []) {
|
|
|
129
341
|
const paths = [...DEFAULT_SEARCH_PATHS, ...extraPaths];
|
|
130
342
|
for (const raw of paths) {
|
|
131
343
|
const path = expandHome(raw);
|
|
132
|
-
if (!
|
|
344
|
+
if (!existsSync3(path)) continue;
|
|
133
345
|
try {
|
|
134
346
|
const content = readFileSync2(path, "utf8");
|
|
135
347
|
for (const line of content.split("\n")) {
|
|
@@ -161,7 +373,7 @@ function hookAuthHeaders(extra = {}) {
|
|
|
161
373
|
}
|
|
162
374
|
|
|
163
375
|
// src/core/hook/query-extract.ts
|
|
164
|
-
var
|
|
376
|
+
var STOPWORDS2 = /* @__PURE__ */ new Set([
|
|
165
377
|
"a",
|
|
166
378
|
"an",
|
|
167
379
|
"the",
|
|
@@ -293,30 +505,63 @@ var STOPWORDS = /* @__PURE__ */ new Set([
|
|
|
293
505
|
]);
|
|
294
506
|
var MIN_CONTENT_WORDS = 2;
|
|
295
507
|
var MIN_WORD_LEN = 3;
|
|
508
|
+
var SYSTEM_MESSAGE_PREFIX = /^<(task-notification|command-name|command-message|command-args|local-command-stdout|local-command-caveat|output-file|system-reminder)\b/;
|
|
296
509
|
function extractRecallQuery(prompt) {
|
|
510
|
+
if (SYSTEM_MESSAGE_PREFIX.test(prompt.trim())) return null;
|
|
297
511
|
const tokens = prompt.trim().split(/\s+/).map((t) => t.replace(/^[^\w-]+|[^\w-]+$/g, "")).filter((t) => t.length >= MIN_WORD_LEN);
|
|
298
|
-
const contentWords = tokens.filter((t) => !
|
|
512
|
+
const contentWords = tokens.filter((t) => !STOPWORDS2.has(t.toLowerCase()));
|
|
299
513
|
if (contentWords.length < MIN_CONTENT_WORDS) return null;
|
|
300
514
|
return contentWords.join(" ");
|
|
301
515
|
}
|
|
302
516
|
|
|
517
|
+
// src/hook/hook-helpers.ts
|
|
518
|
+
function readStdin() {
|
|
519
|
+
return new Promise((resolve2) => {
|
|
520
|
+
let data = "";
|
|
521
|
+
process.stdin.setEncoding("utf8");
|
|
522
|
+
process.stdin.on("data", (chunk) => data += chunk);
|
|
523
|
+
process.stdin.on("end", () => resolve2(data));
|
|
524
|
+
process.stdin.on("error", () => resolve2(data));
|
|
525
|
+
});
|
|
526
|
+
}
|
|
527
|
+
async function fetchWithTimeout(url, init, ms) {
|
|
528
|
+
const controller = new AbortController();
|
|
529
|
+
const timer = setTimeout(() => controller.abort(), ms);
|
|
530
|
+
try {
|
|
531
|
+
return await fetch(url, { ...init, signal: controller.signal });
|
|
532
|
+
} finally {
|
|
533
|
+
clearTimeout(timer);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
function hookModeFromEnv() {
|
|
537
|
+
return process.env["NLM_HOOK_MODE"] === "live" ? "live" : "shadow";
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
// src/shared/net.ts
|
|
541
|
+
var DEFAULT_NLM_PORT = "3940";
|
|
542
|
+
|
|
303
543
|
// src/hook/recall-over-http.ts
|
|
304
544
|
var RECALL_LIMIT = 5;
|
|
305
|
-
|
|
306
|
-
|
|
545
|
+
function parseRecallTimeout(raw) {
|
|
546
|
+
if (raw === void 0) return 4e3;
|
|
547
|
+
const n = Number(raw);
|
|
548
|
+
return Number.isFinite(n) && n > 0 ? n : 4e3;
|
|
549
|
+
}
|
|
550
|
+
var RECALL_TIMEOUT_MS = parseRecallTimeout(process.env["NLM_HOOK_RECALL_TIMEOUT_MS"]);
|
|
551
|
+
async function recallOverHttp(prompt, runtime, conversationId, mode = "keyword") {
|
|
307
552
|
const query = extractRecallQuery(prompt);
|
|
308
553
|
if (query === null) return { hits: [], facts: [], exemplars: [] };
|
|
309
|
-
const portValue = process.env["NLM_PORT"] ??
|
|
310
|
-
const url =
|
|
311
|
-
|
|
312
|
-
|
|
554
|
+
const portValue = process.env["NLM_PORT"] ?? DEFAULT_NLM_PORT;
|
|
555
|
+
const url = (
|
|
556
|
+
// 127.0.0.1, not localhost: each hook is a fresh process with no connection
|
|
557
|
+
// reuse, and Node resolves localhost to IPv6 ::1 first — a measured ~50-300ms
|
|
558
|
+
// per-fire connect penalty vs ~3ms on the explicit IPv4 loopback.
|
|
559
|
+
`http://127.0.0.1:${portValue}/api/recall?q=${encodeURIComponent(query)}&mode=${mode}&limit=${RECALL_LIMIT}&withFacts=true&withExemplars=true` + (conversationId ? `&conversation_id=${encodeURIComponent(conversationId)}` : "")
|
|
560
|
+
);
|
|
313
561
|
try {
|
|
314
562
|
const extra = { "x-recall-source": "hook" };
|
|
315
563
|
if (runtime) extra["x-recall-runtime"] = runtime;
|
|
316
|
-
const res = await
|
|
317
|
-
headers: hookAuthHeaders(extra),
|
|
318
|
-
signal: controller.signal
|
|
319
|
-
});
|
|
564
|
+
const res = await fetchWithTimeout(url, { headers: hookAuthHeaders(extra) }, RECALL_TIMEOUT_MS);
|
|
320
565
|
if (!res.ok) return { hits: [], facts: [], exemplars: [] };
|
|
321
566
|
let body;
|
|
322
567
|
try {
|
|
@@ -344,20 +589,114 @@ async function recallOverHttp(prompt, runtime, conversationId) {
|
|
|
344
589
|
taskContext: e.taskContext
|
|
345
590
|
}));
|
|
346
591
|
return { hits, facts, exemplars };
|
|
347
|
-
}
|
|
348
|
-
|
|
592
|
+
} catch {
|
|
593
|
+
return { hits: [], facts: [], exemplars: [] };
|
|
349
594
|
}
|
|
350
595
|
}
|
|
351
596
|
|
|
597
|
+
// src/hook/score-floor.ts
|
|
598
|
+
function parseScoreFloor(raw) {
|
|
599
|
+
if (raw === void 0) return 0;
|
|
600
|
+
const parsed = Number(raw);
|
|
601
|
+
if (!Number.isFinite(parsed) || parsed < 0) return 0;
|
|
602
|
+
return parsed;
|
|
603
|
+
}
|
|
604
|
+
function parseRelativeFloor(raw, fallback) {
|
|
605
|
+
if (raw === void 0) return fallback;
|
|
606
|
+
const parsed = Number(raw);
|
|
607
|
+
if (!Number.isFinite(parsed) || parsed < 0) return fallback;
|
|
608
|
+
return parsed;
|
|
609
|
+
}
|
|
610
|
+
|
|
611
|
+
// src/hook/recall-gate.ts
|
|
612
|
+
var RECALL_GATE_MODEL = "qwen3.5:4b";
|
|
613
|
+
var 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. Output {"gate":"relevant"|"irrelevant"}.`;
|
|
614
|
+
var GATE_FORMAT = { type: "object", properties: { gate: { type: "string", enum: ["relevant", "irrelevant"] } }, required: ["gate"] };
|
|
615
|
+
var GATE_OPTS = { temperature: 0, top_p: 1, top_k: 0, presence_penalty: 0, frequency_penalty: 0 };
|
|
616
|
+
function parseRecallGateMode(env = process.env) {
|
|
617
|
+
const v = env["NLM_HOOK_RECALL_GATE"]?.trim();
|
|
618
|
+
return v === "shadow" || v === "live" ? v : void 0;
|
|
619
|
+
}
|
|
620
|
+
var GATE_TIMEOUT_MS = 4e3;
|
|
621
|
+
function makeOllamaGate(url, model = RECALL_GATE_MODEL, timeoutMs = GATE_TIMEOUT_MS) {
|
|
622
|
+
return async (prompt, candidate) => {
|
|
623
|
+
try {
|
|
624
|
+
const r = await fetchWithTimeout(
|
|
625
|
+
`${url}/api/chat`,
|
|
626
|
+
{
|
|
627
|
+
method: "POST",
|
|
628
|
+
headers: { "Content-Type": "application/json" },
|
|
629
|
+
body: JSON.stringify({
|
|
630
|
+
model,
|
|
631
|
+
stream: false,
|
|
632
|
+
think: false,
|
|
633
|
+
// Keep the gate model resident between fires so only the first fire
|
|
634
|
+
// pays the cold-load; subsequent gates are ~1 judge call.
|
|
635
|
+
keep_alive: "15m",
|
|
636
|
+
format: GATE_FORMAT,
|
|
637
|
+
options: GATE_OPTS,
|
|
638
|
+
messages: [
|
|
639
|
+
{ role: "system", content: RECALL_GATE_SYSTEM },
|
|
640
|
+
{ role: "user", content: `USER PROMPT:
|
|
641
|
+
${prompt}
|
|
642
|
+
|
|
643
|
+
CANDIDATE CONTEXT:
|
|
644
|
+
${candidate}` }
|
|
645
|
+
]
|
|
646
|
+
})
|
|
647
|
+
},
|
|
648
|
+
timeoutMs
|
|
649
|
+
);
|
|
650
|
+
const d = await r.json();
|
|
651
|
+
const v = JSON.parse(d.message?.content ?? "{}").gate;
|
|
652
|
+
return v === "irrelevant" ? "irrelevant" : "relevant";
|
|
653
|
+
} catch {
|
|
654
|
+
return "relevant";
|
|
655
|
+
}
|
|
656
|
+
};
|
|
657
|
+
}
|
|
658
|
+
|
|
352
659
|
// src/hook/prompt-recall-hook.ts
|
|
353
|
-
var SCORE_THRESHOLD =
|
|
660
|
+
var SCORE_THRESHOLD = parseScoreFloor(process.env["NLM_RECALL_SCORE_FLOOR"]);
|
|
661
|
+
var RELATIVE_FLOOR = parseRelativeFloor(process.env["NLM_RECALL_REL_FLOOR"], 0.9);
|
|
354
662
|
var PER_FIRE_CAP = 3;
|
|
355
663
|
var PER_CONVERSATION_CAP = 10;
|
|
356
664
|
var PROMPT_PREVIEW_CHARS = 200;
|
|
665
|
+
function parseHookDeadline(raw, defaultMs = 4e3) {
|
|
666
|
+
if (raw === void 0) return defaultMs;
|
|
667
|
+
const n = Number(raw);
|
|
668
|
+
return Number.isFinite(n) && n > 0 ? n : defaultMs;
|
|
669
|
+
}
|
|
670
|
+
var HOOK_DEADLINE_MS = parseHookDeadline(process.env["NLM_HOOK_DEADLINE_MS"], 6e3);
|
|
671
|
+
async function withDeadline(p, ms, fallback) {
|
|
672
|
+
if (ms <= 0) return fallback;
|
|
673
|
+
let timer;
|
|
674
|
+
const timeout = new Promise((resolve2) => {
|
|
675
|
+
timer = setTimeout(() => resolve2(fallback), ms);
|
|
676
|
+
});
|
|
677
|
+
try {
|
|
678
|
+
return await Promise.race([p, timeout]);
|
|
679
|
+
} finally {
|
|
680
|
+
clearTimeout(timer);
|
|
681
|
+
}
|
|
682
|
+
}
|
|
357
683
|
function hookRuntimeFromEnv(env = process.env) {
|
|
358
684
|
const raw = env["NLM_HOOK_RUNTIME"]?.trim();
|
|
359
685
|
return raw ? raw : "claude-code";
|
|
360
686
|
}
|
|
687
|
+
function promptRecallEnabled(env = process.env) {
|
|
688
|
+
const raw = env["NLM_HOOK_PROMPT_RECALL"]?.trim();
|
|
689
|
+
if (raw === void 0 || raw === "") return false;
|
|
690
|
+
return raw.toLowerCase() !== "off";
|
|
691
|
+
}
|
|
692
|
+
function buildRecallQuery(input, env = process.env) {
|
|
693
|
+
if (env["NLM_HOOK_CONTEXT_RECALL"] !== "1") return input.prompt;
|
|
694
|
+
if (!input.transcriptPath) return input.prompt;
|
|
695
|
+
const minWords = Number.parseInt(env["NLM_HOOK_CONTEXT_MIN_WORDS"] ?? "3", 10);
|
|
696
|
+
if (topicalWordCount(input.prompt) >= minWords) return input.prompt;
|
|
697
|
+
const context = recentConversationContext(input.transcriptPath);
|
|
698
|
+
return context ? `${context} ${input.prompt}` : input.prompt;
|
|
699
|
+
}
|
|
361
700
|
function normalizeRecall(raw) {
|
|
362
701
|
if (Array.isArray(raw)) return { hits: raw, facts: [] };
|
|
363
702
|
return raw;
|
|
@@ -365,7 +704,7 @@ function normalizeRecall(raw) {
|
|
|
365
704
|
async function runHook(input, deps) {
|
|
366
705
|
const gate = classifyPrompt(input.prompt);
|
|
367
706
|
const preview = input.prompt.slice(0, PROMPT_PREVIEW_CHARS);
|
|
368
|
-
if (gate === "generative") {
|
|
707
|
+
if (gate === "generative" || gate === "skip") {
|
|
369
708
|
appendHookLog({
|
|
370
709
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
371
710
|
conversationId: input.conversationId,
|
|
@@ -378,9 +717,12 @@ async function runHook(input, deps) {
|
|
|
378
717
|
});
|
|
379
718
|
return "";
|
|
380
719
|
}
|
|
720
|
+
const deadline = Date.now() + (deps.deadlineMs ?? HOOK_DEADLINE_MS);
|
|
381
721
|
let fetched = { hits: [], facts: [] };
|
|
382
722
|
try {
|
|
383
|
-
fetched = normalizeRecall(
|
|
723
|
+
fetched = normalizeRecall(
|
|
724
|
+
await withDeadline(deps.recall(buildRecallQuery(input)), deadline - Date.now(), { hits: [], facts: [] })
|
|
725
|
+
);
|
|
384
726
|
} catch {
|
|
385
727
|
fetched = { hits: [], facts: [] };
|
|
386
728
|
}
|
|
@@ -390,10 +732,33 @@ async function runHook(input, deps) {
|
|
|
390
732
|
hits,
|
|
391
733
|
surfaced,
|
|
392
734
|
scoreThreshold: SCORE_THRESHOLD,
|
|
735
|
+
relativeFloor: RELATIVE_FLOOR,
|
|
393
736
|
perFireCap: PER_FIRE_CAP,
|
|
394
737
|
perConversationCap: PER_CONVERSATION_CAP
|
|
395
738
|
});
|
|
396
|
-
|
|
739
|
+
let gateDecisions;
|
|
740
|
+
let injected = selected;
|
|
741
|
+
if (deps.recallGate && selected.length > 0) {
|
|
742
|
+
const g = deps.recallGate;
|
|
743
|
+
const toGate = g.maxCandidates ? selected.slice(0, g.maxCandidates) : selected;
|
|
744
|
+
const remaining = deadline - Date.now();
|
|
745
|
+
const gateFallback = toGate.map((h) => ({ id: h.id, gate: "relevant" }));
|
|
746
|
+
if (remaining <= 0) {
|
|
747
|
+
gateDecisions = gateFallback;
|
|
748
|
+
} else {
|
|
749
|
+
gateDecisions = await withDeadline(
|
|
750
|
+
Promise.all(toGate.map(async (h) => ({ id: h.id, gate: await g.judge(input.prompt, `${h.label}
|
|
751
|
+
${h.summary ?? ""}`) }))),
|
|
752
|
+
remaining,
|
|
753
|
+
gateFallback
|
|
754
|
+
);
|
|
755
|
+
}
|
|
756
|
+
if (g.mode === "live") {
|
|
757
|
+
const drop = new Set(gateDecisions.filter((d) => d.gate === "irrelevant").map((d) => d.id));
|
|
758
|
+
injected = selected.filter((h) => !drop.has(h.id));
|
|
759
|
+
}
|
|
760
|
+
}
|
|
761
|
+
const block = formatPointerBlock(injected, fetched.facts, fetched.exemplars);
|
|
397
762
|
const estTokens = Math.ceil(block.length / 4);
|
|
398
763
|
appendHookLog({
|
|
399
764
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -401,25 +766,17 @@ async function runHook(input, deps) {
|
|
|
401
766
|
promptPreview: preview,
|
|
402
767
|
gate,
|
|
403
768
|
hits: hits.map((h) => ({ id: h.id, score: h.matchScore })),
|
|
404
|
-
wouldInject:
|
|
769
|
+
wouldInject: injected.map((h) => h.id),
|
|
405
770
|
estTokens,
|
|
406
|
-
mode: deps.mode
|
|
771
|
+
mode: deps.mode,
|
|
772
|
+
...gateDecisions ? { gateDecisions } : {}
|
|
407
773
|
});
|
|
408
|
-
if (deps.mode === "live" &&
|
|
409
|
-
recordSurfaced(input.conversationId,
|
|
774
|
+
if (deps.mode === "live" && injected.length > 0) {
|
|
775
|
+
recordSurfaced(input.conversationId, injected.map((h) => h.id));
|
|
410
776
|
return block;
|
|
411
777
|
}
|
|
412
778
|
return "";
|
|
413
779
|
}
|
|
414
|
-
function readStdin() {
|
|
415
|
-
return new Promise((resolve2) => {
|
|
416
|
-
let data = "";
|
|
417
|
-
process.stdin.setEncoding("utf8");
|
|
418
|
-
process.stdin.on("data", (chunk) => data += chunk);
|
|
419
|
-
process.stdin.on("end", () => resolve2(data));
|
|
420
|
-
process.stdin.on("error", () => resolve2(data));
|
|
421
|
-
});
|
|
422
|
-
}
|
|
423
780
|
async function main() {
|
|
424
781
|
try {
|
|
425
782
|
autoloadEnv();
|
|
@@ -427,12 +784,21 @@ async function main() {
|
|
|
427
784
|
const payload = JSON.parse(raw);
|
|
428
785
|
const prompt = typeof payload.prompt === "string" ? payload.prompt : "";
|
|
429
786
|
const conversationId = typeof payload.session_id === "string" ? payload.session_id : "unknown";
|
|
787
|
+
const transcriptPath = typeof payload.transcript_path === "string" ? payload.transcript_path : void 0;
|
|
430
788
|
if (!prompt) return;
|
|
431
|
-
|
|
789
|
+
if (!promptRecallEnabled()) return;
|
|
790
|
+
const mode = hookModeFromEnv();
|
|
432
791
|
const runtime = hookRuntimeFromEnv();
|
|
792
|
+
const gateMode = parseRecallGateMode();
|
|
793
|
+
const gateUrl = process.env["OLLAMA_URL"] ?? "http://127.0.0.1:11434";
|
|
794
|
+
const gateTopN = Math.max(1, Number.parseInt(process.env["NLM_HOOK_RECALL_GATE_TOPN"] ?? "1", 10) || 1);
|
|
433
795
|
const out = await runHook(
|
|
434
|
-
{ prompt, conversationId },
|
|
435
|
-
{
|
|
796
|
+
{ prompt, conversationId, ...transcriptPath ? { transcriptPath } : {} },
|
|
797
|
+
{
|
|
798
|
+
mode,
|
|
799
|
+
recall: (q) => recallOverHttp(q, runtime, conversationId === "unknown" ? void 0 : conversationId),
|
|
800
|
+
...gateMode ? { recallGate: { mode: gateMode, judge: makeOllamaGate(gateUrl), maxCandidates: gateTopN } } : {}
|
|
801
|
+
}
|
|
436
802
|
);
|
|
437
803
|
if (out) process.stdout.write(out);
|
|
438
804
|
} catch {
|
|
@@ -442,6 +808,10 @@ if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href)
|
|
|
442
808
|
void main();
|
|
443
809
|
}
|
|
444
810
|
export {
|
|
811
|
+
HOOK_DEADLINE_MS,
|
|
812
|
+
buildRecallQuery,
|
|
445
813
|
hookRuntimeFromEnv,
|
|
814
|
+
parseHookDeadline,
|
|
815
|
+
promptRecallEnabled,
|
|
446
816
|
runHook
|
|
447
817
|
};
|