nlm-memory 0.20.0 → 0.21.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +19 -10
- package/dist/cli/digest.js +60 -1
- package/dist/cli/digest.js.map +1 -1
- package/dist/cli/init.d.ts +12 -0
- package/dist/cli/init.js +49 -0
- package/dist/cli/init.js.map +1 -0
- package/dist/cli/nlm.d.ts +13 -1
- package/dist/cli/nlm.js +872 -61
- package/dist/cli/nlm.js.map +1 -1
- package/dist/cli/scope-backfill.d.ts +34 -0
- package/dist/cli/scope-backfill.js +237 -0
- package/dist/cli/scope-backfill.js.map +1 -0
- package/dist/cli/scope-coverage.d.ts +44 -0
- package/dist/cli/scope-coverage.js +205 -0
- package/dist/cli/scope-coverage.js.map +1 -0
- package/dist/core/actions/overlay.d.ts +20 -6
- package/dist/core/actions/overlay.js +32 -18
- package/dist/core/actions/overlay.js.map +1 -1
- package/dist/core/adapters/claude-code.d.ts +14 -0
- package/dist/core/adapters/claude-code.js +36 -0
- package/dist/core/adapters/claude-code.js.map +1 -1
- package/dist/core/classifier/prompt.js +3 -3
- package/dist/core/dataset/build-dataset.js +1 -0
- package/dist/core/dataset/build-dataset.js.map +1 -1
- package/dist/core/digest/compose.d.ts +13 -3
- package/dist/core/digest/compose.js +23 -18
- package/dist/core/digest/compose.js.map +1 -1
- package/dist/core/digest/hook-injection.d.ts +43 -0
- package/dist/core/digest/hook-injection.js +79 -0
- package/dist/core/digest/hook-injection.js.map +1 -0
- package/dist/core/embedding/embed-backfill.d.ts +13 -2
- package/dist/core/embedding/embed-backfill.js +181 -43
- package/dist/core/embedding/embed-backfill.js.map +1 -1
- package/dist/core/embedding/embedding-config.d.ts +17 -0
- package/dist/core/embedding/embedding-config.js +12 -0
- package/dist/core/embedding/embedding-config.js.map +1 -0
- package/dist/core/embedding/pg-embed-backfill.d.ts +31 -0
- package/dist/core/embedding/pg-embed-backfill.js +264 -0
- package/dist/core/embedding/pg-embed-backfill.js.map +1 -0
- package/dist/core/entities/dedup-suggest.d.ts +25 -0
- package/dist/core/entities/dedup-suggest.js +107 -0
- package/dist/core/entities/dedup-suggest.js.map +1 -0
- package/dist/core/eval/classifier-fixture-eval.d.ts +29 -0
- package/dist/core/eval/classifier-fixture-eval.js +217 -0
- package/dist/core/eval/classifier-fixture-eval.js.map +1 -0
- package/dist/core/eval/extraction-scoring.d.ts +63 -0
- package/dist/core/eval/extraction-scoring.js +79 -0
- package/dist/core/eval/extraction-scoring.js.map +1 -0
- package/dist/core/eval/run-eval.d.ts +40 -0
- package/dist/core/eval/run-eval.js +34 -0
- package/dist/core/eval/run-eval.js.map +1 -0
- package/dist/core/exemplars/capture-from-session.d.ts +2 -0
- package/dist/core/exemplars/capture-from-session.js +7 -3
- package/dist/core/exemplars/capture-from-session.js.map +1 -1
- package/dist/core/exemplars/diff-parse.d.ts +23 -0
- package/dist/core/exemplars/diff-parse.js +73 -0
- package/dist/core/exemplars/diff-parse.js.map +1 -0
- package/dist/core/exemplars/embed-backfill.d.ts +36 -0
- package/dist/core/exemplars/embed-backfill.js +85 -0
- package/dist/core/exemplars/embed-backfill.js.map +1 -0
- package/dist/core/exemplars/embed-text.d.ts +15 -0
- package/dist/core/exemplars/embed-text.js +18 -0
- package/dist/core/exemplars/embed-text.js.map +1 -0
- package/dist/core/exemplars/extract-exemplar.d.ts +2 -0
- package/dist/core/exemplars/extract-exemplar.js +9 -66
- package/dist/core/exemplars/extract-exemplar.js.map +1 -1
- package/dist/core/exemplars/ingest-exemplar.d.ts +1 -0
- package/dist/core/exemplars/ingest-exemplar.js +1 -0
- package/dist/core/exemplars/ingest-exemplar.js.map +1 -1
- package/dist/core/exemplars/recall-code.js +4 -0
- package/dist/core/exemplars/recall-code.js.map +1 -1
- package/dist/core/exemplars/warm-embedder.d.ts +14 -0
- package/dist/core/exemplars/warm-embedder.js +18 -0
- package/dist/core/exemplars/warm-embedder.js.map +1 -0
- package/dist/core/health/corpus-state.d.ts +8 -0
- package/dist/core/health/corpus-state.js +11 -0
- package/dist/core/health/corpus-state.js.map +1 -0
- package/dist/core/health/embed-inflight.d.ts +9 -0
- package/dist/core/health/embed-inflight.js +30 -0
- package/dist/core/health/embed-inflight.js.map +1 -0
- package/dist/core/health/embedding-lane-state.d.ts +6 -0
- package/dist/core/health/embedding-lane-state.js +18 -0
- package/dist/core/health/embedding-lane-state.js.map +1 -0
- package/dist/core/health/warmup-state.d.ts +12 -0
- package/dist/core/health/warmup-state.js +12 -0
- package/dist/core/health/warmup-state.js.map +1 -0
- package/dist/core/hook/claude-settings.d.ts +1 -1
- package/dist/core/hook/claude-settings.js +0 -1
- package/dist/core/hook/claude-settings.js.map +1 -1
- package/dist/core/hook/gate.d.ts +13 -6
- package/dist/core/hook/gate.js +47 -6
- package/dist/core/hook/gate.js.map +1 -1
- package/dist/core/hook/hook-log.d.ts +5 -0
- package/dist/core/hook/hook-log.js.map +1 -1
- package/dist/core/hook/memo.d.ts +12 -0
- package/dist/core/hook/memo.js +41 -1
- package/dist/core/hook/memo.js.map +1 -1
- package/dist/core/hook/pointer-block.d.ts +1 -0
- package/dist/core/hook/pointer-block.js +23 -2
- package/dist/core/hook/pointer-block.js.map +1 -1
- package/dist/core/hook/query-extract.d.ts +2 -1
- package/dist/core/hook/query-extract.js +10 -1
- package/dist/core/hook/query-extract.js.map +1 -1
- package/dist/core/hook/resolve-conversation-by-query.d.ts +28 -0
- package/dist/core/hook/resolve-conversation-by-query.js +98 -0
- package/dist/core/hook/resolve-conversation-by-query.js.map +1 -0
- package/dist/core/hook/select.d.ts +8 -0
- package/dist/core/hook/select.js +9 -2
- package/dist/core/hook/select.js.map +1 -1
- package/dist/core/hook/transcript.js +16 -2
- package/dist/core/hook/transcript.js.map +1 -1
- package/dist/core/ingest/backfill-derivables.d.ts +92 -0
- package/dist/core/ingest/backfill-derivables.js +175 -0
- package/dist/core/ingest/backfill-derivables.js.map +1 -0
- package/dist/core/ingest/ingest-session.d.ts +5 -0
- package/dist/core/ingest/ingest-session.js +23 -0
- package/dist/core/ingest/ingest-session.js.map +1 -1
- package/dist/core/ingest/reclassify-oversized.d.ts +5 -0
- package/dist/core/ingest/reclassify-oversized.js +9 -0
- package/dist/core/ingest/reclassify-oversized.js.map +1 -1
- package/dist/core/ingest/reprocess.d.ts +96 -0
- package/dist/core/ingest/reprocess.js +280 -0
- package/dist/core/ingest/reprocess.js.map +1 -0
- package/dist/core/ingest/transcript-derivables.d.ts +27 -0
- package/dist/core/ingest/transcript-derivables.js +138 -0
- package/dist/core/ingest/transcript-derivables.js.map +1 -0
- package/dist/core/integrity/check-invariants.d.ts +6 -1
- package/dist/core/integrity/check-invariants.js +209 -3
- package/dist/core/integrity/check-invariants.js.map +1 -1
- package/dist/core/metrics/corpus-stats.d.ts +49 -0
- package/dist/core/metrics/corpus-stats.js +111 -0
- package/dist/core/metrics/corpus-stats.js.map +1 -0
- package/dist/core/metrics/re-derivation.d.ts +48 -0
- package/dist/core/metrics/re-derivation.js +87 -0
- package/dist/core/metrics/re-derivation.js.map +1 -0
- package/dist/core/outcome/coverage.d.ts +33 -0
- package/dist/core/outcome/coverage.js +46 -0
- package/dist/core/outcome/coverage.js.map +1 -0
- package/dist/core/outcome/rollup.d.ts +33 -0
- package/dist/core/outcome/rollup.js +113 -0
- package/dist/core/outcome/rollup.js.map +1 -0
- package/dist/core/recall/citation-log.d.ts +8 -0
- package/dist/core/recall/citation-log.js +17 -0
- package/dist/core/recall/citation-log.js.map +1 -1
- package/dist/core/recall/hook-recall-log.d.ts +2 -2
- package/dist/core/recall/hook-recall-log.js +2 -2
- package/dist/core/recall/query-intent.d.ts +7 -0
- package/dist/core/recall/query-intent.js +25 -0
- package/dist/core/recall/query-intent.js.map +1 -0
- package/dist/core/recall/query-log.d.ts +4 -0
- package/dist/core/recall/query-log.js +4 -0
- package/dist/core/recall/query-log.js.map +1 -1
- package/dist/core/recall/recall-service.d.ts +37 -1
- package/dist/core/recall/recall-service.js +106 -63
- package/dist/core/recall/recall-service.js.map +1 -1
- package/dist/core/recall/related-exemplars.d.ts +1 -0
- package/dist/core/recall/related-exemplars.js +4 -1
- package/dist/core/recall/related-exemplars.js.map +1 -1
- package/dist/core/recall/related-facts.js +2 -2
- package/dist/core/recall/related-facts.js.map +1 -1
- package/dist/core/recall/reranker.d.ts +2 -1
- package/dist/core/recall/reranker.js +3 -3
- package/dist/core/recall/reranker.js.map +1 -1
- package/dist/core/recall-facts/fact-query-log.d.ts +7 -0
- package/dist/core/recall-facts/fact-query-log.js +2 -0
- package/dist/core/recall-facts/fact-query-log.js.map +1 -1
- package/dist/core/recall-facts/fact-recall-service.d.ts +9 -1
- package/dist/core/recall-facts/fact-recall-service.js +22 -8
- package/dist/core/recall-facts/fact-recall-service.js.map +1 -1
- package/dist/core/scheduler/scan-once.d.ts +6 -6
- package/dist/core/scheduler/scan-once.js +15 -49
- package/dist/core/scheduler/scan-once.js.map +1 -1
- package/dist/core/scheduler/scheduler.d.ts +10 -0
- package/dist/core/scheduler/scheduler.js +109 -13
- package/dist/core/scheduler/scheduler.js.map +1 -1
- package/dist/core/scope/alias-map.d.ts +10 -0
- package/dist/core/scope/alias-map.js +41 -0
- package/dist/core/scope/alias-map.js.map +1 -0
- package/dist/core/scope/derive-scope.d.ts +8 -0
- package/dist/core/scope/derive-scope.js +54 -0
- package/dist/core/scope/derive-scope.js.map +1 -0
- package/dist/core/scope/scope-clause.d.ts +16 -0
- package/dist/core/scope/scope-clause.js +21 -0
- package/dist/core/scope/scope-clause.js.map +1 -0
- package/dist/core/scope/stamp-flag.d.ts +1 -0
- package/dist/core/scope/stamp-flag.js +4 -0
- package/dist/core/scope/stamp-flag.js.map +1 -0
- package/dist/core/signals/code-signal.d.ts +50 -0
- package/dist/core/signals/code-signal.js +54 -0
- package/dist/core/signals/code-signal.js.map +1 -0
- package/dist/core/signals/ingest-signal.d.ts +1 -1
- package/dist/core/signals/ingest-signal.js +3 -1
- package/dist/core/signals/ingest-signal.js.map +1 -1
- package/dist/core/signals/stamp-scope.d.ts +18 -0
- package/dist/core/signals/stamp-scope.js +28 -0
- package/dist/core/signals/stamp-scope.js.map +1 -0
- package/dist/core/storage/db-restore.js +3 -1
- package/dist/core/storage/db-restore.js.map +1 -1
- package/dist/core/storage/fact-batch.d.ts +9 -0
- package/dist/core/storage/fact-batch.js +14 -0
- package/dist/core/storage/fact-batch.js.map +1 -0
- package/dist/core/storage/pg-code-exemplar-store.d.ts +1 -0
- package/dist/core/storage/pg-code-exemplar-store.js +13 -2
- package/dist/core/storage/pg-code-exemplar-store.js.map +1 -1
- package/dist/core/storage/pg-embedding-config.d.ts +29 -0
- package/dist/core/storage/pg-embedding-config.js +53 -0
- package/dist/core/storage/pg-embedding-config.js.map +1 -0
- package/dist/core/storage/pg-entity-store.d.ts +12 -0
- package/dist/core/storage/pg-entity-store.js +79 -0
- package/dist/core/storage/pg-entity-store.js.map +1 -0
- package/dist/core/storage/pg-fact-ingest.d.ts +15 -0
- package/dist/core/storage/pg-fact-ingest.js +34 -0
- package/dist/core/storage/pg-fact-ingest.js.map +1 -0
- package/dist/core/storage/pg-fact-store.d.ts +3 -0
- package/dist/core/storage/pg-fact-store.js +26 -30
- package/dist/core/storage/pg-fact-store.js.map +1 -1
- package/dist/core/storage/pg-migrate.d.ts +18 -0
- package/dist/core/storage/pg-migrate.js +73 -0
- package/dist/core/storage/pg-migrate.js.map +1 -0
- package/dist/core/storage/pg-session-store.d.ts +11 -2
- package/dist/core/storage/pg-session-store.js +224 -96
- package/dist/core/storage/pg-session-store.js.map +1 -1
- package/dist/core/storage/pg-signal-store.js +4 -3
- package/dist/core/storage/pg-signal-store.js.map +1 -1
- package/dist/core/storage/pg-storage.d.ts +9 -7
- package/dist/core/storage/pg-storage.js +20 -50
- package/dist/core/storage/pg-storage.js.map +1 -1
- package/dist/core/storage/pg-workstream-store.d.ts +25 -0
- package/dist/core/storage/pg-workstream-store.js +97 -0
- package/dist/core/storage/pg-workstream-store.js.map +1 -0
- package/dist/core/storage/sqlite-code-exemplar-store.d.ts +1 -0
- package/dist/core/storage/sqlite-code-exemplar-store.js +15 -2
- package/dist/core/storage/sqlite-code-exemplar-store.js.map +1 -1
- package/dist/core/storage/sqlite-embedding-config.d.ts +10 -0
- package/dist/core/storage/sqlite-embedding-config.js +27 -0
- package/dist/core/storage/sqlite-embedding-config.js.map +1 -0
- package/dist/core/storage/sqlite-entity-store.d.ts +13 -0
- package/dist/core/storage/sqlite-entity-store.js +96 -0
- package/dist/core/storage/sqlite-entity-store.js.map +1 -0
- package/dist/core/storage/sqlite-fact-store.d.ts +15 -7
- package/dist/core/storage/sqlite-fact-store.js +81 -27
- package/dist/core/storage/sqlite-fact-store.js.map +1 -1
- package/dist/core/storage/sqlite-outcome-store.d.ts +89 -0
- package/dist/core/storage/sqlite-outcome-store.js +158 -0
- package/dist/core/storage/sqlite-outcome-store.js.map +1 -0
- package/dist/core/storage/sqlite-session-store.d.ts +42 -7
- package/dist/core/storage/sqlite-session-store.js +208 -95
- package/dist/core/storage/sqlite-session-store.js.map +1 -1
- package/dist/core/storage/sqlite-signal-store.js +4 -2
- package/dist/core/storage/sqlite-signal-store.js.map +1 -1
- package/dist/core/storage/sqlite-storage.d.ts +8 -8
- package/dist/core/storage/sqlite-storage.js +21 -32
- package/dist/core/storage/sqlite-storage.js.map +1 -1
- package/dist/core/storage/sqlite-workstream-store.d.ts +25 -0
- package/dist/core/storage/sqlite-workstream-store.js +99 -0
- package/dist/core/storage/sqlite-workstream-store.js.map +1 -0
- package/dist/core/storage/supersedence-log.js +2 -0
- package/dist/core/storage/supersedence-log.js.map +1 -1
- package/dist/core/telemetry/probe-filter.d.ts +35 -0
- package/dist/core/telemetry/probe-filter.js +56 -0
- package/dist/core/telemetry/probe-filter.js.map +1 -0
- package/dist/core/update-check/check.d.ts +6 -0
- package/dist/core/update-check/check.js +35 -0
- package/dist/core/update-check/check.js.map +1 -1
- package/dist/core/work-digest/active-spans.d.ts +8 -0
- package/dist/core/work-digest/active-spans.js +29 -0
- package/dist/core/work-digest/active-spans.js.map +1 -0
- package/dist/core/work-digest/attribute.d.ts +15 -0
- package/dist/core/work-digest/attribute.js +76 -0
- package/dist/core/work-digest/attribute.js.map +1 -0
- package/dist/core/work-digest/build-work-digest.d.ts +12 -0
- package/dist/core/work-digest/build-work-digest.js +69 -0
- package/dist/core/work-digest/build-work-digest.js.map +1 -0
- package/dist/core/work-digest/compose-work-digest.d.ts +3 -0
- package/dist/core/work-digest/compose-work-digest.js +52 -0
- package/dist/core/work-digest/compose-work-digest.js.map +1 -0
- package/dist/core/work-digest/merge-active.d.ts +10 -0
- package/dist/core/work-digest/merge-active.js +22 -0
- package/dist/core/work-digest/merge-active.js.map +1 -0
- package/dist/core/work-digest/read-transcript-timestamps.d.ts +7 -0
- package/dist/core/work-digest/read-transcript-timestamps.js +40 -0
- package/dist/core/work-digest/read-transcript-timestamps.js.map +1 -0
- package/dist/core/work-digest/topics.d.ts +18 -0
- package/dist/core/work-digest/topics.js +26 -0
- package/dist/core/work-digest/topics.js.map +1 -0
- package/dist/core/work-digest/types.d.ts +45 -0
- package/dist/core/work-digest/types.js +2 -0
- package/dist/core/work-digest/types.js.map +1 -0
- package/dist/core/workstream/bind.d.ts +26 -0
- package/dist/core/workstream/bind.js +42 -0
- package/dist/core/workstream/bind.js.map +1 -0
- package/dist/core/workstream/compose-recall.d.ts +2 -0
- package/dist/core/workstream/compose-recall.js +39 -0
- package/dist/core/workstream/compose-recall.js.map +1 -0
- package/dist/core/workstream/merge-suggest.d.ts +19 -0
- package/dist/core/workstream/merge-suggest.js +61 -0
- package/dist/core/workstream/merge-suggest.js.map +1 -0
- package/dist/core/workstream/model.d.ts +21 -0
- package/dist/core/workstream/model.js +9 -0
- package/dist/core/workstream/model.js.map +1 -0
- package/dist/core/workstream/name-match.d.ts +8 -0
- package/dist/core/workstream/name-match.js +18 -0
- package/dist/core/workstream/name-match.js.map +1 -0
- package/dist/core/workstream/resolve.d.ts +10 -0
- package/dist/core/workstream/resolve.js +21 -0
- package/dist/core/workstream/resolve.js.map +1 -0
- package/dist/core/workstream/rollup.d.ts +12 -0
- package/dist/core/workstream/rollup.js +17 -0
- package/dist/core/workstream/rollup.js.map +1 -0
- package/dist/core/workstream/work-topics.d.ts +10 -0
- package/dist/core/workstream/work-topics.js +55 -0
- package/dist/core/workstream/work-topics.js.map +1 -0
- package/dist/hook/hook-helpers.d.ts +4 -0
- package/dist/hook/hook-helpers.js +36 -0
- package/dist/hook/hook-helpers.js.map +1 -0
- package/dist/hook/pre-compact-hook.d.ts +23 -0
- package/dist/hook/pre-compact-hook.js +67 -0
- package/dist/hook/pre-compact-hook.js.map +1 -0
- package/dist/hook/prompt-recall-hook.d.ts +38 -0
- package/dist/hook/prompt-recall-hook.js +124 -20
- package/dist/hook/prompt-recall-hook.js.map +1 -1
- package/dist/hook/recall-gate.d.ts +29 -0
- package/dist/hook/recall-gate.js +50 -0
- package/dist/hook/recall-gate.js.map +1 -0
- package/dist/hook/recall-over-http.d.ts +3 -2
- package/dist/hook/recall-over-http.js +26 -13
- package/dist/hook/recall-over-http.js.map +1 -1
- package/dist/hook/recent-context.d.ts +25 -0
- package/dist/hook/recent-context.js +104 -0
- package/dist/hook/recent-context.js.map +1 -0
- package/dist/hook/score-floor.d.ts +27 -0
- package/dist/hook/score-floor.js +42 -0
- package/dist/hook/score-floor.js.map +1 -0
- package/dist/hook/session-end-hook.js +8 -29
- package/dist/hook/session-end-hook.js.map +1 -1
- package/dist/hook/session-start-hook.js +26 -60
- package/dist/hook/session-start-hook.js.map +1 -1
- package/dist/hook/stop-hook.js +24 -60
- package/dist/hook/stop-hook.js.map +1 -1
- package/dist/http/app.d.ts +6 -0
- package/dist/http/app.js +52 -44
- package/dist/http/app.js.map +1 -1
- package/dist/install/setup.js +2 -0
- package/dist/install/setup.js.map +1 -1
- package/dist/llm/build-classifier.d.ts +22 -0
- package/dist/llm/build-classifier.js +42 -0
- package/dist/llm/build-classifier.js.map +1 -0
- package/dist/llm/build-embedder.d.ts +11 -0
- package/dist/llm/build-embedder.js +42 -0
- package/dist/llm/build-embedder.js.map +1 -0
- package/dist/llm/bundled-embedder-client.d.ts +37 -0
- package/dist/llm/bundled-embedder-client.js +90 -0
- package/dist/llm/bundled-embedder-client.js.map +1 -0
- package/dist/llm/classifier-box.d.ts +13 -5
- package/dist/llm/classifier-box.js +28 -6
- package/dist/llm/classifier-box.js.map +1 -1
- package/dist/llm/classifier-egress.d.ts +18 -0
- package/dist/llm/classifier-egress.js +59 -0
- package/dist/llm/classifier-egress.js.map +1 -0
- package/dist/llm/client-shared.d.ts +4 -0
- package/dist/llm/client-shared.js +39 -0
- package/dist/llm/client-shared.js.map +1 -0
- package/dist/llm/deepseek-client.d.ts +20 -0
- package/dist/llm/deepseek-client.js +59 -45
- package/dist/llm/deepseek-client.js.map +1 -1
- package/dist/llm/embedder-info.d.ts +15 -0
- package/dist/llm/embedder-info.js +27 -0
- package/dist/llm/embedder-info.js.map +1 -0
- package/dist/llm/model-quirks.d.ts +4 -0
- package/dist/llm/model-quirks.js +7 -0
- package/dist/llm/model-quirks.js.map +1 -0
- package/dist/llm/naming.d.ts +5 -0
- package/dist/llm/naming.js +20 -0
- package/dist/llm/naming.js.map +1 -0
- package/dist/llm/ollama-client.d.ts +7 -8
- package/dist/llm/ollama-client.js +59 -51
- package/dist/llm/ollama-client.js.map +1 -1
- package/dist/llm/ollama-code-embedder.d.ts +8 -3
- package/dist/llm/ollama-code-embedder.js +14 -24
- package/dist/llm/ollama-code-embedder.js.map +1 -1
- package/dist/llm/openai-code-embedder-client.d.ts +25 -0
- package/dist/llm/openai-code-embedder-client.js +52 -0
- package/dist/llm/openai-code-embedder-client.js.map +1 -0
- package/dist/llm/openai-embedder-client.d.ts +41 -0
- package/dist/llm/openai-embedder-client.js +83 -0
- package/dist/llm/openai-embedder-client.js.map +1 -0
- package/dist/mcp/server.d.ts +69 -2
- package/dist/mcp/server.js +404 -19
- package/dist/mcp/server.js.map +1 -1
- package/dist/ports/code-embedder.d.ts +6 -1
- package/dist/ports/code-exemplar-store.d.ts +5 -0
- package/dist/ports/entity-store.d.ts +35 -0
- package/dist/ports/entity-store.js +7 -0
- package/dist/ports/entity-store.js.map +1 -0
- package/dist/ports/fact-store.d.ts +0 -0
- package/dist/ports/fact-store.js.map +1 -1
- package/dist/ports/llm-client.d.ts +9 -1
- package/dist/ports/outcome.d.ts +85 -0
- package/dist/ports/outcome.js +11 -0
- package/dist/ports/outcome.js.map +1 -0
- package/dist/ports/session-store.d.ts +12 -6
- package/dist/ports/storage.d.ts +7 -30
- package/dist/ports/storage.js +3 -7
- package/dist/ports/storage.js.map +1 -1
- package/dist/ports/transcript-adapter.d.ts +2 -0
- package/dist/ports/workstream-store.d.ts +22 -0
- package/dist/ports/workstream-store.js +2 -0
- package/dist/ports/workstream-store.js.map +1 -0
- package/dist/shared/net.d.ts +3 -0
- package/dist/shared/net.js +4 -0
- package/dist/shared/net.js.map +1 -0
- package/dist/shared/types.d.ts +56 -2
- package/dist/shared/types.js +4 -1
- package/dist/shared/types.js.map +1 -1
- package/dist/ui/assets/{index-CMpQkbQy.js → index-CSaBtgXD.js} +1 -1
- package/dist/ui/index.html +1 -1
- package/fixtures/classifier-gold/README.md +94 -0
- package/fixtures/classifier-gold/reference.json +318 -0
- package/fixtures/classifier-gold/transcripts/01-bug-fix-null-pointer-api.txt +75 -0
- package/fixtures/classifier-gold/transcripts/02-bug-fix-race-condition-queue.txt +77 -0
- package/fixtures/classifier-gold/transcripts/03-bug-fix-jwt-clock-skew.txt +86 -0
- package/fixtures/classifier-gold/transcripts/04-feature-webhook-retry.txt +95 -0
- package/fixtures/classifier-gold/transcripts/05-feature-csv-export.txt +86 -0
- package/fixtures/classifier-gold/transcripts/06-feature-rate-limiting.txt +61 -0
- package/fixtures/classifier-gold/transcripts/07-refactor-db-abstraction.txt +72 -0
- package/fixtures/classifier-gold/transcripts/08-refactor-config-loading.txt +89 -0
- package/fixtures/classifier-gold/transcripts/09-ops-docker-staging.txt +94 -0
- package/fixtures/classifier-gold/transcripts/10-ops-nginx-ssl.txt +121 -0
- package/fixtures/classifier-gold/transcripts/11-ops-postgres-migration.txt +74 -0
- package/fixtures/classifier-gold/transcripts/12-ops-cron-debugging.txt +73 -0
- package/fixtures/classifier-gold/transcripts/13-research-vector-db-comparison.txt +59 -0
- package/fixtures/classifier-gold/transcripts/14-research-oauth-provider.txt +74 -0
- package/fixtures/classifier-gold/transcripts/15-writing-api-docs.txt +154 -0
- package/fixtures/classifier-gold/transcripts/16-meeting-sprint-planning.txt +69 -0
- package/fixtures/classifier-gold/transcripts/17-trivial-list-files.txt +14 -0
- package/fixtures/classifier-gold/transcripts/18-trivial-version-check.txt +21 -0
- package/fixtures/classifier-gold/transcripts/19-trivial-syntax-help.txt +29 -0
- package/fixtures/classifier-gold/transcripts/20-trivial-read-package-json.txt +41 -0
- package/migrations/000_initial_schema.sql +1 -1
- package/migrations/024_backfill_exemplar_repo_basename.sql +32 -0
- package/migrations/025_workstreams.sql +30 -0
- package/migrations/026_embedding_config.sql +12 -0
- package/migrations/027_drop_legacy_session_embeddings.sql +7 -0
- package/migrations/028_classifier_provenance.sql +5 -0
- package/migrations/029_project_scope.sql +21 -0
- package/migrations/032_session_derivables.sql +11 -0
- package/migrations/pg/001_initial.sql +34 -0
- package/migrations/pg/019_split_replaces.sql +16 -7
- package/migrations/pg/025_workstreams.sql +34 -0
- package/migrations/pg/026_embedding_config.sql +12 -0
- package/migrations/pg/028_classifier_provenance.sql +5 -0
- package/migrations/pg/029_entity_variants.sql +9 -0
- package/migrations/pg/030_project_scope.sql +23 -0
- package/migrations/pg/031_widen_session_edges_kind.sql +11 -0
- package/migrations/pg/032_session_derivables.sql +17 -0
- package/nlm/index.js +405 -39
- package/package.json +12 -2
- package/plugin/.codex-plugin/plugin.json +2 -2
- package/plugin/scripts/prompt-recall-hook.mjs +413 -43
- package/plugin/scripts/session-start-hook.mjs +295 -61
- package/plugin/scripts/stop-hook.mjs +89 -66
- package/plugin-hermes-agent/__init__.py +2 -1
- package/templates/agent-contract/README.md +24 -0
- package/templates/agent-contract/claude-code.md +40 -0
- package/templates/agent-contract/generic.md +38 -0
- package/dist/core/embedding/embed-normalize.d.ts +0 -28
- package/dist/core/embedding/embed-normalize.js +0 -98
- package/dist/core/embedding/embed-normalize.js.map +0 -1
- package/dist/core/storage/pg-tx-context.d.ts +0 -52
- package/dist/core/storage/pg-tx-context.js +0 -149
- package/dist/core/storage/pg-tx-context.js.map +0 -1
package/nlm/index.js
CHANGED
|
@@ -181,30 +181,63 @@ var STOPWORDS = /* @__PURE__ */ new Set([
|
|
|
181
181
|
]);
|
|
182
182
|
var MIN_CONTENT_WORDS = 2;
|
|
183
183
|
var MIN_WORD_LEN = 3;
|
|
184
|
+
var SYSTEM_MESSAGE_PREFIX = /^<(task-notification|command-name|command-message|command-args|local-command-stdout|local-command-caveat|output-file|system-reminder)\b/;
|
|
184
185
|
function extractRecallQuery(prompt) {
|
|
186
|
+
if (SYSTEM_MESSAGE_PREFIX.test(prompt.trim())) return null;
|
|
185
187
|
const tokens = prompt.trim().split(/\s+/).map((t) => t.replace(/^[^\w-]+|[^\w-]+$/g, "")).filter((t) => t.length >= MIN_WORD_LEN);
|
|
186
188
|
const contentWords = tokens.filter((t) => !STOPWORDS.has(t.toLowerCase()));
|
|
187
189
|
if (contentWords.length < MIN_CONTENT_WORDS) return null;
|
|
188
190
|
return contentWords.join(" ");
|
|
189
191
|
}
|
|
190
192
|
|
|
193
|
+
// src/hook/hook-helpers.ts
|
|
194
|
+
function readStdin() {
|
|
195
|
+
return new Promise((resolve2) => {
|
|
196
|
+
let data = "";
|
|
197
|
+
process.stdin.setEncoding("utf8");
|
|
198
|
+
process.stdin.on("data", (chunk) => data += chunk);
|
|
199
|
+
process.stdin.on("end", () => resolve2(data));
|
|
200
|
+
process.stdin.on("error", () => resolve2(data));
|
|
201
|
+
});
|
|
202
|
+
}
|
|
203
|
+
async function fetchWithTimeout(url, init, ms) {
|
|
204
|
+
const controller = new AbortController();
|
|
205
|
+
const timer = setTimeout(() => controller.abort(), ms);
|
|
206
|
+
try {
|
|
207
|
+
return await fetch(url, { ...init, signal: controller.signal });
|
|
208
|
+
} finally {
|
|
209
|
+
clearTimeout(timer);
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
function hookModeFromEnv() {
|
|
213
|
+
return process.env["NLM_HOOK_MODE"] === "live" ? "live" : "shadow";
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
// src/shared/net.ts
|
|
217
|
+
var DEFAULT_NLM_PORT = "3940";
|
|
218
|
+
|
|
191
219
|
// src/hook/recall-over-http.ts
|
|
192
220
|
var RECALL_LIMIT = 5;
|
|
193
|
-
|
|
194
|
-
|
|
221
|
+
function parseRecallTimeout(raw) {
|
|
222
|
+
if (raw === void 0) return 4e3;
|
|
223
|
+
const n = Number(raw);
|
|
224
|
+
return Number.isFinite(n) && n > 0 ? n : 4e3;
|
|
225
|
+
}
|
|
226
|
+
var RECALL_TIMEOUT_MS = parseRecallTimeout(process.env["NLM_HOOK_RECALL_TIMEOUT_MS"]);
|
|
227
|
+
async function recallOverHttp(prompt, runtime, conversationId, mode = "keyword") {
|
|
195
228
|
const query = extractRecallQuery(prompt);
|
|
196
229
|
if (query === null) return { hits: [], facts: [], exemplars: [] };
|
|
197
|
-
const portValue = process.env["NLM_PORT"] ??
|
|
198
|
-
const url =
|
|
199
|
-
|
|
200
|
-
|
|
230
|
+
const portValue = process.env["NLM_PORT"] ?? DEFAULT_NLM_PORT;
|
|
231
|
+
const url = (
|
|
232
|
+
// 127.0.0.1, not localhost: each hook is a fresh process with no connection
|
|
233
|
+
// reuse, and Node resolves localhost to IPv6 ::1 first — a measured ~50-300ms
|
|
234
|
+
// per-fire connect penalty vs ~3ms on the explicit IPv4 loopback.
|
|
235
|
+
`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)}` : "")
|
|
236
|
+
);
|
|
201
237
|
try {
|
|
202
238
|
const extra = { "x-recall-source": "hook" };
|
|
203
239
|
if (runtime) extra["x-recall-runtime"] = runtime;
|
|
204
|
-
const res = await
|
|
205
|
-
headers: hookAuthHeaders(extra),
|
|
206
|
-
signal: controller.signal
|
|
207
|
-
});
|
|
240
|
+
const res = await fetchWithTimeout(url, { headers: hookAuthHeaders(extra) }, RECALL_TIMEOUT_MS);
|
|
208
241
|
if (!res.ok) return { hits: [], facts: [], exemplars: [] };
|
|
209
242
|
let body;
|
|
210
243
|
try {
|
|
@@ -232,8 +265,8 @@ async function recallOverHttp(prompt, runtime, conversationId) {
|
|
|
232
265
|
taskContext: e.taskContext
|
|
233
266
|
}));
|
|
234
267
|
return { hits, facts, exemplars };
|
|
235
|
-
}
|
|
236
|
-
|
|
268
|
+
} catch {
|
|
269
|
+
return { hits: [], facts: [], exemplars: [] };
|
|
237
270
|
}
|
|
238
271
|
}
|
|
239
272
|
|
|
@@ -243,14 +276,192 @@ import { pathToFileURL } from "node:url";
|
|
|
243
276
|
// src/core/hook/gate.ts
|
|
244
277
|
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;
|
|
245
278
|
var GENERATIVE_OPENER = /^(write|draft|create|compose|generate|brainstorm|design|outline|sketch|invent|rename|come up with)\b/i;
|
|
279
|
+
var HARNESS_TAGS = [
|
|
280
|
+
"ide_selection",
|
|
281
|
+
"ide_opened_file",
|
|
282
|
+
"ide_closed_file",
|
|
283
|
+
"ide_diagnostics",
|
|
284
|
+
"ide_recently_modified_file",
|
|
285
|
+
"task-notification",
|
|
286
|
+
"system-reminder",
|
|
287
|
+
"command-name",
|
|
288
|
+
"command-message",
|
|
289
|
+
"command-args",
|
|
290
|
+
"local-command-stdout",
|
|
291
|
+
"local-command-stderr"
|
|
292
|
+
];
|
|
293
|
+
var HARNESS_BLOCK = new RegExp(
|
|
294
|
+
`<(${HARNESS_TAGS.join("|")})\\b[^>]*>[\\s\\S]*?(?:</\\1>|$)`,
|
|
295
|
+
"gi"
|
|
296
|
+
);
|
|
297
|
+
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;
|
|
298
|
+
function contentWordCount(s) {
|
|
299
|
+
const m = s.match(/[A-Za-z0-9]{2,}/g);
|
|
300
|
+
return m ? m.length : 0;
|
|
301
|
+
}
|
|
246
302
|
function classifyPrompt(prompt) {
|
|
247
|
-
let p = prompt.trim();
|
|
303
|
+
let p = prompt.replace(HARNESS_BLOCK, " ").replace(/\s+/g, " ").trim();
|
|
304
|
+
if (contentWordCount(p) === 0) return "skip";
|
|
305
|
+
if (ACK_ONLY.test(p)) return "skip";
|
|
248
306
|
for (let i = 0; i < 3 && LEADING_FILLER.test(p); i++) {
|
|
249
307
|
p = p.replace(LEADING_FILLER, "");
|
|
250
308
|
}
|
|
309
|
+
p = p.trim();
|
|
310
|
+
if (contentWordCount(p) === 0) return "skip";
|
|
251
311
|
return GENERATIVE_OPENER.test(p) ? "generative" : "evaluate";
|
|
252
312
|
}
|
|
253
313
|
|
|
314
|
+
// src/hook/recent-context.ts
|
|
315
|
+
import { closeSync, existsSync as existsSync2, fstatSync, openSync, readSync } from "node:fs";
|
|
316
|
+
var DEFAULT_MAX_TURNS = 3;
|
|
317
|
+
var DEFAULT_MAX_BYTES = 64 * 1024;
|
|
318
|
+
var DEFAULT_PER_TURN_CHARS = 400;
|
|
319
|
+
var STOPWORDS2 = /* @__PURE__ */ new Set([
|
|
320
|
+
"the",
|
|
321
|
+
"a",
|
|
322
|
+
"an",
|
|
323
|
+
"and",
|
|
324
|
+
"or",
|
|
325
|
+
"but",
|
|
326
|
+
"is",
|
|
327
|
+
"are",
|
|
328
|
+
"was",
|
|
329
|
+
"were",
|
|
330
|
+
"be",
|
|
331
|
+
"been",
|
|
332
|
+
"being",
|
|
333
|
+
"do",
|
|
334
|
+
"does",
|
|
335
|
+
"did",
|
|
336
|
+
"doing",
|
|
337
|
+
"you",
|
|
338
|
+
"your",
|
|
339
|
+
"i",
|
|
340
|
+
"we",
|
|
341
|
+
"our",
|
|
342
|
+
"it",
|
|
343
|
+
"its",
|
|
344
|
+
"this",
|
|
345
|
+
"that",
|
|
346
|
+
"these",
|
|
347
|
+
"those",
|
|
348
|
+
"what",
|
|
349
|
+
"which",
|
|
350
|
+
"how",
|
|
351
|
+
"why",
|
|
352
|
+
"when",
|
|
353
|
+
"where",
|
|
354
|
+
"who",
|
|
355
|
+
"can",
|
|
356
|
+
"could",
|
|
357
|
+
"would",
|
|
358
|
+
"should",
|
|
359
|
+
"will",
|
|
360
|
+
"shall",
|
|
361
|
+
"may",
|
|
362
|
+
"might",
|
|
363
|
+
"must",
|
|
364
|
+
"please",
|
|
365
|
+
"to",
|
|
366
|
+
"of",
|
|
367
|
+
"in",
|
|
368
|
+
"on",
|
|
369
|
+
"for",
|
|
370
|
+
"with",
|
|
371
|
+
"so",
|
|
372
|
+
"now",
|
|
373
|
+
"then",
|
|
374
|
+
"here",
|
|
375
|
+
"there",
|
|
376
|
+
"just",
|
|
377
|
+
"also",
|
|
378
|
+
"as",
|
|
379
|
+
"at",
|
|
380
|
+
"by",
|
|
381
|
+
"if",
|
|
382
|
+
"my",
|
|
383
|
+
"me",
|
|
384
|
+
"us",
|
|
385
|
+
"ok",
|
|
386
|
+
"okay",
|
|
387
|
+
"yes",
|
|
388
|
+
"no",
|
|
389
|
+
"not",
|
|
390
|
+
"up",
|
|
391
|
+
"out",
|
|
392
|
+
"go",
|
|
393
|
+
"get",
|
|
394
|
+
"got",
|
|
395
|
+
"make",
|
|
396
|
+
"made",
|
|
397
|
+
"give",
|
|
398
|
+
"tell",
|
|
399
|
+
"want",
|
|
400
|
+
"need",
|
|
401
|
+
"think",
|
|
402
|
+
"know",
|
|
403
|
+
"let",
|
|
404
|
+
"lets",
|
|
405
|
+
"about",
|
|
406
|
+
"from",
|
|
407
|
+
"into"
|
|
408
|
+
]);
|
|
409
|
+
function topicalWordCount(s) {
|
|
410
|
+
const tokens = s.toLowerCase().match(/[a-z0-9]{2,}/g) ?? [];
|
|
411
|
+
return tokens.filter((t) => !STOPWORDS2.has(t)).length;
|
|
412
|
+
}
|
|
413
|
+
function textOf(message) {
|
|
414
|
+
if (typeof message !== "object" || message === null) return "";
|
|
415
|
+
const content = message.content;
|
|
416
|
+
if (typeof content === "string") return content;
|
|
417
|
+
if (Array.isArray(content)) {
|
|
418
|
+
return content.filter(
|
|
419
|
+
(b) => typeof b === "object" && b !== null && b.type === "text"
|
|
420
|
+
).map((b) => b.text).join(" ");
|
|
421
|
+
}
|
|
422
|
+
return "";
|
|
423
|
+
}
|
|
424
|
+
function tailRead(path, maxBytes) {
|
|
425
|
+
const fd = openSync(path, "r");
|
|
426
|
+
try {
|
|
427
|
+
const size = fstatSync(fd).size;
|
|
428
|
+
const start = size > maxBytes ? size - maxBytes : 0;
|
|
429
|
+
const len = size - start;
|
|
430
|
+
if (len <= 0) return "";
|
|
431
|
+
const buf = Buffer.alloc(len);
|
|
432
|
+
readSync(fd, buf, 0, len, start);
|
|
433
|
+
return buf.toString("utf8");
|
|
434
|
+
} finally {
|
|
435
|
+
closeSync(fd);
|
|
436
|
+
}
|
|
437
|
+
}
|
|
438
|
+
function recentConversationContext(transcriptPath, opts = {}) {
|
|
439
|
+
try {
|
|
440
|
+
if (!transcriptPath || !existsSync2(transcriptPath)) return "";
|
|
441
|
+
const maxTurns = opts.maxTurns ?? DEFAULT_MAX_TURNS;
|
|
442
|
+
const perTurnChars = opts.perTurnChars ?? DEFAULT_PER_TURN_CHARS;
|
|
443
|
+
const lines = tailRead(transcriptPath, opts.maxBytes ?? DEFAULT_MAX_BYTES).split("\n");
|
|
444
|
+
const turns = [];
|
|
445
|
+
for (let i = lines.length - 1; i >= 0 && turns.length < maxTurns; i--) {
|
|
446
|
+
const line = lines[i].trim();
|
|
447
|
+
if (!line) continue;
|
|
448
|
+
let evt;
|
|
449
|
+
try {
|
|
450
|
+
evt = JSON.parse(line);
|
|
451
|
+
} catch {
|
|
452
|
+
continue;
|
|
453
|
+
}
|
|
454
|
+
if (evt.type !== "user" && evt.type !== "assistant") continue;
|
|
455
|
+
const text = textOf(evt.message).trim();
|
|
456
|
+
if (!text) continue;
|
|
457
|
+
turns.unshift(text.slice(0, perTurnChars));
|
|
458
|
+
}
|
|
459
|
+
return turns.join(" ").trim();
|
|
460
|
+
} catch {
|
|
461
|
+
return "";
|
|
462
|
+
}
|
|
463
|
+
}
|
|
464
|
+
|
|
254
465
|
// src/core/hook/hook-log.ts
|
|
255
466
|
import { appendFileSync, mkdirSync } from "node:fs";
|
|
256
467
|
import { homedir as homedir2 } from "node:os";
|
|
@@ -269,7 +480,15 @@ function appendHookLog(entry) {
|
|
|
269
480
|
}
|
|
270
481
|
|
|
271
482
|
// src/core/hook/memo.ts
|
|
272
|
-
import {
|
|
483
|
+
import {
|
|
484
|
+
existsSync as existsSync3,
|
|
485
|
+
mkdirSync as mkdirSync2,
|
|
486
|
+
readdirSync,
|
|
487
|
+
readFileSync as readFileSync2,
|
|
488
|
+
rmSync,
|
|
489
|
+
statSync,
|
|
490
|
+
writeFileSync
|
|
491
|
+
} from "node:fs";
|
|
273
492
|
import { homedir as homedir3 } from "node:os";
|
|
274
493
|
import { join as join2 } from "node:path";
|
|
275
494
|
function stateDir() {
|
|
@@ -282,7 +501,7 @@ function memoPath(conversationId) {
|
|
|
282
501
|
function loadSurfaced(conversationId) {
|
|
283
502
|
try {
|
|
284
503
|
const path = memoPath(conversationId);
|
|
285
|
-
if (!
|
|
504
|
+
if (!existsSync3(path)) return /* @__PURE__ */ new Set();
|
|
286
505
|
const parsed = JSON.parse(readFileSync2(path, "utf8"));
|
|
287
506
|
if (!Array.isArray(parsed)) return /* @__PURE__ */ new Set();
|
|
288
507
|
return new Set(parsed.filter((x) => typeof x === "string"));
|
|
@@ -301,6 +520,26 @@ function recordSurfaced(conversationId, ids) {
|
|
|
301
520
|
}
|
|
302
521
|
|
|
303
522
|
// src/core/hook/pointer-block.ts
|
|
523
|
+
function truncateSummary(s, max = 200) {
|
|
524
|
+
if (s.length <= max) return s;
|
|
525
|
+
const window = s.slice(0, max);
|
|
526
|
+
const last = window[window.length - 1];
|
|
527
|
+
if ((last === "." || last === "!" || last === "?") && window.length - 1 >= 60) {
|
|
528
|
+
return window;
|
|
529
|
+
}
|
|
530
|
+
for (let i = window.length - 2; i >= 60; i--) {
|
|
531
|
+
const c = window[i];
|
|
532
|
+
if ((c === "." || c === "!" || c === "?") && window[i + 1] === " ") {
|
|
533
|
+
return s.slice(0, i + 1);
|
|
534
|
+
}
|
|
535
|
+
}
|
|
536
|
+
for (let i = window.length - 1; i >= 60; i--) {
|
|
537
|
+
if (window[i] === " ") {
|
|
538
|
+
return s.slice(0, i) + " ...";
|
|
539
|
+
}
|
|
540
|
+
}
|
|
541
|
+
return window + " ...";
|
|
542
|
+
}
|
|
304
543
|
function formatPointerBlock(hits, facts = [], exemplars = []) {
|
|
305
544
|
if (hits.length === 0 && facts.length === 0 && exemplars.length === 0) return "";
|
|
306
545
|
const out = [];
|
|
@@ -309,7 +548,7 @@ function formatPointerBlock(hits, facts = [], exemplars = []) {
|
|
|
309
548
|
for (const h of hits) {
|
|
310
549
|
const datePart = h.startedAt.slice(0, 10);
|
|
311
550
|
if (h.summary) {
|
|
312
|
-
out.push(`- ${h.id} \xB7 ${h.label} (${datePart}) \u2014 ${h.summary
|
|
551
|
+
out.push(`- ${h.id} \xB7 ${h.label} (${datePart}) \u2014 ${truncateSummary(h.summary)}`);
|
|
313
552
|
} else {
|
|
314
553
|
out.push(`- ${h.id} \xB7 ${h.label} (${datePart})`);
|
|
315
554
|
}
|
|
@@ -328,7 +567,7 @@ function formatPointerBlock(hits, facts = [], exemplars = []) {
|
|
|
328
567
|
out.push("## Related code exemplars (nlm-memory)");
|
|
329
568
|
for (const e of exemplars) {
|
|
330
569
|
const langPart = e.lang ? `${e.lang} \xB7 ` : "";
|
|
331
|
-
out.push(`- [${e.outcome}] ${langPart}${e.repo} - ${e.taskContext
|
|
570
|
+
out.push(`- [${e.outcome}] ${langPart}${e.repo} - ${truncateSummary(e.taskContext)}`);
|
|
332
571
|
}
|
|
333
572
|
}
|
|
334
573
|
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).";
|
|
@@ -337,25 +576,125 @@ function formatPointerBlock(hits, facts = [], exemplars = []) {
|
|
|
337
576
|
}
|
|
338
577
|
|
|
339
578
|
// src/core/hook/select.ts
|
|
579
|
+
function medianScore(hits) {
|
|
580
|
+
if (hits.length === 0) return 0;
|
|
581
|
+
const sorted = hits.map((h) => h.matchScore).sort((a, b) => a - b);
|
|
582
|
+
return sorted[Math.floor(sorted.length / 2)] ?? 0;
|
|
583
|
+
}
|
|
340
584
|
function selectHits(params) {
|
|
341
|
-
const { hits, surfaced, scoreThreshold, perFireCap, perConversationCap } = params;
|
|
585
|
+
const { hits, surfaced, scoreThreshold, perFireCap, perConversationCap, relativeFloor = 0 } = params;
|
|
586
|
+
const relCut = relativeFloor > 0 ? relativeFloor * medianScore(hits) : 0;
|
|
342
587
|
const eligible = hits.filter(
|
|
343
|
-
(h) => h.matchScore >= scoreThreshold && !surfaced.has(h.id)
|
|
588
|
+
(h) => h.matchScore >= scoreThreshold && h.matchScore >= relCut && !surfaced.has(h.id)
|
|
344
589
|
);
|
|
345
590
|
const budget = Math.max(0, perConversationCap - surfaced.size);
|
|
346
591
|
const limit = Math.min(perFireCap, budget);
|
|
347
592
|
return eligible.slice(0, limit);
|
|
348
593
|
}
|
|
349
594
|
|
|
595
|
+
// src/hook/score-floor.ts
|
|
596
|
+
function parseScoreFloor(raw) {
|
|
597
|
+
if (raw === void 0) return 0;
|
|
598
|
+
const parsed = Number(raw);
|
|
599
|
+
if (!Number.isFinite(parsed) || parsed < 0) return 0;
|
|
600
|
+
return parsed;
|
|
601
|
+
}
|
|
602
|
+
function parseRelativeFloor(raw, fallback) {
|
|
603
|
+
if (raw === void 0) return fallback;
|
|
604
|
+
const parsed = Number(raw);
|
|
605
|
+
if (!Number.isFinite(parsed) || parsed < 0) return fallback;
|
|
606
|
+
return parsed;
|
|
607
|
+
}
|
|
608
|
+
|
|
609
|
+
// src/hook/recall-gate.ts
|
|
610
|
+
var RECALL_GATE_MODEL = "qwen3.5:4b";
|
|
611
|
+
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"}.`;
|
|
612
|
+
var GATE_FORMAT = { type: "object", properties: { gate: { type: "string", enum: ["relevant", "irrelevant"] } }, required: ["gate"] };
|
|
613
|
+
var GATE_OPTS = { temperature: 0, top_p: 1, top_k: 0, presence_penalty: 0, frequency_penalty: 0 };
|
|
614
|
+
function parseRecallGateMode(env = process.env) {
|
|
615
|
+
const v = env["NLM_HOOK_RECALL_GATE"]?.trim();
|
|
616
|
+
return v === "shadow" || v === "live" ? v : void 0;
|
|
617
|
+
}
|
|
618
|
+
var GATE_TIMEOUT_MS = 4e3;
|
|
619
|
+
function makeOllamaGate(url, model = RECALL_GATE_MODEL, timeoutMs = GATE_TIMEOUT_MS) {
|
|
620
|
+
return async (prompt, candidate) => {
|
|
621
|
+
try {
|
|
622
|
+
const r = await fetchWithTimeout(
|
|
623
|
+
`${url}/api/chat`,
|
|
624
|
+
{
|
|
625
|
+
method: "POST",
|
|
626
|
+
headers: { "Content-Type": "application/json" },
|
|
627
|
+
body: JSON.stringify({
|
|
628
|
+
model,
|
|
629
|
+
stream: false,
|
|
630
|
+
think: false,
|
|
631
|
+
// Keep the gate model resident between fires so only the first fire
|
|
632
|
+
// pays the cold-load; subsequent gates are ~1 judge call.
|
|
633
|
+
keep_alive: "15m",
|
|
634
|
+
format: GATE_FORMAT,
|
|
635
|
+
options: GATE_OPTS,
|
|
636
|
+
messages: [
|
|
637
|
+
{ role: "system", content: RECALL_GATE_SYSTEM },
|
|
638
|
+
{ role: "user", content: `USER PROMPT:
|
|
639
|
+
${prompt}
|
|
640
|
+
|
|
641
|
+
CANDIDATE CONTEXT:
|
|
642
|
+
${candidate}` }
|
|
643
|
+
]
|
|
644
|
+
})
|
|
645
|
+
},
|
|
646
|
+
timeoutMs
|
|
647
|
+
);
|
|
648
|
+
const d = await r.json();
|
|
649
|
+
const v = JSON.parse(d.message?.content ?? "{}").gate;
|
|
650
|
+
return v === "irrelevant" ? "irrelevant" : "relevant";
|
|
651
|
+
} catch {
|
|
652
|
+
return "relevant";
|
|
653
|
+
}
|
|
654
|
+
};
|
|
655
|
+
}
|
|
656
|
+
|
|
350
657
|
// src/hook/prompt-recall-hook.ts
|
|
351
|
-
var SCORE_THRESHOLD =
|
|
658
|
+
var SCORE_THRESHOLD = parseScoreFloor(process.env["NLM_RECALL_SCORE_FLOOR"]);
|
|
659
|
+
var RELATIVE_FLOOR = parseRelativeFloor(process.env["NLM_RECALL_REL_FLOOR"], 0.9);
|
|
352
660
|
var PER_FIRE_CAP = 3;
|
|
353
661
|
var PER_CONVERSATION_CAP = 10;
|
|
354
662
|
var PROMPT_PREVIEW_CHARS = 200;
|
|
663
|
+
function parseHookDeadline(raw, defaultMs = 4e3) {
|
|
664
|
+
if (raw === void 0) return defaultMs;
|
|
665
|
+
const n = Number(raw);
|
|
666
|
+
return Number.isFinite(n) && n > 0 ? n : defaultMs;
|
|
667
|
+
}
|
|
668
|
+
var HOOK_DEADLINE_MS = parseHookDeadline(process.env["NLM_HOOK_DEADLINE_MS"], 6e3);
|
|
669
|
+
async function withDeadline(p, ms, fallback) {
|
|
670
|
+
if (ms <= 0) return fallback;
|
|
671
|
+
let timer;
|
|
672
|
+
const timeout = new Promise((resolve2) => {
|
|
673
|
+
timer = setTimeout(() => resolve2(fallback), ms);
|
|
674
|
+
});
|
|
675
|
+
try {
|
|
676
|
+
return await Promise.race([p, timeout]);
|
|
677
|
+
} finally {
|
|
678
|
+
clearTimeout(timer);
|
|
679
|
+
}
|
|
680
|
+
}
|
|
355
681
|
function hookRuntimeFromEnv(env = process.env) {
|
|
356
682
|
const raw = env["NLM_HOOK_RUNTIME"]?.trim();
|
|
357
683
|
return raw ? raw : "claude-code";
|
|
358
684
|
}
|
|
685
|
+
function promptRecallEnabled(env = process.env) {
|
|
686
|
+
const raw = env["NLM_HOOK_PROMPT_RECALL"]?.trim();
|
|
687
|
+
if (raw === void 0 || raw === "") return false;
|
|
688
|
+
return raw.toLowerCase() !== "off";
|
|
689
|
+
}
|
|
690
|
+
function buildRecallQuery(input, env = process.env) {
|
|
691
|
+
if (env["NLM_HOOK_CONTEXT_RECALL"] !== "1") return input.prompt;
|
|
692
|
+
if (!input.transcriptPath) return input.prompt;
|
|
693
|
+
const minWords = Number.parseInt(env["NLM_HOOK_CONTEXT_MIN_WORDS"] ?? "3", 10);
|
|
694
|
+
if (topicalWordCount(input.prompt) >= minWords) return input.prompt;
|
|
695
|
+
const context = recentConversationContext(input.transcriptPath);
|
|
696
|
+
return context ? `${context} ${input.prompt}` : input.prompt;
|
|
697
|
+
}
|
|
359
698
|
function normalizeRecall(raw) {
|
|
360
699
|
if (Array.isArray(raw)) return { hits: raw, facts: [] };
|
|
361
700
|
return raw;
|
|
@@ -363,7 +702,7 @@ function normalizeRecall(raw) {
|
|
|
363
702
|
async function runHook(input, deps) {
|
|
364
703
|
const gate = classifyPrompt(input.prompt);
|
|
365
704
|
const preview = input.prompt.slice(0, PROMPT_PREVIEW_CHARS);
|
|
366
|
-
if (gate === "generative") {
|
|
705
|
+
if (gate === "generative" || gate === "skip") {
|
|
367
706
|
appendHookLog({
|
|
368
707
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
369
708
|
conversationId: input.conversationId,
|
|
@@ -376,9 +715,12 @@ async function runHook(input, deps) {
|
|
|
376
715
|
});
|
|
377
716
|
return "";
|
|
378
717
|
}
|
|
718
|
+
const deadline = Date.now() + (deps.deadlineMs ?? HOOK_DEADLINE_MS);
|
|
379
719
|
let fetched = { hits: [], facts: [] };
|
|
380
720
|
try {
|
|
381
|
-
fetched = normalizeRecall(
|
|
721
|
+
fetched = normalizeRecall(
|
|
722
|
+
await withDeadline(deps.recall(buildRecallQuery(input)), deadline - Date.now(), { hits: [], facts: [] })
|
|
723
|
+
);
|
|
382
724
|
} catch {
|
|
383
725
|
fetched = { hits: [], facts: [] };
|
|
384
726
|
}
|
|
@@ -388,10 +730,33 @@ async function runHook(input, deps) {
|
|
|
388
730
|
hits,
|
|
389
731
|
surfaced,
|
|
390
732
|
scoreThreshold: SCORE_THRESHOLD,
|
|
733
|
+
relativeFloor: RELATIVE_FLOOR,
|
|
391
734
|
perFireCap: PER_FIRE_CAP,
|
|
392
735
|
perConversationCap: PER_CONVERSATION_CAP
|
|
393
736
|
});
|
|
394
|
-
|
|
737
|
+
let gateDecisions;
|
|
738
|
+
let injected = selected;
|
|
739
|
+
if (deps.recallGate && selected.length > 0) {
|
|
740
|
+
const g = deps.recallGate;
|
|
741
|
+
const toGate = g.maxCandidates ? selected.slice(0, g.maxCandidates) : selected;
|
|
742
|
+
const remaining = deadline - Date.now();
|
|
743
|
+
const gateFallback = toGate.map((h) => ({ id: h.id, gate: "relevant" }));
|
|
744
|
+
if (remaining <= 0) {
|
|
745
|
+
gateDecisions = gateFallback;
|
|
746
|
+
} else {
|
|
747
|
+
gateDecisions = await withDeadline(
|
|
748
|
+
Promise.all(toGate.map(async (h) => ({ id: h.id, gate: await g.judge(input.prompt, `${h.label}
|
|
749
|
+
${h.summary ?? ""}`) }))),
|
|
750
|
+
remaining,
|
|
751
|
+
gateFallback
|
|
752
|
+
);
|
|
753
|
+
}
|
|
754
|
+
if (g.mode === "live") {
|
|
755
|
+
const drop = new Set(gateDecisions.filter((d) => d.gate === "irrelevant").map((d) => d.id));
|
|
756
|
+
injected = selected.filter((h) => !drop.has(h.id));
|
|
757
|
+
}
|
|
758
|
+
}
|
|
759
|
+
const block = formatPointerBlock(injected, fetched.facts, fetched.exemplars);
|
|
395
760
|
const estTokens = Math.ceil(block.length / 4);
|
|
396
761
|
appendHookLog({
|
|
397
762
|
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
@@ -399,25 +764,17 @@ async function runHook(input, deps) {
|
|
|
399
764
|
promptPreview: preview,
|
|
400
765
|
gate,
|
|
401
766
|
hits: hits.map((h) => ({ id: h.id, score: h.matchScore })),
|
|
402
|
-
wouldInject:
|
|
767
|
+
wouldInject: injected.map((h) => h.id),
|
|
403
768
|
estTokens,
|
|
404
|
-
mode: deps.mode
|
|
769
|
+
mode: deps.mode,
|
|
770
|
+
...gateDecisions ? { gateDecisions } : {}
|
|
405
771
|
});
|
|
406
|
-
if (deps.mode === "live" &&
|
|
407
|
-
recordSurfaced(input.conversationId,
|
|
772
|
+
if (deps.mode === "live" && injected.length > 0) {
|
|
773
|
+
recordSurfaced(input.conversationId, injected.map((h) => h.id));
|
|
408
774
|
return block;
|
|
409
775
|
}
|
|
410
776
|
return "";
|
|
411
777
|
}
|
|
412
|
-
function readStdin() {
|
|
413
|
-
return new Promise((resolve2) => {
|
|
414
|
-
let data = "";
|
|
415
|
-
process.stdin.setEncoding("utf8");
|
|
416
|
-
process.stdin.on("data", (chunk) => data += chunk);
|
|
417
|
-
process.stdin.on("end", () => resolve2(data));
|
|
418
|
-
process.stdin.on("error", () => resolve2(data));
|
|
419
|
-
});
|
|
420
|
-
}
|
|
421
778
|
async function main() {
|
|
422
779
|
try {
|
|
423
780
|
autoloadEnv();
|
|
@@ -425,12 +782,21 @@ async function main() {
|
|
|
425
782
|
const payload = JSON.parse(raw);
|
|
426
783
|
const prompt = typeof payload.prompt === "string" ? payload.prompt : "";
|
|
427
784
|
const conversationId = typeof payload.session_id === "string" ? payload.session_id : "unknown";
|
|
785
|
+
const transcriptPath = typeof payload.transcript_path === "string" ? payload.transcript_path : void 0;
|
|
428
786
|
if (!prompt) return;
|
|
429
|
-
|
|
787
|
+
if (!promptRecallEnabled()) return;
|
|
788
|
+
const mode = hookModeFromEnv();
|
|
430
789
|
const runtime = hookRuntimeFromEnv();
|
|
790
|
+
const gateMode = parseRecallGateMode();
|
|
791
|
+
const gateUrl = process.env["OLLAMA_URL"] ?? "http://127.0.0.1:11434";
|
|
792
|
+
const gateTopN = Math.max(1, Number.parseInt(process.env["NLM_HOOK_RECALL_GATE_TOPN"] ?? "1", 10) || 1);
|
|
431
793
|
const out = await runHook(
|
|
432
|
-
{ prompt, conversationId },
|
|
433
|
-
{
|
|
794
|
+
{ prompt, conversationId, ...transcriptPath ? { transcriptPath } : {} },
|
|
795
|
+
{
|
|
796
|
+
mode,
|
|
797
|
+
recall: (q) => recallOverHttp(q, runtime, conversationId === "unknown" ? void 0 : conversationId),
|
|
798
|
+
...gateMode ? { recallGate: { mode: gateMode, judge: makeOllamaGate(gateUrl), maxCandidates: gateTopN } } : {}
|
|
799
|
+
}
|
|
434
800
|
);
|
|
435
801
|
if (out) process.stdout.write(out);
|
|
436
802
|
} catch {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nlm-memory",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.21.0",
|
|
4
4
|
"description": "Local-first non-linear memory operating system for AI operators.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -29,6 +29,8 @@
|
|
|
29
29
|
"files": [
|
|
30
30
|
"dist",
|
|
31
31
|
"migrations",
|
|
32
|
+
"fixtures",
|
|
33
|
+
"templates",
|
|
32
34
|
"plugin",
|
|
33
35
|
"plugin-hermes-agent",
|
|
34
36
|
"nlm",
|
|
@@ -52,16 +54,24 @@
|
|
|
52
54
|
"test:watch": "vitest",
|
|
53
55
|
"test:unit": "vitest run tests/unit",
|
|
54
56
|
"test:integration": "vitest run tests/integration",
|
|
57
|
+
"test:pg": "vitest run --no-file-parallelism .pg.test.ts",
|
|
55
58
|
"typecheck": "tsc -p tsconfig.json --noEmit && tsc -p tsconfig.test.json",
|
|
56
59
|
"bench:longmemeval": "tsx scripts/longmemeval/run-harness.ts",
|
|
57
60
|
"bench:classifier": "tsx scripts/longmemeval/run-harness.ts --classifier",
|
|
58
61
|
"bench:compare": "tsx scripts/longmemeval/compare-classifiers.ts",
|
|
59
62
|
"eval:signals": "tsx scripts/eval/signals-eval.ts",
|
|
60
|
-
"eval:classifier": "tsx scripts/eval/classifier-eval.ts"
|
|
63
|
+
"eval:classifier": "tsx scripts/eval/classifier-eval.ts",
|
|
64
|
+
"eval:fact-recall": "tsx scripts/eval/fact-recall-eval.ts",
|
|
65
|
+
"eval:code-exemplar": "tsx scripts/eval/code-exemplar-eval.ts",
|
|
66
|
+
"eval:reranker": "tsx scripts/eval/reranker-ablation.ts",
|
|
67
|
+
"eval:floor": "tsx scripts/eval/floor-calibration.ts",
|
|
68
|
+
"eval:matcher": "tsx scripts/eval/tune-matcher.ts",
|
|
69
|
+
"eval:naming": "tsx scripts/eval/tune-naming.ts"
|
|
61
70
|
},
|
|
62
71
|
"dependencies": {
|
|
63
72
|
"@clack/prompts": "^1.4.0",
|
|
64
73
|
"@hono/node-server": "^2.0.3",
|
|
74
|
+
"@huggingface/transformers": "4.2.0",
|
|
65
75
|
"@modelcontextprotocol/sdk": "^1.29.0",
|
|
66
76
|
"@toon-format/toon": "^2.3.0",
|
|
67
77
|
"better-sqlite3": "^12.10.0",
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nlm-memory",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.21.0",
|
|
4
4
|
"description": "Local-first persistent memory for AI coding agents. Recalls relevant prior sessions on every prompt; learns which sessions actually get cited. Owner-edited timeline beats append-only memory layers.",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Edward Chalupa",
|
|
@@ -19,4 +19,4 @@
|
|
|
19
19
|
],
|
|
20
20
|
"mcpServers": "./.mcp.json",
|
|
21
21
|
"hooks": "./hooks/hooks.json"
|
|
22
|
-
}
|
|
22
|
+
}
|