nlm-memory 0.19.0 → 0.20.1
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 +17 -10
- package/dist/cli/digest.js +21 -0
- 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 +766 -55
- package/dist/cli/nlm.js.map +1 -1
- 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.js +6 -0
- package/dist/core/adapters/claude-code.js.map +1 -1
- package/dist/core/classifier/hierarchical-classify.d.ts +6 -2
- package/dist/core/classifier/hierarchical-classify.js +10 -6
- package/dist/core/classifier/hierarchical-classify.js.map +1 -1
- package/dist/core/classifier/prompt.js +3 -3
- package/dist/core/digest/compose.d.ts +10 -0
- package/dist/core/digest/compose.js +7 -1
- package/dist/core/digest/compose.js.map +1 -1
- 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 +1 -0
- package/dist/core/exemplars/capture-from-session.js +6 -2
- 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.js +2 -65
- package/dist/core/exemplars/extract-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/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/ingest-session.d.ts +5 -0
- package/dist/core/ingest/ingest-session.js +3 -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 +3 -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 +275 -0
- package/dist/core/ingest/reprocess.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/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/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 +21 -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 +13 -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 +80 -28
- package/dist/core/scheduler/scheduler.js.map +1 -1
- package/dist/core/signals/code-signal.d.ts +49 -0
- package/dist/core/signals/code-signal.js +53 -0
- package/dist/core/signals/code-signal.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 +7 -0
- 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 +9 -0
- package/dist/core/storage/pg-session-store.js +204 -76
- package/dist/core/storage/pg-session-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 +24 -0
- package/dist/core/storage/pg-workstream-store.js +96 -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 +11 -0
- 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 +76 -25
- package/dist/core/storage/sqlite-fact-store.js.map +1 -1
- package/dist/core/storage/sqlite-session-store.d.ts +20 -5
- package/dist/core/storage/sqlite-session-store.js +183 -68
- package/dist/core/storage/sqlite-session-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 +24 -0
- package/dist/core/storage/sqlite-workstream-store.js +98 -0
- package/dist/core/storage/sqlite-workstream-store.js.map +1 -0
- package/dist/core/util/with-timeout.d.ts +5 -0
- package/dist/core/util/with-timeout.js +20 -0
- package/dist/core/util/with-timeout.js.map +1 -0
- 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 +25 -0
- package/dist/core/workstream/bind.js +23 -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 +20 -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 +66 -0
- package/dist/hook/pre-compact-hook.js.map +1 -0
- package/dist/hook/prompt-recall-hook.d.ts +37 -0
- package/dist/hook/prompt-recall-hook.js +120 -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 +1 -1
- package/dist/hook/recall-over-http.js +11 -11
- 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 +24 -59
- package/dist/hook/session-start-hook.js.map +1 -1
- package/dist/hook/stop-hook.js +22 -59
- package/dist/hook/stop-hook.js.map +1 -1
- package/dist/hook/subagent-start-hook.d.ts +28 -0
- package/dist/hook/subagent-start-hook.js +81 -0
- package/dist/hook/subagent-start-hook.js.map +1 -0
- package/dist/http/app.js +28 -8
- 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 +47 -2
- package/dist/mcp/server.js +303 -18
- 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/session-store.d.ts +12 -0
- 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 +21 -0
- package/dist/ports/workstream-store.js +2 -0
- package/dist/ports/workstream-store.js.map +1 -0
- package/dist/shared/types.d.ts +23 -2
- 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/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/nlm/index.js +395 -37
- package/package.json +12 -2
- package/plugin/.codex-plugin/plugin.json +1 -1
- package/plugin/scripts/prompt-recall-hook.mjs +402 -41
- package/plugin/scripts/session-start-hook.mjs +286 -60
- package/plugin/scripts/stop-hook.mjs +85 -65
- 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
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Recent-conversation context for the recall hook.
|
|
3
|
+
*
|
|
4
|
+
* Thin prompts ("what do you recommend?", "do it") carry no topic, so recalling
|
|
5
|
+
* on the bare prompt surfaces off-topic sessions (measured: 82% off-topic). When
|
|
6
|
+
* the prompt is thin we prepend the last few conversation turns — that is where
|
|
7
|
+
* the topic actually lives — to the recall query.
|
|
8
|
+
*
|
|
9
|
+
* Hot-path safe: TAIL-reads at most `maxBytes` of the transcript (not the whole
|
|
10
|
+
* file, which can be MBs in a long session), and only thin prompts call it.
|
|
11
|
+
* Fail-open: any I/O or parse error yields "" so the hook never breaks and the
|
|
12
|
+
* caller falls back to bare-prompt recall (today's behavior).
|
|
13
|
+
*
|
|
14
|
+
* Runtime-specific: only runtimes that expose a transcript path can use this
|
|
15
|
+
* (Claude Code does). Others pass no path and get "" → bare-prompt fallback.
|
|
16
|
+
*/
|
|
17
|
+
import { closeSync, existsSync, fstatSync, openSync, readSync } from "node:fs";
|
|
18
|
+
const DEFAULT_MAX_TURNS = 3;
|
|
19
|
+
const DEFAULT_MAX_BYTES = 64 * 1024;
|
|
20
|
+
const DEFAULT_PER_TURN_CHARS = 400;
|
|
21
|
+
// Conversational / function words that carry no recall topic. A prompt with few
|
|
22
|
+
// NON-stopword words is "thin" — that is the off-topic failure band ("what do you
|
|
23
|
+
// recommend", "do it now"), not a raw token count (which counts do/you/the).
|
|
24
|
+
const STOPWORDS = new Set([
|
|
25
|
+
"the", "a", "an", "and", "or", "but", "is", "are", "was", "were", "be", "been", "being",
|
|
26
|
+
"do", "does", "did", "doing", "you", "your", "i", "we", "our", "it", "its", "this", "that",
|
|
27
|
+
"these", "those", "what", "which", "how", "why", "when", "where", "who", "can", "could",
|
|
28
|
+
"would", "should", "will", "shall", "may", "might", "must", "please", "to", "of", "in", "on",
|
|
29
|
+
"for", "with", "so", "now", "then", "here", "there", "just", "also", "as", "at", "by", "if",
|
|
30
|
+
"my", "me", "us", "ok", "okay", "yes", "no", "not", "up", "out", "go", "get", "got", "make",
|
|
31
|
+
"made", "give", "tell", "want", "need", "think", "know", "let", "lets", "about", "from", "into",
|
|
32
|
+
]);
|
|
33
|
+
/** Count topical (non-stopword) words — the thinness signal for context-recall. */
|
|
34
|
+
export function topicalWordCount(s) {
|
|
35
|
+
const tokens = s.toLowerCase().match(/[a-z0-9]{2,}/g) ?? [];
|
|
36
|
+
return tokens.filter((t) => !STOPWORDS.has(t)).length;
|
|
37
|
+
}
|
|
38
|
+
function textOf(message) {
|
|
39
|
+
if (typeof message !== "object" || message === null)
|
|
40
|
+
return "";
|
|
41
|
+
const content = message.content;
|
|
42
|
+
if (typeof content === "string")
|
|
43
|
+
return content;
|
|
44
|
+
if (Array.isArray(content)) {
|
|
45
|
+
return content
|
|
46
|
+
.filter((b) => typeof b === "object" && b !== null && b.type === "text")
|
|
47
|
+
.map((b) => b.text)
|
|
48
|
+
.join(" ");
|
|
49
|
+
}
|
|
50
|
+
return "";
|
|
51
|
+
}
|
|
52
|
+
/** Read at most `maxBytes` from the end of the file. */
|
|
53
|
+
function tailRead(path, maxBytes) {
|
|
54
|
+
const fd = openSync(path, "r");
|
|
55
|
+
try {
|
|
56
|
+
const size = fstatSync(fd).size;
|
|
57
|
+
const start = size > maxBytes ? size - maxBytes : 0;
|
|
58
|
+
const len = size - start;
|
|
59
|
+
if (len <= 0)
|
|
60
|
+
return "";
|
|
61
|
+
const buf = Buffer.alloc(len);
|
|
62
|
+
readSync(fd, buf, 0, len, start);
|
|
63
|
+
return buf.toString("utf8");
|
|
64
|
+
}
|
|
65
|
+
finally {
|
|
66
|
+
closeSync(fd);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
export function recentConversationContext(transcriptPath, opts = {}) {
|
|
70
|
+
try {
|
|
71
|
+
if (!transcriptPath || !existsSync(transcriptPath))
|
|
72
|
+
return "";
|
|
73
|
+
const maxTurns = opts.maxTurns ?? DEFAULT_MAX_TURNS;
|
|
74
|
+
const perTurnChars = opts.perTurnChars ?? DEFAULT_PER_TURN_CHARS;
|
|
75
|
+
const lines = tailRead(transcriptPath, opts.maxBytes ?? DEFAULT_MAX_BYTES).split("\n");
|
|
76
|
+
const turns = [];
|
|
77
|
+
// Walk from the end; a tail read may truncate the first line mid-JSON, which
|
|
78
|
+
// JSON.parse rejects and we skip — that is the intended "drop partial leading
|
|
79
|
+
// line" behavior.
|
|
80
|
+
for (let i = lines.length - 1; i >= 0 && turns.length < maxTurns; i--) {
|
|
81
|
+
const line = lines[i].trim();
|
|
82
|
+
if (!line)
|
|
83
|
+
continue;
|
|
84
|
+
let evt;
|
|
85
|
+
try {
|
|
86
|
+
evt = JSON.parse(line);
|
|
87
|
+
}
|
|
88
|
+
catch {
|
|
89
|
+
continue;
|
|
90
|
+
}
|
|
91
|
+
if (evt.type !== "user" && evt.type !== "assistant")
|
|
92
|
+
continue;
|
|
93
|
+
const text = textOf(evt.message).trim();
|
|
94
|
+
if (!text)
|
|
95
|
+
continue;
|
|
96
|
+
turns.unshift(text.slice(0, perTurnChars));
|
|
97
|
+
}
|
|
98
|
+
return turns.join(" ").trim();
|
|
99
|
+
}
|
|
100
|
+
catch {
|
|
101
|
+
return "";
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
//# sourceMappingURL=recent-context.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"recent-context.js","sourceRoot":"","sources":["../../src/hook/recent-context.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,EAAE,SAAS,EAAE,UAAU,EAAE,SAAS,EAAE,QAAQ,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAE/E,MAAM,iBAAiB,GAAG,CAAC,CAAC;AAC5B,MAAM,iBAAiB,GAAG,EAAE,GAAG,IAAI,CAAC;AACpC,MAAM,sBAAsB,GAAG,GAAG,CAAC;AAQnC,gFAAgF;AAChF,kFAAkF;AAClF,6EAA6E;AAC7E,MAAM,SAAS,GAAG,IAAI,GAAG,CAAC;IACxB,KAAK,EAAE,GAAG,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO;IACvF,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,GAAG,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM;IAC1F,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,EAAE,OAAO;IACvF,OAAO,EAAE,QAAQ,EAAE,MAAM,EAAE,OAAO,EAAE,KAAK,EAAE,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI;IAC5F,KAAK,EAAE,MAAM,EAAE,IAAI,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI;IAC3F,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,MAAM;IAC3F,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,MAAM;CAChG,CAAC,CAAC;AAEH,mFAAmF;AACnF,MAAM,UAAU,gBAAgB,CAAC,CAAS;IACxC,MAAM,MAAM,GAAG,CAAC,CAAC,WAAW,EAAE,CAAC,KAAK,CAAC,eAAe,CAAC,IAAI,EAAE,CAAC;IAC5D,OAAO,MAAM,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,SAAS,CAAC,GAAG,CAAC,CAAC,CAAC,CAAC,CAAC,MAAM,CAAC;AACxD,CAAC;AAED,SAAS,MAAM,CAAC,OAAgB;IAC9B,IAAI,OAAO,OAAO,KAAK,QAAQ,IAAI,OAAO,KAAK,IAAI;QAAE,OAAO,EAAE,CAAC;IAC/D,MAAM,OAAO,GAAI,OAAiC,CAAC,OAAO,CAAC;IAC3D,IAAI,OAAO,OAAO,KAAK,QAAQ;QAAE,OAAO,OAAO,CAAC;IAChD,IAAI,KAAK,CAAC,OAAO,CAAC,OAAO,CAAC,EAAE,CAAC;QAC3B,OAAO,OAAO;aACX,MAAM,CACL,CAAC,CAAC,EAAuC,EAAE,CACzC,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC,KAAK,IAAI,IAAK,CAAuB,CAAC,IAAI,KAAK,MAAM,CAClF;aACA,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC;aAClB,IAAI,CAAC,GAAG,CAAC,CAAC;IACf,CAAC;IACD,OAAO,EAAE,CAAC;AACZ,CAAC;AAED,wDAAwD;AACxD,SAAS,QAAQ,CAAC,IAAY,EAAE,QAAgB;IAC9C,MAAM,EAAE,GAAG,QAAQ,CAAC,IAAI,EAAE,GAAG,CAAC,CAAC;IAC/B,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,SAAS,CAAC,EAAE,CAAC,CAAC,IAAI,CAAC;QAChC,MAAM,KAAK,GAAG,IAAI,GAAG,QAAQ,CAAC,CAAC,CAAC,IAAI,GAAG,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAC;QACpD,MAAM,GAAG,GAAG,IAAI,GAAG,KAAK,CAAC;QACzB,IAAI,GAAG,IAAI,CAAC;YAAE,OAAO,EAAE,CAAC;QACxB,MAAM,GAAG,GAAG,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QAC9B,QAAQ,CAAC,EAAE,EAAE,GAAG,EAAE,CAAC,EAAE,GAAG,EAAE,KAAK,CAAC,CAAC;QACjC,OAAO,GAAG,CAAC,QAAQ,CAAC,MAAM,CAAC,CAAC;IAC9B,CAAC;YAAS,CAAC;QACT,SAAS,CAAC,EAAE,CAAC,CAAC;IAChB,CAAC;AACH,CAAC;AAED,MAAM,UAAU,yBAAyB,CAAC,cAAsB,EAAE,OAAa,EAAE;IAC/E,IAAI,CAAC;QACH,IAAI,CAAC,cAAc,IAAI,CAAC,UAAU,CAAC,cAAc,CAAC;YAAE,OAAO,EAAE,CAAC;QAC9D,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,IAAI,iBAAiB,CAAC;QACpD,MAAM,YAAY,GAAG,IAAI,CAAC,YAAY,IAAI,sBAAsB,CAAC;QACjE,MAAM,KAAK,GAAG,QAAQ,CAAC,cAAc,EAAE,IAAI,CAAC,QAAQ,IAAI,iBAAiB,CAAC,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;QAEvF,MAAM,KAAK,GAAa,EAAE,CAAC;QAC3B,6EAA6E;QAC7E,8EAA8E;QAC9E,kBAAkB;QAClB,KAAK,IAAI,CAAC,GAAG,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,CAAC,IAAI,CAAC,IAAI,KAAK,CAAC,MAAM,GAAG,QAAQ,EAAE,CAAC,EAAE,EAAE,CAAC;YACtE,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAE,CAAC,IAAI,EAAE,CAAC;YAC9B,IAAI,CAAC,IAAI;gBAAE,SAAS;YACpB,IAAI,GAA0C,CAAC;YAC/C,IAAI,CAAC;gBACH,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,IAAI,CAA0C,CAAC;YAClE,CAAC;YAAC,MAAM,CAAC;gBACP,SAAS;YACX,CAAC;YACD,IAAI,GAAG,CAAC,IAAI,KAAK,MAAM,IAAI,GAAG,CAAC,IAAI,KAAK,WAAW;gBAAE,SAAS;YAC9D,MAAM,IAAI,GAAG,MAAM,CAAC,GAAG,CAAC,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC;YACxC,IAAI,CAAC,IAAI;gBAAE,SAAS;YACpB,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,YAAY,CAAC,CAAC,CAAC;QAC7C,CAAC;QACD,OAAO,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,IAAI,EAAE,CAAC;IAChC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parses the NLM_RECALL_SCORE_FLOOR env knob into a safe BM25 score cutoff.
|
|
3
|
+
*
|
|
4
|
+
* A bad value (NaN, non-finite, negative) must never silently flip recall into
|
|
5
|
+
* admit-all or deny-all. The unguarded `Number(env ?? "0")` returns NaN for a
|
|
6
|
+
* non-numeric value, and `matchScore >= NaN` is always false in select.ts —
|
|
7
|
+
* that would silently deny-all. This guard collapses every invalid input back
|
|
8
|
+
* to the safe default of 0 (no cutoff, FTS5 MATCH still gates relevance).
|
|
9
|
+
*
|
|
10
|
+
* Default stays 0: the calibrated keyword-mode floor (2.0, per the score-floor
|
|
11
|
+
* diagnostic on raw BM25 scores) is NOT a safe global default — the
|
|
12
|
+
* session-start hook recalls in hybrid mode whose matchScore is normalized to
|
|
13
|
+
* 0..1, so a 2.0 floor would deny-all there. The calibrated value is pending a
|
|
14
|
+
* per-mode application and ships as an opt-in env override only.
|
|
15
|
+
*/
|
|
16
|
+
export declare function parseScoreFloor(raw: string | undefined): number;
|
|
17
|
+
/**
|
|
18
|
+
* Parses NLM_RECALL_REL_FLOOR into a fire-median-relative cutoff: drop hits
|
|
19
|
+
* scoring below this fraction of the fire's median score. Unlike the absolute
|
|
20
|
+
* floor above, this is scale-invariant (a ratio), so the SAME value works on
|
|
21
|
+
* raw BM25 (keyword) or normalized (hybrid) scores and ports across installs.
|
|
22
|
+
* Invalid input collapses to the supplied default; 0 disables. Calibrated to
|
|
23
|
+
* 0.9 on the keyword/per-message path (scripts/eval/floor-calibration.ts, #284):
|
|
24
|
+
* keeps ~97% of cited recalls, trims weak tail noise. Set to 0.8 for zero
|
|
25
|
+
* measured gold loss, or 0 to disable.
|
|
26
|
+
*/
|
|
27
|
+
export declare function parseRelativeFloor(raw: string | undefined, fallback: number): number;
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Parses the NLM_RECALL_SCORE_FLOOR env knob into a safe BM25 score cutoff.
|
|
3
|
+
*
|
|
4
|
+
* A bad value (NaN, non-finite, negative) must never silently flip recall into
|
|
5
|
+
* admit-all or deny-all. The unguarded `Number(env ?? "0")` returns NaN for a
|
|
6
|
+
* non-numeric value, and `matchScore >= NaN` is always false in select.ts —
|
|
7
|
+
* that would silently deny-all. This guard collapses every invalid input back
|
|
8
|
+
* to the safe default of 0 (no cutoff, FTS5 MATCH still gates relevance).
|
|
9
|
+
*
|
|
10
|
+
* Default stays 0: the calibrated keyword-mode floor (2.0, per the score-floor
|
|
11
|
+
* diagnostic on raw BM25 scores) is NOT a safe global default — the
|
|
12
|
+
* session-start hook recalls in hybrid mode whose matchScore is normalized to
|
|
13
|
+
* 0..1, so a 2.0 floor would deny-all there. The calibrated value is pending a
|
|
14
|
+
* per-mode application and ships as an opt-in env override only.
|
|
15
|
+
*/
|
|
16
|
+
export function parseScoreFloor(raw) {
|
|
17
|
+
if (raw === undefined)
|
|
18
|
+
return 0;
|
|
19
|
+
const parsed = Number(raw);
|
|
20
|
+
if (!Number.isFinite(parsed) || parsed < 0)
|
|
21
|
+
return 0;
|
|
22
|
+
return parsed;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Parses NLM_RECALL_REL_FLOOR into a fire-median-relative cutoff: drop hits
|
|
26
|
+
* scoring below this fraction of the fire's median score. Unlike the absolute
|
|
27
|
+
* floor above, this is scale-invariant (a ratio), so the SAME value works on
|
|
28
|
+
* raw BM25 (keyword) or normalized (hybrid) scores and ports across installs.
|
|
29
|
+
* Invalid input collapses to the supplied default; 0 disables. Calibrated to
|
|
30
|
+
* 0.9 on the keyword/per-message path (scripts/eval/floor-calibration.ts, #284):
|
|
31
|
+
* keeps ~97% of cited recalls, trims weak tail noise. Set to 0.8 for zero
|
|
32
|
+
* measured gold loss, or 0 to disable.
|
|
33
|
+
*/
|
|
34
|
+
export function parseRelativeFloor(raw, fallback) {
|
|
35
|
+
if (raw === undefined)
|
|
36
|
+
return fallback;
|
|
37
|
+
const parsed = Number(raw);
|
|
38
|
+
if (!Number.isFinite(parsed) || parsed < 0)
|
|
39
|
+
return fallback;
|
|
40
|
+
return parsed;
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=score-floor.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"score-floor.js","sourceRoot":"","sources":["../../src/hook/score-floor.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AACH,MAAM,UAAU,eAAe,CAAC,GAAuB;IACrD,IAAI,GAAG,KAAK,SAAS;QAAE,OAAO,CAAC,CAAC;IAChC,MAAM,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;IAC3B,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,MAAM,GAAG,CAAC;QAAE,OAAO,CAAC,CAAC;IACrD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED;;;;;;;;;GASG;AACH,MAAM,UAAU,kBAAkB,CAAC,GAAuB,EAAE,QAAgB;IAC1E,IAAI,GAAG,KAAK,SAAS;QAAE,OAAO,QAAQ,CAAC;IACvC,MAAM,MAAM,GAAG,MAAM,CAAC,GAAG,CAAC,CAAC;IAC3B,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC,IAAI,MAAM,GAAG,CAAC;QAAE,OAAO,QAAQ,CAAC;IAC5D,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
|
@@ -12,43 +12,22 @@
|
|
|
12
12
|
* with no output, so the hook can never block Claude Code shutdown.
|
|
13
13
|
*/
|
|
14
14
|
import { pathToFileURL } from "node:url";
|
|
15
|
-
import { appendFileSync, mkdirSync } from "node:fs";
|
|
16
|
-
import { homedir } from "node:os";
|
|
17
|
-
import { dirname, join } from "node:path";
|
|
18
15
|
import { clearSurfaced } from "../core/hook/memo.js";
|
|
19
16
|
import { clearCited } from "../core/hook/cite-memo.js";
|
|
17
|
+
import { readStdin, hookModeFromEnv, appendHookEvent } from "./hook-helpers.js";
|
|
20
18
|
export function runSessionEnd(conversationId) {
|
|
21
19
|
const surfacedCleared = clearSurfaced(conversationId);
|
|
22
20
|
const citedCleared = clearCited(conversationId);
|
|
23
21
|
return { conversationId, cleared: surfacedCleared || citedCleared };
|
|
24
22
|
}
|
|
25
|
-
function readStdin() {
|
|
26
|
-
return new Promise((resolve) => {
|
|
27
|
-
let data = "";
|
|
28
|
-
process.stdin.setEncoding("utf8");
|
|
29
|
-
process.stdin.on("data", (chunk) => (data += chunk));
|
|
30
|
-
process.stdin.on("end", () => resolve(data));
|
|
31
|
-
process.stdin.on("error", () => resolve(data));
|
|
32
|
-
});
|
|
33
|
-
}
|
|
34
|
-
function logPath() {
|
|
35
|
-
return process.env["NLM_HOOK_LOG"] ?? join(homedir(), ".nlm", "hook-log.jsonl");
|
|
36
|
-
}
|
|
37
23
|
function logSessionEnd(result) {
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
cleared: result.cleared,
|
|
46
|
-
mode: process.env["NLM_HOOK_MODE"] === "live" ? "live" : "shadow",
|
|
47
|
-
})}\n`, "utf8");
|
|
48
|
-
}
|
|
49
|
-
catch {
|
|
50
|
-
// Telemetry failure must never break the hook.
|
|
51
|
-
}
|
|
24
|
+
appendHookEvent({
|
|
25
|
+
ts: new Date().toISOString(),
|
|
26
|
+
kind: "session-end",
|
|
27
|
+
conversationId: result.conversationId,
|
|
28
|
+
cleared: result.cleared,
|
|
29
|
+
mode: hookModeFromEnv(),
|
|
30
|
+
});
|
|
52
31
|
}
|
|
53
32
|
async function main() {
|
|
54
33
|
try {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-end-hook.js","sourceRoot":"","sources":["../../src/hook/session-end-hook.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"session-end-hook.js","sourceRoot":"","sources":["../../src/hook/session-end-hook.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,aAAa,EAAE,MAAM,oBAAoB,CAAC;AACnD,OAAO,EAAE,UAAU,EAAE,MAAM,yBAAyB,CAAC;AACrD,OAAO,EAAE,SAAS,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AAOhF,MAAM,UAAU,aAAa,CAAC,cAAsB;IAClD,MAAM,eAAe,GAAG,aAAa,CAAC,cAAc,CAAC,CAAC;IACtD,MAAM,YAAY,GAAG,UAAU,CAAC,cAAc,CAAC,CAAC;IAChD,OAAO,EAAE,cAAc,EAAE,OAAO,EAAE,eAAe,IAAI,YAAY,EAAE,CAAC;AACtE,CAAC;AAED,SAAS,aAAa,CAAC,MAAwB;IAC7C,eAAe,CAAC;QACd,EAAE,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QAC5B,IAAI,EAAE,aAAa;QACnB,cAAc,EAAE,MAAM,CAAC,cAAc;QACrC,OAAO,EAAE,MAAM,CAAC,OAAO;QACvB,IAAI,EAAE,eAAe,EAAE;KACxB,CAAC,CAAC;AACL,CAAC;AAED,KAAK,UAAU,IAAI;IACjB,IAAI,CAAC;QACH,MAAM,GAAG,GAAG,MAAM,SAAS,EAAE,CAAC;QAC9B,MAAM,OAAO,GAAG,IAAI,CAAC,KAAK,CAAC,GAAG,CAA6B,CAAC;QAC5D,MAAM,cAAc,GAClB,OAAO,OAAO,CAAC,UAAU,KAAK,QAAQ,CAAC,CAAC,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,SAAS,CAAC;QAC1E,aAAa,CAAC,aAAa,CAAC,cAAc,CAAC,CAAC,CAAC;IAC/C,CAAC;IAAC,MAAM,CAAC;QACP,gDAAgD;IAClD,CAAC;AACH,CAAC;AAED,IAAI,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,IAAI,MAAM,CAAC,IAAI,CAAC,GAAG,KAAK,aAAa,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;IAC/E,KAAK,IAAI,EAAE,CAAC;AACd,CAAC"}
|
|
@@ -18,10 +18,20 @@ import { formatPointerBlock } from "../core/hook/pointer-block.js";
|
|
|
18
18
|
import { selectHits } from "../core/hook/select.js";
|
|
19
19
|
import { autoloadEnv } from "../llm/env-autoload.js";
|
|
20
20
|
import { hookAuthHeaders } from "./hook-auth.js";
|
|
21
|
-
|
|
21
|
+
import { parseScoreFloor, parseRelativeFloor } from "./score-floor.js";
|
|
22
|
+
import { recallOverHttp } from "./recall-over-http.js";
|
|
23
|
+
import { readStdin, hookModeFromEnv, fetchWithTimeout } from "./hook-helpers.js";
|
|
24
|
+
// This hook recalls in hybrid mode, whose matchScore is normalized to 0..1
|
|
25
|
+
// (mergeHybrid in recall-service.ts), so the default absolute floor is 0.
|
|
26
|
+
// NLM_RECALL_SCORE_FLOOR is shared with the keyword-mode prompt hook; the
|
|
27
|
+
// calibrated keyword floor (2.0, raw BM25) would deny-all here on the 0..1
|
|
28
|
+
// scale, so it is opt-in only. parseScoreFloor guards a bad env value.
|
|
29
|
+
const SCORE_THRESHOLD = parseScoreFloor(process.env["NLM_RECALL_SCORE_FLOOR"]);
|
|
30
|
+
// The relative floor IS scale-invariant (ratio to the fire median), so it
|
|
31
|
+
// applies cleanly to this hybrid path too — parity with the per-message hook.
|
|
32
|
+
const RELATIVE_FLOOR = parseRelativeFloor(process.env["NLM_RECALL_REL_FLOOR"], 0.9);
|
|
22
33
|
const PER_FIRE_CAP = 3;
|
|
23
34
|
const PER_CONVERSATION_CAP = 10;
|
|
24
|
-
const RECALL_LIMIT = 5;
|
|
25
35
|
const RECALL_TIMEOUT_MS = 2000;
|
|
26
36
|
/** Orchestration. Returns the text to emit on stdout ("" for nothing). */
|
|
27
37
|
export async function runHook(input, deps) {
|
|
@@ -37,6 +47,7 @@ export async function runHook(input, deps) {
|
|
|
37
47
|
hits,
|
|
38
48
|
surfaced,
|
|
39
49
|
scoreThreshold: SCORE_THRESHOLD,
|
|
50
|
+
relativeFloor: RELATIVE_FLOOR,
|
|
40
51
|
perFireCap: PER_FIRE_CAP,
|
|
41
52
|
perConversationCap: PER_CONVERSATION_CAP,
|
|
42
53
|
});
|
|
@@ -66,14 +77,11 @@ async function fetchFailureModeBlock(repo) {
|
|
|
66
77
|
if (!repo)
|
|
67
78
|
return "";
|
|
68
79
|
const portValue = process.env["NLM_PORT"] ?? "3940";
|
|
69
|
-
const url = `http://
|
|
70
|
-
const controller = new AbortController();
|
|
71
|
-
const timer = setTimeout(() => controller.abort(), RECALL_TIMEOUT_MS);
|
|
80
|
+
const url = `http://127.0.0.1:${portValue}/api/signals/failure-modes?repo=${encodeURIComponent(repo)}`;
|
|
72
81
|
try {
|
|
73
|
-
const res = await
|
|
82
|
+
const res = await fetchWithTimeout(url, {
|
|
74
83
|
headers: hookAuthHeaders({ "x-recall-source": "session-start-hook" }),
|
|
75
|
-
|
|
76
|
-
});
|
|
84
|
+
}, RECALL_TIMEOUT_MS);
|
|
77
85
|
if (!res.ok)
|
|
78
86
|
return "";
|
|
79
87
|
const body = (await res.json());
|
|
@@ -82,9 +90,6 @@ async function fetchFailureModeBlock(repo) {
|
|
|
82
90
|
catch {
|
|
83
91
|
return "";
|
|
84
92
|
}
|
|
85
|
-
finally {
|
|
86
|
-
clearTimeout(timer);
|
|
87
|
-
}
|
|
88
93
|
}
|
|
89
94
|
/** Derive a best-effort query from SessionStart payload fields. */
|
|
90
95
|
function buildQuery(workingDirectory, projectName) {
|
|
@@ -92,51 +97,6 @@ function buildQuery(workingDirectory, projectName) {
|
|
|
92
97
|
const parts = [dirTail, projectName].filter(Boolean);
|
|
93
98
|
return parts.join(" ").trim() || "session start";
|
|
94
99
|
}
|
|
95
|
-
function readStdin() {
|
|
96
|
-
return new Promise((resolve) => {
|
|
97
|
-
let data = "";
|
|
98
|
-
process.stdin.setEncoding("utf8");
|
|
99
|
-
process.stdin.on("data", (chunk) => (data += chunk));
|
|
100
|
-
process.stdin.on("end", () => resolve(data));
|
|
101
|
-
process.stdin.on("error", () => resolve(data));
|
|
102
|
-
});
|
|
103
|
-
}
|
|
104
|
-
async function recallOverHttp(query, conversationId) {
|
|
105
|
-
const portValue = process.env["NLM_PORT"] ?? "3940";
|
|
106
|
-
const url = `http://localhost:${portValue}/api/recall` +
|
|
107
|
-
`?q=${encodeURIComponent(query)}&mode=hybrid&limit=${RECALL_LIMIT}` +
|
|
108
|
-
(conversationId ? `&conversation_id=${encodeURIComponent(conversationId)}` : "");
|
|
109
|
-
const controller = new AbortController();
|
|
110
|
-
const timer = setTimeout(() => controller.abort(), RECALL_TIMEOUT_MS);
|
|
111
|
-
try {
|
|
112
|
-
const res = await fetch(url, {
|
|
113
|
-
headers: hookAuthHeaders({
|
|
114
|
-
"x-recall-source": "session-start-hook",
|
|
115
|
-
"x-recall-runtime": "claude-code",
|
|
116
|
-
}),
|
|
117
|
-
signal: controller.signal,
|
|
118
|
-
});
|
|
119
|
-
if (!res.ok)
|
|
120
|
-
return [];
|
|
121
|
-
let body;
|
|
122
|
-
try {
|
|
123
|
-
body = (await res.json());
|
|
124
|
-
}
|
|
125
|
-
catch {
|
|
126
|
-
return [];
|
|
127
|
-
}
|
|
128
|
-
return (body.results ?? []).map((r) => ({
|
|
129
|
-
id: r.id,
|
|
130
|
-
label: r.label,
|
|
131
|
-
startedAt: r.startedAt,
|
|
132
|
-
matchScore: r.matchScore,
|
|
133
|
-
...(r.summary !== undefined ? { summary: r.summary } : {}),
|
|
134
|
-
}));
|
|
135
|
-
}
|
|
136
|
-
finally {
|
|
137
|
-
clearTimeout(timer);
|
|
138
|
-
}
|
|
139
|
-
}
|
|
140
100
|
async function main() {
|
|
141
101
|
try {
|
|
142
102
|
autoloadEnv();
|
|
@@ -150,9 +110,14 @@ async function main() {
|
|
|
150
110
|
: "";
|
|
151
111
|
const projectName = typeof payload.project_name === "string" ? payload.project_name : "";
|
|
152
112
|
const query = buildQuery(workingDirectory, projectName);
|
|
153
|
-
const mode =
|
|
154
|
-
const out = await
|
|
155
|
-
|
|
113
|
+
const mode = hookModeFromEnv();
|
|
114
|
+
const [out, failureModes] = await Promise.all([
|
|
115
|
+
runHook({ conversationId, query }, {
|
|
116
|
+
mode,
|
|
117
|
+
recall: async (q, cid) => (await recallOverHttp(q, "claude-code", cid === "unknown" ? undefined : cid, "hybrid")).hits,
|
|
118
|
+
}),
|
|
119
|
+
mode === "live" ? fetchFailureModeBlock(workingDirectory) : Promise.resolve(""),
|
|
120
|
+
]);
|
|
156
121
|
const combined = composeSessionStartOutput(failureModes, out);
|
|
157
122
|
if (combined)
|
|
158
123
|
process.stdout.write(combined);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"session-start-hook.js","sourceRoot":"","sources":["../../src/hook/session-start-hook.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,aAAa,EAAE,MAAM,wBAAwB,CAAC;AACvD,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAClE,OAAO,EAAE,kBAAkB,EAAE,MAAM,6BAA6B,CAAC;AACjE,OAAO,EAAE,UAAU,EAAuB,MAAM,sBAAsB,CAAC;AACvE,OAAO,EAAE,WAAW,EAAE,MAAM,wBAAwB,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;
|
|
1
|
+
{"version":3,"file":"session-start-hook.js","sourceRoot":"","sources":["../../src/hook/session-start-hook.ts"],"names":[],"mappings":"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"}
|
package/dist/hook/stop-hook.js
CHANGED
|
@@ -12,9 +12,6 @@
|
|
|
12
12
|
* Stop hook can never block Claude Code's response.
|
|
13
13
|
*/
|
|
14
14
|
import { pathToFileURL } from "node:url";
|
|
15
|
-
import { appendFileSync, mkdirSync } from "node:fs";
|
|
16
|
-
import { homedir } from "node:os";
|
|
17
|
-
import { dirname, join } from "node:path";
|
|
18
15
|
import { detectCitations, } from "../core/hook/citation-detect.js";
|
|
19
16
|
import { detectMisses } from "../core/hook/miss-detect.js";
|
|
20
17
|
import { loadSurfaced } from "../core/hook/memo.js";
|
|
@@ -23,6 +20,7 @@ import { appendMisses } from "../core/recall/miss-log.js";
|
|
|
23
20
|
import { readAllAssistantTurns, } from "../core/hook/transcript.js";
|
|
24
21
|
import { autoloadEnv } from "../llm/env-autoload.js";
|
|
25
22
|
import { hookAuthHeaders } from "./hook-auth.js";
|
|
23
|
+
import { readStdin, fetchWithTimeout, hookModeFromEnv, appendHookEvent } from "./hook-helpers.js";
|
|
26
24
|
const RESPONSE_PREVIEW_CHARS = 200;
|
|
27
25
|
const POST_TIMEOUT_MS = 1500;
|
|
28
26
|
export async function runStopHook(input, deps) {
|
|
@@ -92,15 +90,7 @@ export async function runStopHook(input, deps) {
|
|
|
92
90
|
// ranges across earlier turns.
|
|
93
91
|
const lastText = turns[turns.length - 1]?.text ?? "";
|
|
94
92
|
const preview = lastText.slice(0, RESPONSE_PREVIEW_CHARS);
|
|
95
|
-
|
|
96
|
-
try {
|
|
97
|
-
await deps.postCitation(input.conversationId, c.id, c.kind, preview);
|
|
98
|
-
}
|
|
99
|
-
catch {
|
|
100
|
-
// Daemon down or HTTP error — local memo update below still records
|
|
101
|
-
// the citation so we don't repost on the next Stop fire.
|
|
102
|
-
}
|
|
103
|
-
}
|
|
93
|
+
await Promise.allSettled(fresh.map((c) => deps.postCitation(input.conversationId, c.id, c.kind, preview)));
|
|
104
94
|
if (fresh.length > 0) {
|
|
105
95
|
recordCited(input.conversationId, fresh.map((c) => c.id));
|
|
106
96
|
}
|
|
@@ -112,58 +102,31 @@ export async function runStopHook(input, deps) {
|
|
|
112
102
|
skipped: false,
|
|
113
103
|
};
|
|
114
104
|
}
|
|
115
|
-
function logPath() {
|
|
116
|
-
return process.env["NLM_HOOK_LOG"] ?? join(homedir(), ".nlm", "hook-log.jsonl");
|
|
117
|
-
}
|
|
118
105
|
function logStopResult(result) {
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
citationKinds: result.citations.map((c) => c.kind),
|
|
129
|
-
skipped: result.skipped,
|
|
130
|
-
mode: process.env["NLM_HOOK_MODE"] === "live" ? "live" : "shadow",
|
|
131
|
-
})}\n`, "utf8");
|
|
132
|
-
}
|
|
133
|
-
catch {
|
|
134
|
-
// Telemetry failure must never break the hook.
|
|
135
|
-
}
|
|
136
|
-
}
|
|
137
|
-
function readStdin() {
|
|
138
|
-
return new Promise((resolve) => {
|
|
139
|
-
let data = "";
|
|
140
|
-
process.stdin.setEncoding("utf8");
|
|
141
|
-
process.stdin.on("data", (chunk) => (data += chunk));
|
|
142
|
-
process.stdin.on("end", () => resolve(data));
|
|
143
|
-
process.stdin.on("error", () => resolve(data));
|
|
106
|
+
appendHookEvent({
|
|
107
|
+
ts: new Date().toISOString(),
|
|
108
|
+
kind: "stop",
|
|
109
|
+
conversationId: result.conversationId,
|
|
110
|
+
surfacedCount: result.surfacedCount,
|
|
111
|
+
citedIds: result.citations.map((c) => c.id),
|
|
112
|
+
citationKinds: result.citations.map((c) => c.kind),
|
|
113
|
+
skipped: result.skipped,
|
|
114
|
+
mode: hookModeFromEnv(),
|
|
144
115
|
});
|
|
145
116
|
}
|
|
146
117
|
async function postCitationOverHttp(conversationId, citedId, kind, responsePreview) {
|
|
147
118
|
const port = process.env["NLM_PORT"] ?? "3940";
|
|
148
|
-
const url = `http://
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
response_preview: responsePreview,
|
|
160
|
-
}),
|
|
161
|
-
signal: controller.signal,
|
|
162
|
-
});
|
|
163
|
-
}
|
|
164
|
-
finally {
|
|
165
|
-
clearTimeout(timer);
|
|
166
|
-
}
|
|
119
|
+
const url = `http://127.0.0.1:${port}/api/recall/cite-event`;
|
|
120
|
+
await fetchWithTimeout(url, {
|
|
121
|
+
method: "POST",
|
|
122
|
+
headers: hookAuthHeaders({ "content-type": "application/json" }),
|
|
123
|
+
body: JSON.stringify({
|
|
124
|
+
...(conversationId !== "unknown" ? { conversation_id: conversationId } : {}),
|
|
125
|
+
cited_id: citedId,
|
|
126
|
+
kind,
|
|
127
|
+
response_preview: responsePreview,
|
|
128
|
+
}),
|
|
129
|
+
}, POST_TIMEOUT_MS);
|
|
167
130
|
}
|
|
168
131
|
async function main() {
|
|
169
132
|
try {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"stop-hook.js","sourceRoot":"","sources":["../../src/hook/stop-hook.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,
|
|
1
|
+
{"version":3,"file":"stop-hook.js","sourceRoot":"","sources":["../../src/hook/stop-hook.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Claude Code SubagentStart hook entrypoint for NLM.
|
|
3
|
+
*
|
|
4
|
+
* Fires when Claude Code dispatches a subagent (Agent tool). Subagents have
|
|
5
|
+
* their own session IDs but are invisible to NLM's session corpus today.
|
|
6
|
+
* This hook captures the parent→subagent link so NLM can correlate subagent
|
|
7
|
+
* transcripts back to the dispatching conversation when SessionEnd fires.
|
|
8
|
+
*
|
|
9
|
+
* Capture-only. No recall injection — subagents inherit context from their
|
|
10
|
+
* dispatch prompt; additional recall would pollute their narrow task scope.
|
|
11
|
+
*
|
|
12
|
+
* Daemon endpoint: POST /api/hook/subagent-start
|
|
13
|
+
*
|
|
14
|
+
* Payload: { parent_conversation_id, subagent_session_id, subagent_description, ts }
|
|
15
|
+
*
|
|
16
|
+
* Fail-open by design: any error yields a clean exit with no output.
|
|
17
|
+
*/
|
|
18
|
+
export interface SubagentStartInput {
|
|
19
|
+
readonly parentConversationId: string;
|
|
20
|
+
readonly subagentSessionId: string;
|
|
21
|
+
readonly subagentDescription: string;
|
|
22
|
+
}
|
|
23
|
+
export interface SubagentStartResult {
|
|
24
|
+
readonly parentConversationId: string;
|
|
25
|
+
readonly subagentSessionId: string;
|
|
26
|
+
readonly posted: boolean;
|
|
27
|
+
}
|
|
28
|
+
export declare function runSubagentStart(input: SubagentStartInput, portValue?: string): Promise<SubagentStartResult>;
|