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,275 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* reprocess: retroactive re-classification of prior sessions under a
|
|
3
|
+
* stronger configured lane.
|
|
4
|
+
*
|
|
5
|
+
* Selection: sessions with a non-empty body whose classifier_model is NULL,
|
|
6
|
+
* differs from the current lane model, or (when --min-confidence is given)
|
|
7
|
+
* whose stored confidence falls below the threshold even for the same model.
|
|
8
|
+
*
|
|
9
|
+
* Per session: classifyAdaptive on the stored body, then a full insertSession
|
|
10
|
+
* upsert (refreshes chunks + facts). A below-floor classification updates the
|
|
11
|
+
* session row (label, summary, markers, provenance) but preserves prior facts
|
|
12
|
+
* and their embeddings unchanged. It is counted in belowFloorOverwrites.
|
|
13
|
+
* Workstream binding (workstream_id) is NOT touched by the upsert.
|
|
14
|
+
*
|
|
15
|
+
* Resumable via a JSON state file (default ~/.nlm/reprocess.state). A lane
|
|
16
|
+
* change (different provider or model) invalidates the done-set.
|
|
17
|
+
*
|
|
18
|
+
* SQLite only. Pg variant deferred; file task against nlm#TODO in report.
|
|
19
|
+
*/
|
|
20
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
21
|
+
import { dirname, join } from "node:path";
|
|
22
|
+
import { homedir } from "node:os";
|
|
23
|
+
import { classifyAdaptive } from "../classifier/hierarchical-classify.js";
|
|
24
|
+
import { extractFacts } from "../facts/extract-facts.js";
|
|
25
|
+
const DEFAULT_STATE_PATH = join(homedir(), ".nlm", "reprocess.state");
|
|
26
|
+
const SAVE_EVERY = 25;
|
|
27
|
+
const CONFIDENCE_FLOOR = 0.4;
|
|
28
|
+
function confidenceBand(conf) {
|
|
29
|
+
if (conf === null)
|
|
30
|
+
return "null";
|
|
31
|
+
if (conf < 0.4)
|
|
32
|
+
return "<0.4";
|
|
33
|
+
if (conf < 0.6)
|
|
34
|
+
return "0.4-0.6";
|
|
35
|
+
if (conf < 0.8)
|
|
36
|
+
return "0.6-0.8";
|
|
37
|
+
return ">=0.8";
|
|
38
|
+
}
|
|
39
|
+
function loadState(path, lane) {
|
|
40
|
+
if (!existsSync(path))
|
|
41
|
+
return new Set();
|
|
42
|
+
try {
|
|
43
|
+
const raw = JSON.parse(readFileSync(path, "utf8"));
|
|
44
|
+
if (raw.lane?.provider === lane.provider && raw.lane?.model === lane.model) {
|
|
45
|
+
return new Set(raw.done ?? []);
|
|
46
|
+
}
|
|
47
|
+
return new Set();
|
|
48
|
+
}
|
|
49
|
+
catch {
|
|
50
|
+
return new Set();
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
function saveState(path, done, lane) {
|
|
54
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
55
|
+
writeFileSync(path, JSON.stringify({ done: [...done].sort(), lane }));
|
|
56
|
+
}
|
|
57
|
+
export function selectReprocessCandidates(db, model, minConfidence, opts) {
|
|
58
|
+
const params = [];
|
|
59
|
+
let classifierClause;
|
|
60
|
+
if (opts?.onlyNull) {
|
|
61
|
+
classifierClause = "classifier_model IS NULL";
|
|
62
|
+
}
|
|
63
|
+
else if (minConfidence !== undefined) {
|
|
64
|
+
classifierClause = "(classifier_model IS NULL OR classifier_model != ? OR classifier_confidence < ?)";
|
|
65
|
+
params.push(model, minConfidence);
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
classifierClause = "(classifier_model IS NULL OR classifier_model != ?)";
|
|
69
|
+
params.push(model);
|
|
70
|
+
}
|
|
71
|
+
const excludeModels = opts?.excludeModels ?? [];
|
|
72
|
+
let excludeClause = "";
|
|
73
|
+
if (excludeModels.length > 0) {
|
|
74
|
+
const placeholders = excludeModels.map(() => "?").join(", ");
|
|
75
|
+
excludeClause = ` AND (classifier_model IS NULL OR classifier_model NOT IN (${placeholders}))`;
|
|
76
|
+
params.push(...excludeModels);
|
|
77
|
+
}
|
|
78
|
+
const sql = "SELECT id, runtime, runtime_session_id, started_at, ended_at, duration_min, " +
|
|
79
|
+
"label, summary, status, transcript_kind, transcript_path, " +
|
|
80
|
+
"transcript_offset, transcript_length, " +
|
|
81
|
+
"classifier_provider, classifier_model, classifier_confidence " +
|
|
82
|
+
"FROM sessions " +
|
|
83
|
+
"WHERE body IS NOT NULL AND length(body) > 0 " +
|
|
84
|
+
"AND " +
|
|
85
|
+
classifierClause +
|
|
86
|
+
excludeClause +
|
|
87
|
+
" " +
|
|
88
|
+
"ORDER BY started_at DESC";
|
|
89
|
+
return db.prepare(sql).all(...params);
|
|
90
|
+
}
|
|
91
|
+
export async function reprocess(deps, opts = {}) {
|
|
92
|
+
const { db, store, factStore, embedder, classifier, classifierDescriptor } = deps;
|
|
93
|
+
const log = deps.log ?? ((m) => console.error(m));
|
|
94
|
+
const statePath = opts.statePath ?? DEFAULT_STATE_PATH;
|
|
95
|
+
const lane = {
|
|
96
|
+
provider: classifierDescriptor.provider,
|
|
97
|
+
model: classifierDescriptor.model,
|
|
98
|
+
};
|
|
99
|
+
const allCandidates = selectReprocessCandidates(db, classifierDescriptor.model, opts.minConfidence, {
|
|
100
|
+
...(opts.onlyNull ? { onlyNull: true } : {}),
|
|
101
|
+
...(opts.excludeModels ? { excludeModels: opts.excludeModels } : {}),
|
|
102
|
+
});
|
|
103
|
+
if (opts.dryRun) {
|
|
104
|
+
const groupMap = new Map();
|
|
105
|
+
for (const row of allCandidates) {
|
|
106
|
+
const key = `${row.classifier_provider ?? ""}\u0000${row.classifier_model ?? ""}\u0000${confidenceBand(row.classifier_confidence)}`;
|
|
107
|
+
const existing = groupMap.get(key);
|
|
108
|
+
if (existing) {
|
|
109
|
+
groupMap.set(key, { ...existing, count: existing.count + 1 });
|
|
110
|
+
}
|
|
111
|
+
else {
|
|
112
|
+
groupMap.set(key, {
|
|
113
|
+
classifierProvider: row.classifier_provider,
|
|
114
|
+
classifierModel: row.classifier_model,
|
|
115
|
+
confidenceBand: confidenceBand(row.classifier_confidence),
|
|
116
|
+
count: 1,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
const cohort = [...groupMap.values()].sort((a, b) => b.count - a.count);
|
|
121
|
+
let stateNote;
|
|
122
|
+
if (existsSync(statePath)) {
|
|
123
|
+
const n = loadState(statePath, lane).size;
|
|
124
|
+
stateNote = `state file present, ${n} sessions already done, real run will skip them`;
|
|
125
|
+
}
|
|
126
|
+
return {
|
|
127
|
+
totalEligible: allCandidates.length,
|
|
128
|
+
processed: 0,
|
|
129
|
+
succeeded: 0,
|
|
130
|
+
failed: 0,
|
|
131
|
+
skippedAlreadyDone: 0,
|
|
132
|
+
limitSkipped: 0,
|
|
133
|
+
belowFloorOverwrites: 0,
|
|
134
|
+
meanConfidenceOld: null,
|
|
135
|
+
meanConfidenceNew: null,
|
|
136
|
+
cohort,
|
|
137
|
+
...(stateNote !== undefined ? { stateNote } : {}),
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
if (deps.embeddingConfig && deps.embedderDescriptor) {
|
|
141
|
+
const stored = deps.embeddingConfig.getLane("prose");
|
|
142
|
+
if (stored) {
|
|
143
|
+
let dim = null;
|
|
144
|
+
try {
|
|
145
|
+
const result = await deps.embedder.embed("nlm probe", "query");
|
|
146
|
+
dim = result.vector.length;
|
|
147
|
+
}
|
|
148
|
+
catch {
|
|
149
|
+
// probe failure: pre-tracking tolerance, proceed
|
|
150
|
+
}
|
|
151
|
+
if (dim !== null) {
|
|
152
|
+
const mismatch = stored.provider !== deps.embedderDescriptor.provider ||
|
|
153
|
+
stored.model !== deps.embedderDescriptor.model ||
|
|
154
|
+
stored.dim !== dim;
|
|
155
|
+
if (mismatch && !opts.forceEmbed) {
|
|
156
|
+
const msg = `reprocess: prose embedding lane mismatch: stored ${stored.provider}/${stored.model}@${stored.dim}` +
|
|
157
|
+
` vs runtime ${deps.embedderDescriptor.provider}/${deps.embedderDescriptor.model}@${dim}.` +
|
|
158
|
+
` Re-embedding under a mismatched lane writes mixed vectors. Run nlm embed-backfill first,` +
|
|
159
|
+
` or pass --force-embed to override.`;
|
|
160
|
+
log(msg);
|
|
161
|
+
throw new Error(msg);
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
const bodyStmt = db.prepare("SELECT body FROM sessions WHERE id = ?");
|
|
167
|
+
const done = loadState(statePath, lane);
|
|
168
|
+
const skippedByState = allCandidates.filter((r) => done.has(r.id)).length;
|
|
169
|
+
const candidates = allCandidates.filter((r) => !done.has(r.id));
|
|
170
|
+
const work = opts.limit !== undefined ? candidates.slice(0, opts.limit) : candidates;
|
|
171
|
+
const total = work.length;
|
|
172
|
+
let processed = 0;
|
|
173
|
+
let succeeded = 0;
|
|
174
|
+
let failed = 0;
|
|
175
|
+
let belowFloorOverwrites = 0;
|
|
176
|
+
let sumConfidenceOld = 0;
|
|
177
|
+
let countOld = 0;
|
|
178
|
+
let sumConfidenceNew = 0;
|
|
179
|
+
let countNew = 0;
|
|
180
|
+
for (let i = 0; i < work.length; i++) {
|
|
181
|
+
const row = work[i];
|
|
182
|
+
const sid = row.id;
|
|
183
|
+
const idx = i + 1;
|
|
184
|
+
const bodyRow = bodyStmt.get(sid);
|
|
185
|
+
if (!bodyRow?.body) {
|
|
186
|
+
failed++;
|
|
187
|
+
processed++;
|
|
188
|
+
log(`[reprocess] empty body ${sid}: skipping`);
|
|
189
|
+
opts.onProgress?.(idx, total, sid, "empty_body");
|
|
190
|
+
continue;
|
|
191
|
+
}
|
|
192
|
+
const body = bodyRow.body;
|
|
193
|
+
let classification;
|
|
194
|
+
try {
|
|
195
|
+
classification = await classifyAdaptive(body, classifier, {
|
|
196
|
+
...(opts.classifyTimeoutMs ? { perCallTimeoutMs: opts.classifyTimeoutMs } : {}),
|
|
197
|
+
});
|
|
198
|
+
}
|
|
199
|
+
catch (e) {
|
|
200
|
+
failed++;
|
|
201
|
+
processed++;
|
|
202
|
+
log(`[reprocess] classify failed ${sid}: ${e instanceof Error ? e.message : String(e)}`);
|
|
203
|
+
opts.onProgress?.(idx, total, sid, "classify_failed");
|
|
204
|
+
continue;
|
|
205
|
+
}
|
|
206
|
+
if (row.classifier_confidence !== null) {
|
|
207
|
+
sumConfidenceOld += row.classifier_confidence;
|
|
208
|
+
countOld++;
|
|
209
|
+
}
|
|
210
|
+
sumConfidenceNew += classification.confidence;
|
|
211
|
+
countNew++;
|
|
212
|
+
const belowFloor = classification.confidence < CONFIDENCE_FLOOR;
|
|
213
|
+
if (belowFloor) {
|
|
214
|
+
belowFloorOverwrites++;
|
|
215
|
+
if (opts.verbose) {
|
|
216
|
+
log(`[reprocess] below-floor ${sid}: old=${row.classifier_confidence ?? "null"} new=${classification.confidence}`);
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
else if (opts.verbose) {
|
|
220
|
+
log(`[reprocess] ${sid}: old=${row.classifier_confidence ?? "null"} new=${classification.confidence}`);
|
|
221
|
+
}
|
|
222
|
+
const record = {
|
|
223
|
+
id: sid,
|
|
224
|
+
runtime: row.runtime,
|
|
225
|
+
runtimeSessionId: row.runtime_session_id,
|
|
226
|
+
startedAt: row.started_at,
|
|
227
|
+
endedAt: row.ended_at,
|
|
228
|
+
durationMin: row.duration_min,
|
|
229
|
+
label: classification.label,
|
|
230
|
+
summary: classification.summary,
|
|
231
|
+
body,
|
|
232
|
+
status: row.status,
|
|
233
|
+
transcriptKind: row.transcript_kind,
|
|
234
|
+
transcriptPath: row.transcript_path,
|
|
235
|
+
transcriptOffset: row.transcript_offset,
|
|
236
|
+
transcriptLength: row.transcript_length,
|
|
237
|
+
entities: classification.entities,
|
|
238
|
+
decisions: classification.decisions,
|
|
239
|
+
openQuestions: classification.open,
|
|
240
|
+
classifier: { ...classifierDescriptor, confidence: classification.confidence },
|
|
241
|
+
};
|
|
242
|
+
try {
|
|
243
|
+
const factSinkArg = belowFloor
|
|
244
|
+
? null
|
|
245
|
+
: { factStore, facts: extractFacts(classification, sid, row.started_at) };
|
|
246
|
+
await store.insertSession(record, embedder, null, factSinkArg);
|
|
247
|
+
done.add(sid);
|
|
248
|
+
succeeded++;
|
|
249
|
+
processed++;
|
|
250
|
+
opts.onProgress?.(idx, total, sid, "ok");
|
|
251
|
+
if (succeeded % SAVE_EVERY === 0)
|
|
252
|
+
saveState(statePath, done, lane);
|
|
253
|
+
}
|
|
254
|
+
catch (e) {
|
|
255
|
+
failed++;
|
|
256
|
+
processed++;
|
|
257
|
+
log(`[reprocess] ingest failed ${sid}: ${e instanceof Error ? e.message : String(e)}`);
|
|
258
|
+
opts.onProgress?.(idx, total, sid, "ingest_failed");
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
saveState(statePath, done, lane);
|
|
262
|
+
const limitSkipped = opts.limit !== undefined ? candidates.length - work.length : 0;
|
|
263
|
+
return {
|
|
264
|
+
totalEligible: allCandidates.length,
|
|
265
|
+
processed,
|
|
266
|
+
succeeded,
|
|
267
|
+
failed,
|
|
268
|
+
skippedAlreadyDone: skippedByState,
|
|
269
|
+
limitSkipped,
|
|
270
|
+
belowFloorOverwrites,
|
|
271
|
+
meanConfidenceOld: countOld > 0 ? sumConfidenceOld / countOld : null,
|
|
272
|
+
meanConfidenceNew: countNew > 0 ? sumConfidenceNew / countNew : null,
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
//# sourceMappingURL=reprocess.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"reprocess.js","sourceRoot":"","sources":["../../../src/core/ingest/reprocess.ts"],"names":[],"mappings":"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"}
|
|
@@ -13,8 +13,12 @@
|
|
|
13
13
|
* 'replaces' edges (BFS, depth-capped at 100)
|
|
14
14
|
* I4 every session_edges endpoint exists in sessions
|
|
15
15
|
* I5 at most one active fact per (subject, predicate); every superseded_by
|
|
16
|
-
* references an existing facts.id
|
|
16
|
+
* references an existing facts.id; superseded_by forms no cycle (I5c)
|
|
17
17
|
* I6 every non-null adapter_state.session_id references a sessions.id
|
|
18
|
+
* I7 every fact_embeddings row has a live (active, non-retired) parent fact
|
|
19
|
+
* I7b chunk-level ghost: every session_embedding_chunks row has a map entry
|
|
20
|
+
* (I7b-1); every session_chunk_map row references an existing session
|
|
21
|
+
* (I7b-2)
|
|
18
22
|
*/
|
|
19
23
|
import type Database from "better-sqlite3";
|
|
20
24
|
import type { Pool } from "pg";
|
|
@@ -36,6 +40,7 @@ export declare function runCheapChecksOnPg(pool: Pool): Promise<ReadonlyArray<Vi
|
|
|
36
40
|
export interface FixReport {
|
|
37
41
|
readonly deletedSelfLoops: number;
|
|
38
42
|
readonly restoredToClosed: number;
|
|
43
|
+
readonly deletedGhostEmbeddings: number;
|
|
39
44
|
}
|
|
40
45
|
export declare function applyFixOnSqlite(db: Database.Database): FixReport;
|
|
41
46
|
export declare function applyFixOnPg(pool: Pool): Promise<FixReport>;
|
|
@@ -13,9 +13,29 @@
|
|
|
13
13
|
* 'replaces' edges (BFS, depth-capped at 100)
|
|
14
14
|
* I4 every session_edges endpoint exists in sessions
|
|
15
15
|
* I5 at most one active fact per (subject, predicate); every superseded_by
|
|
16
|
-
* references an existing facts.id
|
|
16
|
+
* references an existing facts.id; superseded_by forms no cycle (I5c)
|
|
17
17
|
* I6 every non-null adapter_state.session_id references a sessions.id
|
|
18
|
+
* I7 every fact_embeddings row has a live (active, non-retired) parent fact
|
|
19
|
+
* I7b chunk-level ghost: every session_embedding_chunks row has a map entry
|
|
20
|
+
* (I7b-1); every session_chunk_map row references an existing session
|
|
21
|
+
* (I7b-2)
|
|
18
22
|
*/
|
|
23
|
+
// Multi-valued predicates legitimately hold parallel active facts (e.g.
|
|
24
|
+
// nlm-memory|stack = TypeScript AND SQLite, both simultaneously true).
|
|
25
|
+
// Force-superseding one copy would destroy a true fact, which is the
|
|
26
|
+
// #351-bug-3 trap. I5a skips subject|predicate groups whose predicate is
|
|
27
|
+
// in this set and instead reports them as an informational count under
|
|
28
|
+
// the id "I5a-mv". Entries MUST be members of PREDICATE_VOCABULARY
|
|
29
|
+
// (src/core/classifier/prompt.ts): predicates are coerced to that closed
|
|
30
|
+
// vocab before facts are written, so any other token here is inert. Add
|
|
31
|
+
// predicates only when parallelism is a domain invariant, not when the
|
|
32
|
+
// classifier merely failed to supersede (#358).
|
|
33
|
+
const MULTI_VALUED_PREDICATES = new Set([
|
|
34
|
+
"stack",
|
|
35
|
+
"integration",
|
|
36
|
+
"dependency",
|
|
37
|
+
]);
|
|
38
|
+
const MV_IN = [...MULTI_VALUED_PREDICATES].map((p) => `'${p}'`).join(", ");
|
|
19
39
|
// ─── SQL strings (portable) ──────────────────────────────────────────────────
|
|
20
40
|
const SQL_I1 = `
|
|
21
41
|
SELECT from_session AS bad_id
|
|
@@ -58,10 +78,23 @@ const SQL_I4_MISSING_TO = `
|
|
|
58
78
|
// One row per conflicting (subject, predicate) group — the count is the number
|
|
59
79
|
// of conflicts to resolve, not the number of facts involved. MIN(id) keeps the
|
|
60
80
|
// projection aggregate-only so PostgreSQL's GROUP BY strictness accepts it.
|
|
81
|
+
// Multi-valued predicates are excluded here; they are counted by SQL_I5A_MULTI_VALUED.
|
|
61
82
|
const SQL_I5_DUPLICATE_FACTS = `
|
|
62
83
|
SELECT MIN(id) AS bad_id
|
|
63
84
|
FROM facts
|
|
64
|
-
WHERE superseded_by IS NULL
|
|
85
|
+
WHERE superseded_by IS NULL AND retired_at IS NULL
|
|
86
|
+
AND predicate NOT IN (${MV_IN})
|
|
87
|
+
GROUP BY subject, predicate
|
|
88
|
+
HAVING COUNT(*) > 1
|
|
89
|
+
LIMIT 6
|
|
90
|
+
`;
|
|
91
|
+
// One row per (subject, predicate) group in the multi-valued exemption set that
|
|
92
|
+
// holds more than one active fact. Count drives the informational I5a-mv report.
|
|
93
|
+
const SQL_I5A_MULTI_VALUED = `
|
|
94
|
+
SELECT MIN(id) AS bad_id
|
|
95
|
+
FROM facts
|
|
96
|
+
WHERE superseded_by IS NULL AND retired_at IS NULL
|
|
97
|
+
AND predicate IN (${MV_IN})
|
|
65
98
|
GROUP BY subject, predicate
|
|
66
99
|
HAVING COUNT(*) > 1
|
|
67
100
|
LIMIT 6
|
|
@@ -80,6 +113,67 @@ const SQL_I6 = `
|
|
|
80
113
|
AND NOT EXISTS (SELECT 1 FROM sessions s WHERE s.id = a.session_id)
|
|
81
114
|
LIMIT 6
|
|
82
115
|
`;
|
|
116
|
+
// An embedding row must have a live (active, non-retired) parent fact.
|
|
117
|
+
// Superseded/retired facts leave the ANN index at write time (ingest collapse,
|
|
118
|
+
// markSuperseded, retire, session cascade); a row here means a write path
|
|
119
|
+
// skipped cleanup; the NLM #351 orphan class.
|
|
120
|
+
//
|
|
121
|
+
// SQLite: fact_embeddings is a vec0 virtual table; its shadow rowids table
|
|
122
|
+
// (fact_embeddings_rowids, column: id) supports standard LEFT JOIN.
|
|
123
|
+
const SQL_I7_GHOST_SQLITE = `
|
|
124
|
+
SELECT r.id AS bad_id
|
|
125
|
+
FROM fact_embeddings_rowids r
|
|
126
|
+
LEFT JOIN facts f ON f.id = r.id
|
|
127
|
+
WHERE f.id IS NULL OR f.superseded_by IS NOT NULL OR f.retired_at IS NOT NULL
|
|
128
|
+
LIMIT 6
|
|
129
|
+
`;
|
|
130
|
+
// PG: fact_embeddings is a regular table with fact_id as PK; the FK ON DELETE
|
|
131
|
+
// CASCADE means f.id IS NULL cannot occur, but the condition is kept symmetric.
|
|
132
|
+
const SQL_I7_GHOST_PG = `
|
|
133
|
+
SELECT fe.fact_id AS bad_id
|
|
134
|
+
FROM fact_embeddings fe
|
|
135
|
+
LEFT JOIN facts f ON f.id = fe.fact_id
|
|
136
|
+
WHERE f.id IS NULL OR f.superseded_by IS NOT NULL OR f.retired_at IS NOT NULL
|
|
137
|
+
LIMIT 6
|
|
138
|
+
`;
|
|
139
|
+
// I7b-1: chunk vectors with no map row (vec0 has no FK; interrupted deletes
|
|
140
|
+
// orphan here). Uses the session_embedding_chunks_rowids shadow table for
|
|
141
|
+
// full-scan access: vec0 tables do not support unfiltered SELECT without a
|
|
142
|
+
// KNN predicate. For INTEGER PK vec0 tables the shadow column is chunk_id
|
|
143
|
+
// (not id, which is null for non-text PKs).
|
|
144
|
+
const SQL_I7B_1_GHOST_SQLITE = `
|
|
145
|
+
SELECT CAST(r.chunk_id AS TEXT) AS bad_id
|
|
146
|
+
FROM session_embedding_chunks_rowids r
|
|
147
|
+
LEFT JOIN session_chunk_map m ON m.chunk_id = r.chunk_id
|
|
148
|
+
WHERE m.chunk_id IS NULL
|
|
149
|
+
LIMIT 6
|
|
150
|
+
`;
|
|
151
|
+
// I7b-2: map rows pointing at missing sessions (SQLite; FK ON DELETE CASCADE
|
|
152
|
+
// fires in normal operation but vec0 deletes are not atomic with the map).
|
|
153
|
+
const SQL_I7B_2_ORPHAN_MAP_SQLITE = `
|
|
154
|
+
SELECT m.session_id AS bad_id
|
|
155
|
+
FROM session_chunk_map m
|
|
156
|
+
LEFT JOIN sessions s ON s.id = m.session_id
|
|
157
|
+
WHERE s.id IS NULL
|
|
158
|
+
LIMIT 6
|
|
159
|
+
`;
|
|
160
|
+
// PG: session_embedding_chunks.session_id and session_chunk_map both carry
|
|
161
|
+
// FK ON DELETE CASCADE to sessions; both checks are FK-impossible in practice
|
|
162
|
+
// and land as regression guards.
|
|
163
|
+
const SQL_I7B_1_GHOST_PG = `
|
|
164
|
+
SELECT CAST(c.chunk_id AS TEXT) AS bad_id
|
|
165
|
+
FROM session_embedding_chunks c
|
|
166
|
+
LEFT JOIN session_chunk_map m ON m.chunk_id = c.chunk_id
|
|
167
|
+
WHERE m.chunk_id IS NULL
|
|
168
|
+
LIMIT 6
|
|
169
|
+
`;
|
|
170
|
+
const SQL_I7B_2_ORPHAN_MAP_PG = `
|
|
171
|
+
SELECT m.session_id AS bad_id
|
|
172
|
+
FROM session_chunk_map m
|
|
173
|
+
LEFT JOIN sessions s ON s.id = m.session_id
|
|
174
|
+
WHERE s.id IS NULL
|
|
175
|
+
LIMIT 6
|
|
176
|
+
`;
|
|
83
177
|
const MAX_CYCLE_DEPTH = 100;
|
|
84
178
|
export function detectCycles(loadEdges) {
|
|
85
179
|
const edges = loadEdges();
|
|
@@ -177,17 +271,48 @@ export function runChecksOnSqlite(db) {
|
|
|
177
271
|
const count = sqliteCount(db, SQL_I5_DUPLICATE_FACTS);
|
|
178
272
|
violations.push(buildViolation("I5a", "multiple active facts for same (subject, predicate)", i5DupSamples, count));
|
|
179
273
|
}
|
|
274
|
+
// I5a-mv: informational count of multi-valued predicate groups (not violations).
|
|
275
|
+
const i5aMvCount = sqliteCount(db, SQL_I5A_MULTI_VALUED);
|
|
276
|
+
if (i5aMvCount > 0) {
|
|
277
|
+
violations.push(buildViolation("I5a-mv", `multi-valued predicate groups exempted from I5a (${[...MULTI_VALUED_PREDICATES].join(", ")})`, [], i5aMvCount));
|
|
278
|
+
}
|
|
180
279
|
const i5DangleSamples = sqliteRows(db, SQL_I5_DANGLING_SUPERSEDED_BY);
|
|
181
280
|
if (i5DangleSamples.length > 0) {
|
|
182
281
|
const count = sqliteCount(db, SQL_I5_DANGLING_SUPERSEDED_BY);
|
|
183
282
|
violations.push(buildViolation("I5b", "facts.superseded_by references non-existent facts.id", i5DangleSamples, count));
|
|
184
283
|
}
|
|
284
|
+
// I5c — facts.superseded_by is a functional graph; a cycle traps every fact in
|
|
285
|
+
// it as recall-ineligible forever (both copies have a non-null superseded_by).
|
|
286
|
+
const i5cCycle = detectCycles(() => db
|
|
287
|
+
.prepare("SELECT id AS from_session, superseded_by AS to_session FROM facts WHERE superseded_by IS NOT NULL")
|
|
288
|
+
.all());
|
|
289
|
+
if (i5cCycle) {
|
|
290
|
+
violations.push({ ...i5cCycle, id: "I5c", description: "facts.superseded_by forms a cycle" });
|
|
291
|
+
}
|
|
185
292
|
// I6
|
|
186
293
|
const i6Samples = sqliteRows(db, SQL_I6);
|
|
187
294
|
if (i6Samples.length > 0) {
|
|
188
295
|
const count = sqliteCount(db, SQL_I6);
|
|
189
296
|
violations.push(buildViolation("I6", "adapter_state.session_id references non-existent sessions.id", i6Samples, count));
|
|
190
297
|
}
|
|
298
|
+
// I7
|
|
299
|
+
const i7Samples = sqliteRows(db, SQL_I7_GHOST_SQLITE);
|
|
300
|
+
if (i7Samples.length > 0) {
|
|
301
|
+
const count = sqliteCount(db, SQL_I7_GHOST_SQLITE);
|
|
302
|
+
violations.push(buildViolation("I7", "fact embedding without a live parent fact", i7Samples, count));
|
|
303
|
+
}
|
|
304
|
+
// I7b-1
|
|
305
|
+
const i7b1Samples = sqliteRows(db, SQL_I7B_1_GHOST_SQLITE);
|
|
306
|
+
if (i7b1Samples.length > 0) {
|
|
307
|
+
const count = sqliteCount(db, SQL_I7B_1_GHOST_SQLITE);
|
|
308
|
+
violations.push(buildViolation("I7b-1", "chunk embedding without a map entry", i7b1Samples, count));
|
|
309
|
+
}
|
|
310
|
+
// I7b-2
|
|
311
|
+
const i7b2Samples = sqliteRows(db, SQL_I7B_2_ORPHAN_MAP_SQLITE);
|
|
312
|
+
if (i7b2Samples.length > 0) {
|
|
313
|
+
const count = sqliteCount(db, SQL_I7B_2_ORPHAN_MAP_SQLITE);
|
|
314
|
+
violations.push(buildViolation("I7b-2", "session_chunk_map references non-existent session", i7b2Samples, count));
|
|
315
|
+
}
|
|
191
316
|
return violations;
|
|
192
317
|
}
|
|
193
318
|
// ─── PostgreSQL runner ────────────────────────────────────────────────────────
|
|
@@ -247,16 +372,45 @@ export async function runChecksOnPg(pool) {
|
|
|
247
372
|
const count = await pgCount(pool, SQL_I5_DUPLICATE_FACTS);
|
|
248
373
|
violations.push(buildViolation("I5a", "multiple active facts for same (subject, predicate)", i5DupSamples, count));
|
|
249
374
|
}
|
|
375
|
+
// I5a-mv: informational count of multi-valued predicate groups (not violations).
|
|
376
|
+
const i5aMvCount = await pgCount(pool, SQL_I5A_MULTI_VALUED);
|
|
377
|
+
if (i5aMvCount > 0) {
|
|
378
|
+
violations.push(buildViolation("I5a-mv", `multi-valued predicate groups exempted from I5a (${[...MULTI_VALUED_PREDICATES].join(", ")})`, [], i5aMvCount));
|
|
379
|
+
}
|
|
250
380
|
if (i5DangleSamples.length > 0) {
|
|
251
381
|
const count = await pgCount(pool, SQL_I5_DANGLING_SUPERSEDED_BY);
|
|
252
382
|
violations.push(buildViolation("I5b", "facts.superseded_by references non-existent facts.id", i5DangleSamples, count));
|
|
253
383
|
}
|
|
384
|
+
// I5c — fact supersedence cycle (see SQLite runner).
|
|
385
|
+
const factEdges = await pool.query("SELECT id AS from_session, superseded_by AS to_session FROM facts WHERE superseded_by IS NOT NULL");
|
|
386
|
+
const i5cCycle = detectCycles(() => factEdges.rows);
|
|
387
|
+
if (i5cCycle) {
|
|
388
|
+
violations.push({ ...i5cCycle, id: "I5c", description: "facts.superseded_by forms a cycle" });
|
|
389
|
+
}
|
|
254
390
|
// I6
|
|
255
391
|
const i6Samples = await pgRows(pool, SQL_I6);
|
|
256
392
|
if (i6Samples.length > 0) {
|
|
257
393
|
const count = await pgCount(pool, SQL_I6);
|
|
258
394
|
violations.push(buildViolation("I6", "adapter_state.session_id references non-existent sessions.id", i6Samples, count));
|
|
259
395
|
}
|
|
396
|
+
// I7
|
|
397
|
+
const i7Samples = await pgRows(pool, SQL_I7_GHOST_PG);
|
|
398
|
+
if (i7Samples.length > 0) {
|
|
399
|
+
const count = await pgCount(pool, SQL_I7_GHOST_PG);
|
|
400
|
+
violations.push(buildViolation("I7", "fact embedding without a live parent fact", i7Samples, count));
|
|
401
|
+
}
|
|
402
|
+
// I7b-1
|
|
403
|
+
const i7b1Samples = await pgRows(pool, SQL_I7B_1_GHOST_PG);
|
|
404
|
+
if (i7b1Samples.length > 0) {
|
|
405
|
+
const count = await pgCount(pool, SQL_I7B_1_GHOST_PG);
|
|
406
|
+
violations.push(buildViolation("I7b-1", "chunk embedding without a map entry", i7b1Samples, count));
|
|
407
|
+
}
|
|
408
|
+
// I7b-2
|
|
409
|
+
const i7b2Samples = await pgRows(pool, SQL_I7B_2_ORPHAN_MAP_PG);
|
|
410
|
+
if (i7b2Samples.length > 0) {
|
|
411
|
+
const count = await pgCount(pool, SQL_I7B_2_ORPHAN_MAP_PG);
|
|
412
|
+
violations.push(buildViolation("I7b-2", "session_chunk_map references non-existent session", i7b2Samples, count));
|
|
413
|
+
}
|
|
260
414
|
return violations;
|
|
261
415
|
}
|
|
262
416
|
// ─── Cheap subset (I1 + I2 + I6) for scheduled watchdog ─────────────────────
|
|
@@ -278,15 +432,33 @@ export function runCheapChecksOnSqlite(db) {
|
|
|
278
432
|
if (i6Samples.length > 0) {
|
|
279
433
|
violations.push(buildViolation("I6", "adapter_state.session_id references non-existent sessions.id", i6Samples, sqliteCount(db, SQL_I6)));
|
|
280
434
|
}
|
|
435
|
+
// I7: LEFT JOIN over fact_embeddings is index-only; within the cheap budget.
|
|
436
|
+
const i7Samples = sqliteRows(db, SQL_I7_GHOST_SQLITE);
|
|
437
|
+
if (i7Samples.length > 0) {
|
|
438
|
+
violations.push(buildViolation("I7", "fact embedding without a live parent fact", i7Samples, sqliteCount(db, SQL_I7_GHOST_SQLITE)));
|
|
439
|
+
}
|
|
440
|
+
// I7b-1
|
|
441
|
+
const i7b1CheapSamples = sqliteRows(db, SQL_I7B_1_GHOST_SQLITE);
|
|
442
|
+
if (i7b1CheapSamples.length > 0) {
|
|
443
|
+
violations.push(buildViolation("I7b-1", "chunk embedding without a map entry", i7b1CheapSamples, sqliteCount(db, SQL_I7B_1_GHOST_SQLITE)));
|
|
444
|
+
}
|
|
445
|
+
// I7b-2
|
|
446
|
+
const i7b2CheapSamples = sqliteRows(db, SQL_I7B_2_ORPHAN_MAP_SQLITE);
|
|
447
|
+
if (i7b2CheapSamples.length > 0) {
|
|
448
|
+
violations.push(buildViolation("I7b-2", "session_chunk_map references non-existent session", i7b2CheapSamples, sqliteCount(db, SQL_I7B_2_ORPHAN_MAP_SQLITE)));
|
|
449
|
+
}
|
|
281
450
|
return violations;
|
|
282
451
|
}
|
|
283
452
|
export async function runCheapChecksOnPg(pool) {
|
|
284
453
|
const violations = [];
|
|
285
|
-
const [i1, i2, i2r, i6] = await Promise.all([
|
|
454
|
+
const [i1, i2, i2r, i6, i7, i7b1, i7b2] = await Promise.all([
|
|
286
455
|
pgRows(pool, SQL_I1),
|
|
287
456
|
pgRows(pool, SQL_I2_ORPHANED),
|
|
288
457
|
pgRows(pool, SQL_I2_ORPHANED_REPLACED),
|
|
289
458
|
pgRows(pool, SQL_I6),
|
|
459
|
+
pgRows(pool, SQL_I7_GHOST_PG),
|
|
460
|
+
pgRows(pool, SQL_I7B_1_GHOST_PG),
|
|
461
|
+
pgRows(pool, SQL_I7B_2_ORPHAN_MAP_PG),
|
|
290
462
|
]);
|
|
291
463
|
if (i1.length > 0) {
|
|
292
464
|
violations.push(buildViolation("I1", "self-loop edges in session_edges", i1, await pgCount(pool, SQL_I1)));
|
|
@@ -300,6 +472,15 @@ export async function runCheapChecksOnPg(pool) {
|
|
|
300
472
|
if (i6.length > 0) {
|
|
301
473
|
violations.push(buildViolation("I6", "adapter_state.session_id references non-existent sessions.id", i6, await pgCount(pool, SQL_I6)));
|
|
302
474
|
}
|
|
475
|
+
if (i7.length > 0) {
|
|
476
|
+
violations.push(buildViolation("I7", "fact embedding without a live parent fact", i7, await pgCount(pool, SQL_I7_GHOST_PG)));
|
|
477
|
+
}
|
|
478
|
+
if (i7b1.length > 0) {
|
|
479
|
+
violations.push(buildViolation("I7b-1", "chunk embedding without a map entry", i7b1, await pgCount(pool, SQL_I7B_1_GHOST_PG)));
|
|
480
|
+
}
|
|
481
|
+
if (i7b2.length > 0) {
|
|
482
|
+
violations.push(buildViolation("I7b-2", "session_chunk_map references non-existent session", i7b2, await pgCount(pool, SQL_I7B_2_ORPHAN_MAP_PG)));
|
|
483
|
+
}
|
|
303
484
|
return violations;
|
|
304
485
|
}
|
|
305
486
|
export function applyFixOnSqlite(db) {
|
|
@@ -322,9 +503,24 @@ export function applyFixOnSqlite(db) {
|
|
|
322
503
|
AND id NOT IN (SELECT to_session FROM session_edges WHERE kind = 'replaces')
|
|
323
504
|
`)
|
|
324
505
|
.run();
|
|
506
|
+
// I7 fix: vec0 only supports single-value DELETE, so loop over bad fact_ids.
|
|
507
|
+
const ghostIds = db
|
|
508
|
+
.prepare(`
|
|
509
|
+
SELECT r.id AS bad_id
|
|
510
|
+
FROM fact_embeddings_rowids r
|
|
511
|
+
LEFT JOIN facts f ON f.id = r.id
|
|
512
|
+
WHERE f.id IS NULL OR f.superseded_by IS NOT NULL OR f.retired_at IS NOT NULL
|
|
513
|
+
`)
|
|
514
|
+
.all();
|
|
515
|
+
const delGhostEmb = db.prepare("DELETE FROM fact_embeddings WHERE fact_id = ?");
|
|
516
|
+
let deletedGhostEmbeddings = 0;
|
|
517
|
+
for (const { bad_id } of ghostIds) {
|
|
518
|
+
deletedGhostEmbeddings += delGhostEmb.run(bad_id).changes;
|
|
519
|
+
}
|
|
325
520
|
return {
|
|
326
521
|
deletedSelfLoops: deleteResult.changes,
|
|
327
522
|
restoredToClosed: updateSuperseded.changes + updateReplaced.changes,
|
|
523
|
+
deletedGhostEmbeddings,
|
|
328
524
|
};
|
|
329
525
|
}
|
|
330
526
|
export async function applyFixOnPg(pool) {
|
|
@@ -340,10 +536,20 @@ export async function applyFixOnPg(pool) {
|
|
|
340
536
|
SET status = 'closed', updated_at = NOW()
|
|
341
537
|
WHERE status = 'replaced'
|
|
342
538
|
AND id NOT IN (SELECT to_session FROM session_edges WHERE kind = 'replaces')
|
|
539
|
+
`);
|
|
540
|
+
const ghostEmbResult = await pool.query(`
|
|
541
|
+
DELETE FROM fact_embeddings
|
|
542
|
+
WHERE fact_id IN (
|
|
543
|
+
SELECT fe.fact_id
|
|
544
|
+
FROM fact_embeddings fe
|
|
545
|
+
LEFT JOIN facts f ON f.id = fe.fact_id
|
|
546
|
+
WHERE f.id IS NULL OR f.superseded_by IS NOT NULL OR f.retired_at IS NOT NULL
|
|
547
|
+
)
|
|
343
548
|
`);
|
|
344
549
|
return {
|
|
345
550
|
deletedSelfLoops: deleteResult.rowCount ?? 0,
|
|
346
551
|
restoredToClosed: (updateSuperseded.rowCount ?? 0) + (updateReplaced.rowCount ?? 0),
|
|
552
|
+
deletedGhostEmbeddings: ghostEmbResult.rowCount ?? 0,
|
|
347
553
|
};
|
|
348
554
|
}
|
|
349
555
|
//# sourceMappingURL=check-invariants.js.map
|