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
|
@@ -21,7 +21,15 @@ function appendHookLog(entry) {
|
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
// src/core/hook/memo.ts
|
|
24
|
-
import {
|
|
24
|
+
import {
|
|
25
|
+
existsSync,
|
|
26
|
+
mkdirSync as mkdirSync2,
|
|
27
|
+
readdirSync,
|
|
28
|
+
readFileSync,
|
|
29
|
+
rmSync,
|
|
30
|
+
statSync,
|
|
31
|
+
writeFileSync
|
|
32
|
+
} from "node:fs";
|
|
25
33
|
import { homedir as homedir2 } from "node:os";
|
|
26
34
|
import { join as join2 } from "node:path";
|
|
27
35
|
function stateDir() {
|
|
@@ -53,6 +61,26 @@ function recordSurfaced(conversationId, ids) {
|
|
|
53
61
|
}
|
|
54
62
|
|
|
55
63
|
// src/core/hook/pointer-block.ts
|
|
64
|
+
function truncateSummary(s, max = 200) {
|
|
65
|
+
if (s.length <= max) return s;
|
|
66
|
+
const window = s.slice(0, max);
|
|
67
|
+
const last = window[window.length - 1];
|
|
68
|
+
if ((last === "." || last === "!" || last === "?") && window.length - 1 >= 60) {
|
|
69
|
+
return window;
|
|
70
|
+
}
|
|
71
|
+
for (let i = window.length - 2; i >= 60; i--) {
|
|
72
|
+
const c = window[i];
|
|
73
|
+
if ((c === "." || c === "!" || c === "?") && window[i + 1] === " ") {
|
|
74
|
+
return s.slice(0, i + 1);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
for (let i = window.length - 1; i >= 60; i--) {
|
|
78
|
+
if (window[i] === " ") {
|
|
79
|
+
return s.slice(0, i) + " ...";
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return window + " ...";
|
|
83
|
+
}
|
|
56
84
|
function formatPointerBlock(hits, facts = [], exemplars = []) {
|
|
57
85
|
if (hits.length === 0 && facts.length === 0 && exemplars.length === 0) return "";
|
|
58
86
|
const out = [];
|
|
@@ -61,7 +89,7 @@ function formatPointerBlock(hits, facts = [], exemplars = []) {
|
|
|
61
89
|
for (const h of hits) {
|
|
62
90
|
const datePart = h.startedAt.slice(0, 10);
|
|
63
91
|
if (h.summary) {
|
|
64
|
-
out.push(`- ${h.id} \xB7 ${h.label} (${datePart}) \u2014 ${h.summary
|
|
92
|
+
out.push(`- ${h.id} \xB7 ${h.label} (${datePart}) \u2014 ${truncateSummary(h.summary)}`);
|
|
65
93
|
} else {
|
|
66
94
|
out.push(`- ${h.id} \xB7 ${h.label} (${datePart})`);
|
|
67
95
|
}
|
|
@@ -80,7 +108,7 @@ function formatPointerBlock(hits, facts = [], exemplars = []) {
|
|
|
80
108
|
out.push("## Related code exemplars (nlm-memory)");
|
|
81
109
|
for (const e of exemplars) {
|
|
82
110
|
const langPart = e.lang ? `${e.lang} \xB7 ` : "";
|
|
83
|
-
out.push(`- [${e.outcome}] ${langPart}${e.repo} - ${e.taskContext
|
|
111
|
+
out.push(`- [${e.outcome}] ${langPart}${e.repo} - ${truncateSummary(e.taskContext)}`);
|
|
84
112
|
}
|
|
85
113
|
}
|
|
86
114
|
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).";
|
|
@@ -89,10 +117,16 @@ function formatPointerBlock(hits, facts = [], exemplars = []) {
|
|
|
89
117
|
}
|
|
90
118
|
|
|
91
119
|
// src/core/hook/select.ts
|
|
120
|
+
function medianScore(hits) {
|
|
121
|
+
if (hits.length === 0) return 0;
|
|
122
|
+
const sorted = hits.map((h) => h.matchScore).sort((a, b) => a - b);
|
|
123
|
+
return sorted[Math.floor(sorted.length / 2)] ?? 0;
|
|
124
|
+
}
|
|
92
125
|
function selectHits(params) {
|
|
93
|
-
const { hits, surfaced, scoreThreshold, perFireCap, perConversationCap } = params;
|
|
126
|
+
const { hits, surfaced, scoreThreshold, perFireCap, perConversationCap, relativeFloor = 0 } = params;
|
|
127
|
+
const relCut = relativeFloor > 0 ? relativeFloor * medianScore(hits) : 0;
|
|
94
128
|
const eligible = hits.filter(
|
|
95
|
-
(h) => h.matchScore >= scoreThreshold && !surfaced.has(h.id)
|
|
129
|
+
(h) => h.matchScore >= scoreThreshold && h.matchScore >= relCut && !surfaced.has(h.id)
|
|
96
130
|
);
|
|
97
131
|
const budget = Math.max(0, perConversationCap - surfaced.size);
|
|
98
132
|
const limit = Math.min(perFireCap, budget);
|
|
@@ -149,12 +183,248 @@ function hookAuthHeaders(extra = {}) {
|
|
|
149
183
|
return { ...extra, authorization: `Bearer ${token}` };
|
|
150
184
|
}
|
|
151
185
|
|
|
186
|
+
// src/hook/score-floor.ts
|
|
187
|
+
function parseScoreFloor(raw) {
|
|
188
|
+
if (raw === void 0) return 0;
|
|
189
|
+
const parsed = Number(raw);
|
|
190
|
+
if (!Number.isFinite(parsed) || parsed < 0) return 0;
|
|
191
|
+
return parsed;
|
|
192
|
+
}
|
|
193
|
+
function parseRelativeFloor(raw, fallback) {
|
|
194
|
+
if (raw === void 0) return fallback;
|
|
195
|
+
const parsed = Number(raw);
|
|
196
|
+
if (!Number.isFinite(parsed) || parsed < 0) return fallback;
|
|
197
|
+
return parsed;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// src/core/hook/query-extract.ts
|
|
201
|
+
var STOPWORDS = /* @__PURE__ */ new Set([
|
|
202
|
+
"a",
|
|
203
|
+
"an",
|
|
204
|
+
"the",
|
|
205
|
+
"is",
|
|
206
|
+
"are",
|
|
207
|
+
"was",
|
|
208
|
+
"were",
|
|
209
|
+
"be",
|
|
210
|
+
"been",
|
|
211
|
+
"being",
|
|
212
|
+
"have",
|
|
213
|
+
"has",
|
|
214
|
+
"had",
|
|
215
|
+
"do",
|
|
216
|
+
"does",
|
|
217
|
+
"did",
|
|
218
|
+
"will",
|
|
219
|
+
"would",
|
|
220
|
+
"could",
|
|
221
|
+
"should",
|
|
222
|
+
"may",
|
|
223
|
+
"might",
|
|
224
|
+
"shall",
|
|
225
|
+
"can",
|
|
226
|
+
"dare",
|
|
227
|
+
"ought",
|
|
228
|
+
"yes",
|
|
229
|
+
"no",
|
|
230
|
+
"not",
|
|
231
|
+
"please",
|
|
232
|
+
"thank",
|
|
233
|
+
"thanks",
|
|
234
|
+
"ok",
|
|
235
|
+
"okay",
|
|
236
|
+
"i",
|
|
237
|
+
"me",
|
|
238
|
+
"my",
|
|
239
|
+
"we",
|
|
240
|
+
"us",
|
|
241
|
+
"our",
|
|
242
|
+
"you",
|
|
243
|
+
"your",
|
|
244
|
+
"it",
|
|
245
|
+
"its",
|
|
246
|
+
"this",
|
|
247
|
+
"that",
|
|
248
|
+
"these",
|
|
249
|
+
"those",
|
|
250
|
+
"and",
|
|
251
|
+
"or",
|
|
252
|
+
"but",
|
|
253
|
+
"if",
|
|
254
|
+
"so",
|
|
255
|
+
"to",
|
|
256
|
+
"of",
|
|
257
|
+
"in",
|
|
258
|
+
"on",
|
|
259
|
+
"at",
|
|
260
|
+
"by",
|
|
261
|
+
"for",
|
|
262
|
+
"from",
|
|
263
|
+
"with",
|
|
264
|
+
"about",
|
|
265
|
+
"into",
|
|
266
|
+
"through",
|
|
267
|
+
"during",
|
|
268
|
+
"before",
|
|
269
|
+
"after",
|
|
270
|
+
"above",
|
|
271
|
+
"below",
|
|
272
|
+
"up",
|
|
273
|
+
"down",
|
|
274
|
+
"out",
|
|
275
|
+
"off",
|
|
276
|
+
"over",
|
|
277
|
+
"under",
|
|
278
|
+
"again",
|
|
279
|
+
"further",
|
|
280
|
+
"then",
|
|
281
|
+
"once",
|
|
282
|
+
"here",
|
|
283
|
+
"there",
|
|
284
|
+
"when",
|
|
285
|
+
"where",
|
|
286
|
+
"why",
|
|
287
|
+
"how",
|
|
288
|
+
"all",
|
|
289
|
+
"both",
|
|
290
|
+
"each",
|
|
291
|
+
"few",
|
|
292
|
+
"more",
|
|
293
|
+
"most",
|
|
294
|
+
"other",
|
|
295
|
+
"some",
|
|
296
|
+
"such",
|
|
297
|
+
"than",
|
|
298
|
+
"too",
|
|
299
|
+
"very",
|
|
300
|
+
"just",
|
|
301
|
+
"now",
|
|
302
|
+
"also",
|
|
303
|
+
"get",
|
|
304
|
+
"let",
|
|
305
|
+
"what",
|
|
306
|
+
"which",
|
|
307
|
+
"who",
|
|
308
|
+
"whom",
|
|
309
|
+
"whose",
|
|
310
|
+
"any",
|
|
311
|
+
"much",
|
|
312
|
+
"many",
|
|
313
|
+
"sounds",
|
|
314
|
+
"good",
|
|
315
|
+
"great",
|
|
316
|
+
"sure",
|
|
317
|
+
"right",
|
|
318
|
+
"well",
|
|
319
|
+
"done",
|
|
320
|
+
"nice",
|
|
321
|
+
"cool",
|
|
322
|
+
"perfect",
|
|
323
|
+
"exactly",
|
|
324
|
+
"proceed",
|
|
325
|
+
"continue",
|
|
326
|
+
"go",
|
|
327
|
+
"ahead",
|
|
328
|
+
"next",
|
|
329
|
+
"help"
|
|
330
|
+
]);
|
|
331
|
+
var MIN_CONTENT_WORDS = 2;
|
|
332
|
+
var MIN_WORD_LEN = 3;
|
|
333
|
+
var SYSTEM_MESSAGE_PREFIX = /^<(task-notification|command-name|command-message|command-args|local-command-stdout|local-command-caveat|output-file|system-reminder)\b/;
|
|
334
|
+
function extractRecallQuery(prompt) {
|
|
335
|
+
if (SYSTEM_MESSAGE_PREFIX.test(prompt.trim())) return null;
|
|
336
|
+
const tokens = prompt.trim().split(/\s+/).map((t) => t.replace(/^[^\w-]+|[^\w-]+$/g, "")).filter((t) => t.length >= MIN_WORD_LEN);
|
|
337
|
+
const contentWords = tokens.filter((t) => !STOPWORDS.has(t.toLowerCase()));
|
|
338
|
+
if (contentWords.length < MIN_CONTENT_WORDS) return null;
|
|
339
|
+
return contentWords.join(" ");
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
// src/hook/hook-helpers.ts
|
|
343
|
+
function readStdin() {
|
|
344
|
+
return new Promise((resolve2) => {
|
|
345
|
+
let data = "";
|
|
346
|
+
process.stdin.setEncoding("utf8");
|
|
347
|
+
process.stdin.on("data", (chunk) => data += chunk);
|
|
348
|
+
process.stdin.on("end", () => resolve2(data));
|
|
349
|
+
process.stdin.on("error", () => resolve2(data));
|
|
350
|
+
});
|
|
351
|
+
}
|
|
352
|
+
async function fetchWithTimeout(url, init, ms) {
|
|
353
|
+
const controller = new AbortController();
|
|
354
|
+
const timer = setTimeout(() => controller.abort(), ms);
|
|
355
|
+
try {
|
|
356
|
+
return await fetch(url, { ...init, signal: controller.signal });
|
|
357
|
+
} finally {
|
|
358
|
+
clearTimeout(timer);
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
function hookModeFromEnv() {
|
|
362
|
+
return process.env["NLM_HOOK_MODE"] === "live" ? "live" : "shadow";
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
// src/shared/net.ts
|
|
366
|
+
var DEFAULT_NLM_PORT = "3940";
|
|
367
|
+
|
|
368
|
+
// src/hook/recall-over-http.ts
|
|
369
|
+
var RECALL_LIMIT = 5;
|
|
370
|
+
function parseRecallTimeout(raw) {
|
|
371
|
+
if (raw === void 0) return 4e3;
|
|
372
|
+
const n = Number(raw);
|
|
373
|
+
return Number.isFinite(n) && n > 0 ? n : 4e3;
|
|
374
|
+
}
|
|
375
|
+
var RECALL_TIMEOUT_MS = parseRecallTimeout(process.env["NLM_HOOK_RECALL_TIMEOUT_MS"]);
|
|
376
|
+
async function recallOverHttp(prompt, runtime, conversationId, mode = "keyword") {
|
|
377
|
+
const query = extractRecallQuery(prompt);
|
|
378
|
+
if (query === null) return { hits: [], facts: [], exemplars: [] };
|
|
379
|
+
const portValue = process.env["NLM_PORT"] ?? DEFAULT_NLM_PORT;
|
|
380
|
+
const url = (
|
|
381
|
+
// 127.0.0.1, not localhost: each hook is a fresh process with no connection
|
|
382
|
+
// reuse, and Node resolves localhost to IPv6 ::1 first — a measured ~50-300ms
|
|
383
|
+
// per-fire connect penalty vs ~3ms on the explicit IPv4 loopback.
|
|
384
|
+
`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)}` : "")
|
|
385
|
+
);
|
|
386
|
+
try {
|
|
387
|
+
const extra = { "x-recall-source": "hook" };
|
|
388
|
+
if (runtime) extra["x-recall-runtime"] = runtime;
|
|
389
|
+
const res = await fetchWithTimeout(url, { headers: hookAuthHeaders(extra) }, RECALL_TIMEOUT_MS);
|
|
390
|
+
if (!res.ok) return { hits: [], facts: [], exemplars: [] };
|
|
391
|
+
let body;
|
|
392
|
+
try {
|
|
393
|
+
body = await res.json();
|
|
394
|
+
} catch {
|
|
395
|
+
return { hits: [], facts: [], exemplars: [] };
|
|
396
|
+
}
|
|
397
|
+
const hits = (body.results ?? []).map((r) => ({
|
|
398
|
+
id: r.id,
|
|
399
|
+
label: r.label,
|
|
400
|
+
startedAt: r.startedAt,
|
|
401
|
+
matchScore: r.matchScore,
|
|
402
|
+
...r.summary !== void 0 ? { summary: r.summary } : {}
|
|
403
|
+
}));
|
|
404
|
+
const facts = (body.relatedFacts ?? []).map((f) => ({
|
|
405
|
+
subject: f.subject,
|
|
406
|
+
predicate: f.predicate,
|
|
407
|
+
value: f.value,
|
|
408
|
+
corroborationCount: f.corroborationCount
|
|
409
|
+
}));
|
|
410
|
+
const exemplars = (body.relatedExemplars ?? []).map((e) => ({
|
|
411
|
+
outcome: e.outcome,
|
|
412
|
+
lang: e.lang,
|
|
413
|
+
repo: e.repo,
|
|
414
|
+
taskContext: e.taskContext
|
|
415
|
+
}));
|
|
416
|
+
return { hits, facts, exemplars };
|
|
417
|
+
} catch {
|
|
418
|
+
return { hits: [], facts: [], exemplars: [] };
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
|
|
152
422
|
// src/hook/session-start-hook.ts
|
|
153
|
-
var SCORE_THRESHOLD =
|
|
423
|
+
var SCORE_THRESHOLD = parseScoreFloor(process.env["NLM_RECALL_SCORE_FLOOR"]);
|
|
424
|
+
var RELATIVE_FLOOR = parseRelativeFloor(process.env["NLM_RECALL_REL_FLOOR"], 0.9);
|
|
154
425
|
var PER_FIRE_CAP = 3;
|
|
155
426
|
var PER_CONVERSATION_CAP = 10;
|
|
156
|
-
var
|
|
157
|
-
var RECALL_TIMEOUT_MS = 2e3;
|
|
427
|
+
var RECALL_TIMEOUT_MS2 = 2e3;
|
|
158
428
|
async function runHook(input, deps) {
|
|
159
429
|
let hits = [];
|
|
160
430
|
try {
|
|
@@ -167,6 +437,7 @@ async function runHook(input, deps) {
|
|
|
167
437
|
hits,
|
|
168
438
|
surfaced,
|
|
169
439
|
scoreThreshold: SCORE_THRESHOLD,
|
|
440
|
+
relativeFloor: RELATIVE_FLOOR,
|
|
170
441
|
perFireCap: PER_FIRE_CAP,
|
|
171
442
|
perConversationCap: PER_CONVERSATION_CAP
|
|
172
443
|
});
|
|
@@ -193,22 +464,17 @@ function composeSessionStartOutput(failureModeBlock, recallBlock) {
|
|
|
193
464
|
}
|
|
194
465
|
async function fetchFailureModeBlock(repo) {
|
|
195
466
|
if (!repo) return "";
|
|
196
|
-
const portValue = process.env["NLM_PORT"] ??
|
|
197
|
-
const url = `http://
|
|
198
|
-
const controller = new AbortController();
|
|
199
|
-
const timer = setTimeout(() => controller.abort(), RECALL_TIMEOUT_MS);
|
|
467
|
+
const portValue = process.env["NLM_PORT"] ?? DEFAULT_NLM_PORT;
|
|
468
|
+
const url = `http://127.0.0.1:${portValue}/api/signals/failure-modes?repo=${encodeURIComponent(repo)}`;
|
|
200
469
|
try {
|
|
201
|
-
const res = await
|
|
202
|
-
headers: hookAuthHeaders({ "x-recall-source": "session-start-hook" })
|
|
203
|
-
|
|
204
|
-
});
|
|
470
|
+
const res = await fetchWithTimeout(url, {
|
|
471
|
+
headers: hookAuthHeaders({ "x-recall-source": "session-start-hook" })
|
|
472
|
+
}, RECALL_TIMEOUT_MS2);
|
|
205
473
|
if (!res.ok) return "";
|
|
206
474
|
const body = await res.json();
|
|
207
475
|
return typeof body.block === "string" ? body.block : "";
|
|
208
476
|
} catch {
|
|
209
477
|
return "";
|
|
210
|
-
} finally {
|
|
211
|
-
clearTimeout(timer);
|
|
212
478
|
}
|
|
213
479
|
}
|
|
214
480
|
function buildQuery(workingDirectory, projectName) {
|
|
@@ -216,46 +482,6 @@ function buildQuery(workingDirectory, projectName) {
|
|
|
216
482
|
const parts = [dirTail, projectName].filter(Boolean);
|
|
217
483
|
return parts.join(" ").trim() || "session start";
|
|
218
484
|
}
|
|
219
|
-
function readStdin() {
|
|
220
|
-
return new Promise((resolve2) => {
|
|
221
|
-
let data = "";
|
|
222
|
-
process.stdin.setEncoding("utf8");
|
|
223
|
-
process.stdin.on("data", (chunk) => data += chunk);
|
|
224
|
-
process.stdin.on("end", () => resolve2(data));
|
|
225
|
-
process.stdin.on("error", () => resolve2(data));
|
|
226
|
-
});
|
|
227
|
-
}
|
|
228
|
-
async function recallOverHttp(query, conversationId) {
|
|
229
|
-
const portValue = process.env["NLM_PORT"] ?? "3940";
|
|
230
|
-
const url = `http://localhost:${portValue}/api/recall?q=${encodeURIComponent(query)}&mode=hybrid&limit=${RECALL_LIMIT}` + (conversationId ? `&conversation_id=${encodeURIComponent(conversationId)}` : "");
|
|
231
|
-
const controller = new AbortController();
|
|
232
|
-
const timer = setTimeout(() => controller.abort(), RECALL_TIMEOUT_MS);
|
|
233
|
-
try {
|
|
234
|
-
const res = await fetch(url, {
|
|
235
|
-
headers: hookAuthHeaders({
|
|
236
|
-
"x-recall-source": "session-start-hook",
|
|
237
|
-
"x-recall-runtime": "claude-code"
|
|
238
|
-
}),
|
|
239
|
-
signal: controller.signal
|
|
240
|
-
});
|
|
241
|
-
if (!res.ok) return [];
|
|
242
|
-
let body;
|
|
243
|
-
try {
|
|
244
|
-
body = await res.json();
|
|
245
|
-
} catch {
|
|
246
|
-
return [];
|
|
247
|
-
}
|
|
248
|
-
return (body.results ?? []).map((r) => ({
|
|
249
|
-
id: r.id,
|
|
250
|
-
label: r.label,
|
|
251
|
-
startedAt: r.startedAt,
|
|
252
|
-
matchScore: r.matchScore,
|
|
253
|
-
...r.summary !== void 0 ? { summary: r.summary } : {}
|
|
254
|
-
}));
|
|
255
|
-
} finally {
|
|
256
|
-
clearTimeout(timer);
|
|
257
|
-
}
|
|
258
|
-
}
|
|
259
485
|
async function main() {
|
|
260
486
|
try {
|
|
261
487
|
autoloadEnv();
|
|
@@ -265,9 +491,17 @@ async function main() {
|
|
|
265
491
|
const workingDirectory = typeof payload.cwd === "string" ? payload.cwd : typeof payload.working_directory === "string" ? payload.working_directory : "";
|
|
266
492
|
const projectName = typeof payload.project_name === "string" ? payload.project_name : "";
|
|
267
493
|
const query = buildQuery(workingDirectory, projectName);
|
|
268
|
-
const mode =
|
|
269
|
-
const out = await
|
|
270
|
-
|
|
494
|
+
const mode = hookModeFromEnv();
|
|
495
|
+
const [out, failureModes] = await Promise.all([
|
|
496
|
+
runHook(
|
|
497
|
+
{ conversationId, query },
|
|
498
|
+
{
|
|
499
|
+
mode,
|
|
500
|
+
recall: async (q, cid) => (await recallOverHttp(q, "claude-code", cid === "unknown" ? void 0 : cid, "hybrid")).hits
|
|
501
|
+
}
|
|
502
|
+
),
|
|
503
|
+
mode === "live" ? fetchFailureModeBlock(workingDirectory) : Promise.resolve("")
|
|
504
|
+
]);
|
|
271
505
|
const combined = composeSessionStartOutput(failureModes, out);
|
|
272
506
|
if (combined) process.stdout.write(combined);
|
|
273
507
|
} catch {
|
|
@@ -2,9 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
// src/hook/stop-hook.ts
|
|
4
4
|
import { pathToFileURL } from "node:url";
|
|
5
|
-
import { appendFileSync, mkdirSync as mkdirSync3 } from "node:fs";
|
|
6
|
-
import { homedir as homedir5 } from "node:os";
|
|
7
|
-
import { dirname as dirname2, join as join4 } from "node:path";
|
|
8
5
|
|
|
9
6
|
// src/core/hook/citation-detect.ts
|
|
10
7
|
var MIN_ID_LEN = 6;
|
|
@@ -96,7 +93,15 @@ function extractId(input) {
|
|
|
96
93
|
}
|
|
97
94
|
|
|
98
95
|
// src/core/hook/memo.ts
|
|
99
|
-
import {
|
|
96
|
+
import {
|
|
97
|
+
existsSync,
|
|
98
|
+
mkdirSync,
|
|
99
|
+
readdirSync,
|
|
100
|
+
readFileSync,
|
|
101
|
+
rmSync,
|
|
102
|
+
statSync,
|
|
103
|
+
writeFileSync
|
|
104
|
+
} from "node:fs";
|
|
100
105
|
import { homedir } from "node:os";
|
|
101
106
|
import { join } from "node:path";
|
|
102
107
|
function stateDir() {
|
|
@@ -163,10 +168,10 @@ function isEnabled() {
|
|
|
163
168
|
if (raw === void 0) return true;
|
|
164
169
|
return raw !== "0" && raw.toLowerCase() !== "false";
|
|
165
170
|
}
|
|
166
|
-
async function appendMisses(entries,
|
|
171
|
+
async function appendMisses(entries, logPath) {
|
|
167
172
|
if (!isEnabled()) return;
|
|
168
173
|
if (entries.length === 0) return;
|
|
169
|
-
const path =
|
|
174
|
+
const path = logPath ?? defaultLogPath();
|
|
170
175
|
try {
|
|
171
176
|
await mkdir(dirname(path), { recursive: true });
|
|
172
177
|
const ts = (/* @__PURE__ */ new Date()).toISOString();
|
|
@@ -178,7 +183,7 @@ async function appendMisses(entries, logPath2) {
|
|
|
178
183
|
}
|
|
179
184
|
|
|
180
185
|
// src/core/hook/transcript.ts
|
|
181
|
-
import { existsSync as existsSync3,
|
|
186
|
+
import { closeSync, existsSync as existsSync3, fstatSync, openSync, readSync } from "node:fs";
|
|
182
187
|
function parseTurn(parsed) {
|
|
183
188
|
if (parsed.type !== "assistant" || !parsed.message) return null;
|
|
184
189
|
const content = parsed.message.content;
|
|
@@ -198,10 +203,22 @@ function parseTurn(parsed) {
|
|
|
198
203
|
if (textParts.length === 0 && toolUses.length === 0) return null;
|
|
199
204
|
return { text: textParts.join("\n"), toolUses };
|
|
200
205
|
}
|
|
206
|
+
var MAX_TRANSCRIPT_BYTES = 256 * 1024;
|
|
201
207
|
function readLines(transcriptPath) {
|
|
202
208
|
if (!transcriptPath || !existsSync3(transcriptPath)) return null;
|
|
203
209
|
try {
|
|
204
|
-
|
|
210
|
+
const fd = openSync(transcriptPath, "r");
|
|
211
|
+
try {
|
|
212
|
+
const size = fstatSync(fd).size;
|
|
213
|
+
const start = size > MAX_TRANSCRIPT_BYTES ? size - MAX_TRANSCRIPT_BYTES : 0;
|
|
214
|
+
const len = size - start;
|
|
215
|
+
if (len <= 0) return [];
|
|
216
|
+
const buf = Buffer.alloc(len);
|
|
217
|
+
readSync(fd, buf, 0, len, start);
|
|
218
|
+
return buf.toString("utf8").split("\n");
|
|
219
|
+
} finally {
|
|
220
|
+
closeSync(fd);
|
|
221
|
+
}
|
|
205
222
|
} catch {
|
|
206
223
|
return null;
|
|
207
224
|
}
|
|
@@ -226,7 +243,7 @@ function readAllAssistantTurns(transcriptPath) {
|
|
|
226
243
|
}
|
|
227
244
|
|
|
228
245
|
// src/llm/env-autoload.ts
|
|
229
|
-
import { readFileSync as
|
|
246
|
+
import { readFileSync as readFileSync3, existsSync as existsSync4 } from "node:fs";
|
|
230
247
|
import { homedir as homedir4 } from "node:os";
|
|
231
248
|
import { resolve } from "node:path";
|
|
232
249
|
var DEFAULT_SEARCH_PATHS = [
|
|
@@ -246,7 +263,7 @@ function autoloadEnv(extraPaths = []) {
|
|
|
246
263
|
const path = expandHome(raw);
|
|
247
264
|
if (!existsSync4(path)) continue;
|
|
248
265
|
try {
|
|
249
|
-
const content =
|
|
266
|
+
const content = readFileSync3(path, "utf8");
|
|
250
267
|
for (const line of content.split("\n")) {
|
|
251
268
|
const trimmed = line.trim();
|
|
252
269
|
if (!trimmed || trimmed.startsWith("#") || !trimmed.includes("=")) continue;
|
|
@@ -275,6 +292,44 @@ function hookAuthHeaders(extra = {}) {
|
|
|
275
292
|
return { ...extra, authorization: `Bearer ${token}` };
|
|
276
293
|
}
|
|
277
294
|
|
|
295
|
+
// src/hook/hook-helpers.ts
|
|
296
|
+
import { appendFileSync, mkdirSync as mkdirSync3 } from "node:fs";
|
|
297
|
+
import { homedir as homedir5 } from "node:os";
|
|
298
|
+
import { dirname as dirname2, join as join4 } from "node:path";
|
|
299
|
+
function readStdin() {
|
|
300
|
+
return new Promise((resolve2) => {
|
|
301
|
+
let data = "";
|
|
302
|
+
process.stdin.setEncoding("utf8");
|
|
303
|
+
process.stdin.on("data", (chunk) => data += chunk);
|
|
304
|
+
process.stdin.on("end", () => resolve2(data));
|
|
305
|
+
process.stdin.on("error", () => resolve2(data));
|
|
306
|
+
});
|
|
307
|
+
}
|
|
308
|
+
async function fetchWithTimeout(url, init, ms) {
|
|
309
|
+
const controller = new AbortController();
|
|
310
|
+
const timer = setTimeout(() => controller.abort(), ms);
|
|
311
|
+
try {
|
|
312
|
+
return await fetch(url, { ...init, signal: controller.signal });
|
|
313
|
+
} finally {
|
|
314
|
+
clearTimeout(timer);
|
|
315
|
+
}
|
|
316
|
+
}
|
|
317
|
+
function hookModeFromEnv() {
|
|
318
|
+
return process.env["NLM_HOOK_MODE"] === "live" ? "live" : "shadow";
|
|
319
|
+
}
|
|
320
|
+
function appendHookEvent(data) {
|
|
321
|
+
try {
|
|
322
|
+
const path = process.env["NLM_HOOK_LOG"] ?? join4(homedir5(), ".nlm", "hook-log.jsonl");
|
|
323
|
+
mkdirSync3(dirname2(path), { recursive: true });
|
|
324
|
+
appendFileSync(path, `${JSON.stringify(data)}
|
|
325
|
+
`, "utf8");
|
|
326
|
+
} catch {
|
|
327
|
+
}
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
// src/shared/net.ts
|
|
331
|
+
var DEFAULT_NLM_PORT = "3940";
|
|
332
|
+
|
|
278
333
|
// src/hook/stop-hook.ts
|
|
279
334
|
var RESPONSE_PREVIEW_CHARS = 200;
|
|
280
335
|
var POST_TIMEOUT_MS = 1500;
|
|
@@ -335,12 +390,9 @@ async function runStopHook(input, deps) {
|
|
|
335
390
|
}
|
|
336
391
|
const lastText = turns[turns.length - 1]?.text ?? "";
|
|
337
392
|
const preview = lastText.slice(0, RESPONSE_PREVIEW_CHARS);
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
} catch {
|
|
342
|
-
}
|
|
343
|
-
}
|
|
393
|
+
await Promise.allSettled(
|
|
394
|
+
fresh.map((c) => deps.postCitation(input.conversationId, c.id, c.kind, preview))
|
|
395
|
+
);
|
|
344
396
|
if (fresh.length > 0) {
|
|
345
397
|
recordCited(input.conversationId, fresh.map((c) => c.id));
|
|
346
398
|
}
|
|
@@ -352,60 +404,31 @@ async function runStopHook(input, deps) {
|
|
|
352
404
|
skipped: false
|
|
353
405
|
};
|
|
354
406
|
}
|
|
355
|
-
function logPath() {
|
|
356
|
-
return process.env["NLM_HOOK_LOG"] ?? join4(homedir5(), ".nlm", "hook-log.jsonl");
|
|
357
|
-
}
|
|
358
407
|
function logStopResult(result) {
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
surfacedCount: result.surfacedCount,
|
|
369
|
-
citedIds: result.citations.map((c) => c.id),
|
|
370
|
-
citationKinds: result.citations.map((c) => c.kind),
|
|
371
|
-
skipped: result.skipped,
|
|
372
|
-
mode: process.env["NLM_HOOK_MODE"] === "live" ? "live" : "shadow"
|
|
373
|
-
})}
|
|
374
|
-
`,
|
|
375
|
-
"utf8"
|
|
376
|
-
);
|
|
377
|
-
} catch {
|
|
378
|
-
}
|
|
379
|
-
}
|
|
380
|
-
function readStdin() {
|
|
381
|
-
return new Promise((resolve2) => {
|
|
382
|
-
let data = "";
|
|
383
|
-
process.stdin.setEncoding("utf8");
|
|
384
|
-
process.stdin.on("data", (chunk) => data += chunk);
|
|
385
|
-
process.stdin.on("end", () => resolve2(data));
|
|
386
|
-
process.stdin.on("error", () => resolve2(data));
|
|
408
|
+
appendHookEvent({
|
|
409
|
+
ts: (/* @__PURE__ */ new Date()).toISOString(),
|
|
410
|
+
kind: "stop",
|
|
411
|
+
conversationId: result.conversationId,
|
|
412
|
+
surfacedCount: result.surfacedCount,
|
|
413
|
+
citedIds: result.citations.map((c) => c.id),
|
|
414
|
+
citationKinds: result.citations.map((c) => c.kind),
|
|
415
|
+
skipped: result.skipped,
|
|
416
|
+
mode: hookModeFromEnv()
|
|
387
417
|
});
|
|
388
418
|
}
|
|
389
419
|
async function postCitationOverHttp(conversationId, citedId, kind, responsePreview) {
|
|
390
|
-
const port = process.env["NLM_PORT"] ??
|
|
391
|
-
const url = `http://
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
response_preview: responsePreview
|
|
403
|
-
}),
|
|
404
|
-
signal: controller.signal
|
|
405
|
-
});
|
|
406
|
-
} finally {
|
|
407
|
-
clearTimeout(timer);
|
|
408
|
-
}
|
|
420
|
+
const port = process.env["NLM_PORT"] ?? DEFAULT_NLM_PORT;
|
|
421
|
+
const url = `http://127.0.0.1:${port}/api/recall/cite-event`;
|
|
422
|
+
await fetchWithTimeout(url, {
|
|
423
|
+
method: "POST",
|
|
424
|
+
headers: hookAuthHeaders({ "content-type": "application/json" }),
|
|
425
|
+
body: JSON.stringify({
|
|
426
|
+
...conversationId !== "unknown" ? { conversation_id: conversationId } : {},
|
|
427
|
+
cited_id: citedId,
|
|
428
|
+
kind,
|
|
429
|
+
response_preview: responsePreview
|
|
430
|
+
})
|
|
431
|
+
}, POST_TIMEOUT_MS);
|
|
409
432
|
}
|
|
410
433
|
async function main() {
|
|
411
434
|
try {
|
|
@@ -9,6 +9,7 @@ The daemon must be running for hooks to have effect. If it is unreachable,
|
|
|
9
9
|
every hook silently returns None so the agent loop is never blocked.
|
|
10
10
|
"""
|
|
11
11
|
|
|
12
|
+
import http.client
|
|
12
13
|
import json
|
|
13
14
|
import os
|
|
14
15
|
import urllib.error
|
|
@@ -33,7 +34,7 @@ def _post(path: str, payload: dict[str, Any]) -> dict[str, Any]:
|
|
|
33
34
|
try:
|
|
34
35
|
with urllib.request.urlopen(req, timeout=5) as resp:
|
|
35
36
|
return json.loads(resp.read())
|
|
36
|
-
except (urllib.error.URLError, OSError, json.JSONDecodeError):
|
|
37
|
+
except (urllib.error.URLError, OSError, json.JSONDecodeError, http.client.HTTPException):
|
|
37
38
|
return {}
|
|
38
39
|
|
|
39
40
|
|