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,40 @@
|
|
|
1
|
+
import { readFileSync } from "node:fs";
|
|
2
|
+
/**
|
|
3
|
+
* Extract message timestamps (epoch ms) from a transcript JSONL file, keeping
|
|
4
|
+
* only those within [fromMs, toMs). Best-effort: a missing/unreadable file or
|
|
5
|
+
* an unparseable line yields no timestamps rather than throwing. Accepts the
|
|
6
|
+
* common timestamp fields across runtimes (claude-code/pi use `timestamp`).
|
|
7
|
+
*/
|
|
8
|
+
export function readTranscriptTimestamps(path, fromMs, toMs) {
|
|
9
|
+
let raw;
|
|
10
|
+
try {
|
|
11
|
+
raw = readFileSync(path, "utf8");
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return [];
|
|
15
|
+
}
|
|
16
|
+
const out = [];
|
|
17
|
+
for (const line of raw.split("\n")) {
|
|
18
|
+
const trimmed = line.trim();
|
|
19
|
+
if (!trimmed)
|
|
20
|
+
continue;
|
|
21
|
+
let obj;
|
|
22
|
+
try {
|
|
23
|
+
obj = JSON.parse(trimmed);
|
|
24
|
+
}
|
|
25
|
+
catch {
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
const tsRaw = obj["timestamp"] ?? obj["ts"] ?? obj["created_at"];
|
|
29
|
+
if (typeof tsRaw !== "string")
|
|
30
|
+
continue;
|
|
31
|
+
const ms = Date.parse(tsRaw);
|
|
32
|
+
if (!Number.isFinite(ms))
|
|
33
|
+
continue;
|
|
34
|
+
if (ms >= fromMs && ms < toMs)
|
|
35
|
+
out.push(ms);
|
|
36
|
+
}
|
|
37
|
+
out.sort((a, b) => a - b);
|
|
38
|
+
return out;
|
|
39
|
+
}
|
|
40
|
+
//# sourceMappingURL=read-transcript-timestamps.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"read-transcript-timestamps.js","sourceRoot":"","sources":["../../../src/core/work-digest/read-transcript-timestamps.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAEvC;;;;;GAKG;AACH,MAAM,UAAU,wBAAwB,CAAC,IAAY,EAAE,MAAc,EAAE,IAAY;IACjF,IAAI,GAAW,CAAC;IAChB,IAAI,CAAC;QACH,GAAG,GAAG,YAAY,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IACnC,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;IACD,MAAM,GAAG,GAAa,EAAE,CAAC;IACzB,KAAK,MAAM,IAAI,IAAI,GAAG,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QACnC,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QAC5B,IAAI,CAAC,OAAO;YAAE,SAAS;QACvB,IAAI,GAA4B,CAAC;QACjC,IAAI,CAAC;YACH,GAAG,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAA4B,CAAC;QACvD,CAAC;QAAC,MAAM,CAAC;YACP,SAAS;QACX,CAAC;QACD,MAAM,KAAK,GAAG,GAAG,CAAC,WAAW,CAAC,IAAI,GAAG,CAAC,IAAI,CAAC,IAAI,GAAG,CAAC,YAAY,CAAC,CAAC;QACjE,IAAI,OAAO,KAAK,KAAK,QAAQ;YAAE,SAAS;QACxC,MAAM,EAAE,GAAG,IAAI,CAAC,KAAK,CAAC,KAAK,CAAC,CAAC;QAC7B,IAAI,CAAC,MAAM,CAAC,QAAQ,CAAC,EAAE,CAAC;YAAE,SAAS;QACnC,IAAI,EAAE,IAAI,MAAM,IAAI,EAAE,GAAG,IAAI;YAAE,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAC9C,CAAC;IACD,GAAG,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC;IAC1B,OAAO,GAAG,CAAC;AACb,CAAC"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/** The minimal session view a topic provider sees. Extensions (e.g. NLOS) can
|
|
2
|
+
* key on entities and/or label to impose their own taxonomy. */
|
|
3
|
+
export interface TopicInput {
|
|
4
|
+
readonly entities: ReadonlyArray<string>;
|
|
5
|
+
readonly label: string;
|
|
6
|
+
/** Resolved live-workstream label, when the session is bound. Takes precedence over entity/alias topics. */
|
|
7
|
+
readonly workstreamLabel?: string;
|
|
8
|
+
}
|
|
9
|
+
export type TopicProvider = (input: TopicInput) => string;
|
|
10
|
+
/** NLM-core default: the session's first classified entity, normalized. */
|
|
11
|
+
export declare const defaultTopicProvider: TopicProvider;
|
|
12
|
+
/** NLM-core: prefer the bound workstream's label; fall through to `fallback`
|
|
13
|
+
* (alias-map / first-entity) for unbound sessions. Retires the
|
|
14
|
+
* alphabetically-first-entity dependency for bound sessions (spec §10). */
|
|
15
|
+
export declare function workstreamTopicProvider(fallback: TopicProvider): TopicProvider;
|
|
16
|
+
/** NLM-core optional: group entities into labels via an operator-supplied map.
|
|
17
|
+
* The same interface an extension uses to supply a function/taxonomy map. */
|
|
18
|
+
export declare function aliasTopicProvider(map: Record<string, string>): TopicProvider;
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
function normalize(s) {
|
|
2
|
+
return s.trim().toLowerCase();
|
|
3
|
+
}
|
|
4
|
+
/** NLM-core default: the session's first classified entity, normalized. */
|
|
5
|
+
export const defaultTopicProvider = (input) => {
|
|
6
|
+
const first = input.entities[0];
|
|
7
|
+
return first && first.trim() ? normalize(first) : "uncategorized";
|
|
8
|
+
};
|
|
9
|
+
/** NLM-core: prefer the bound workstream's label; fall through to `fallback`
|
|
10
|
+
* (alias-map / first-entity) for unbound sessions. Retires the
|
|
11
|
+
* alphabetically-first-entity dependency for bound sessions (spec §10). */
|
|
12
|
+
export function workstreamTopicProvider(fallback) {
|
|
13
|
+
return (input) => input.workstreamLabel && input.workstreamLabel.trim() ? input.workstreamLabel : fallback(input);
|
|
14
|
+
}
|
|
15
|
+
/** NLM-core optional: group entities into labels via an operator-supplied map.
|
|
16
|
+
* The same interface an extension uses to supply a function/taxonomy map. */
|
|
17
|
+
export function aliasTopicProvider(map) {
|
|
18
|
+
const norm = {};
|
|
19
|
+
for (const [k, v] of Object.entries(map))
|
|
20
|
+
norm[normalize(k)] = v;
|
|
21
|
+
return (input) => {
|
|
22
|
+
const base = defaultTopicProvider(input);
|
|
23
|
+
return norm[base] ?? base;
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
//# sourceMappingURL=topics.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"topics.js","sourceRoot":"","sources":["../../../src/core/work-digest/topics.ts"],"names":[],"mappings":"AAWA,SAAS,SAAS,CAAC,CAAS;IAC1B,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;AAChC,CAAC;AAED,2EAA2E;AAC3E,MAAM,CAAC,MAAM,oBAAoB,GAAkB,CAAC,KAAK,EAAE,EAAE;IAC3D,MAAM,KAAK,GAAG,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC;IAChC,OAAO,KAAK,IAAI,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,eAAe,CAAC;AACpE,CAAC,CAAC;AAEF;;4EAE4E;AAC5E,MAAM,UAAU,uBAAuB,CAAC,QAAuB;IAC7D,OAAO,CAAC,KAAK,EAAE,EAAE,CACf,KAAK,CAAC,eAAe,IAAI,KAAK,CAAC,eAAe,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC,eAAe,CAAC,CAAC,CAAC,QAAQ,CAAC,KAAK,CAAC,CAAC;AACpG,CAAC;AAED;8EAC8E;AAC9E,MAAM,UAAU,kBAAkB,CAAC,GAA2B;IAC5D,MAAM,IAAI,GAA2B,EAAE,CAAC;IACxC,KAAK,MAAM,CAAC,CAAC,EAAE,CAAC,CAAC,IAAI,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC;QAAE,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC;IACjE,OAAO,CAAC,KAAK,EAAE,EAAE;QACf,MAAM,IAAI,GAAG,oBAAoB,CAAC,KAAK,CAAC,CAAC;QACzC,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,IAAI,CAAC;IAC5B,CAAC,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/** A half-open-ish activity interval in epoch milliseconds (end >= start). */
|
|
2
|
+
export interface Interval {
|
|
3
|
+
readonly start: number;
|
|
4
|
+
readonly end: number;
|
|
5
|
+
}
|
|
6
|
+
/** One session's in-day message timestamps plus its resolved topic. */
|
|
7
|
+
export interface SessionActivity {
|
|
8
|
+
readonly sessionId: string;
|
|
9
|
+
readonly topic: string;
|
|
10
|
+
readonly timestampsMs: ReadonlyArray<number>;
|
|
11
|
+
readonly workstreamId?: string | null;
|
|
12
|
+
}
|
|
13
|
+
/** Attention: active minutes attributed to one topic. `meta` is an opaque
|
|
14
|
+
* extension seam (Section 7.1 of the design) — NLM core never reads it. */
|
|
15
|
+
export interface TopicShare {
|
|
16
|
+
readonly topic: string;
|
|
17
|
+
readonly activeMinutes: number;
|
|
18
|
+
readonly share: number;
|
|
19
|
+
readonly meta?: Record<string, unknown>;
|
|
20
|
+
}
|
|
21
|
+
export interface FocusStats {
|
|
22
|
+
readonly contextSwitches: number;
|
|
23
|
+
readonly longestBlockMin: number;
|
|
24
|
+
readonly deepWorkRatio: number;
|
|
25
|
+
readonly projectsTouched: number;
|
|
26
|
+
}
|
|
27
|
+
export interface ProgressStats {
|
|
28
|
+
readonly decisions: ReadonlyArray<string>;
|
|
29
|
+
readonly openLoops: ReadonlyArray<string>;
|
|
30
|
+
}
|
|
31
|
+
export interface Coverage {
|
|
32
|
+
readonly sessions: number;
|
|
33
|
+
readonly activeTimeMeasured: number;
|
|
34
|
+
readonly activeTimeSkipped: number;
|
|
35
|
+
}
|
|
36
|
+
export interface WorkDigest {
|
|
37
|
+
readonly date: string;
|
|
38
|
+
readonly idleThresholdMin: number;
|
|
39
|
+
readonly scopeNote: string;
|
|
40
|
+
readonly coverage: Coverage;
|
|
41
|
+
readonly activeMinutes: number;
|
|
42
|
+
readonly byTopic: ReadonlyArray<TopicShare>;
|
|
43
|
+
readonly focus: FocusStats;
|
|
44
|
+
readonly progress: ProgressStats;
|
|
45
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../../src/core/work-digest/types.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import type { WorkstreamStore } from "../../ports/workstream-store.js";
|
|
2
|
+
import type { SessionStore } from "../../ports/session-store.js";
|
|
3
|
+
import type { LLMClient } from "../../ports/llm-client.js";
|
|
4
|
+
import type { BindingSource } from "./model.js";
|
|
5
|
+
export declare const NAMING_CONTENT_CHARS = 8000;
|
|
6
|
+
export interface BindDeps {
|
|
7
|
+
readonly namer: Pick<LLMClient, "nameWorkstream">;
|
|
8
|
+
readonly workstreams: Pick<WorkstreamStore, "listAll" | "upsertEntities" | "touchLastSession">;
|
|
9
|
+
readonly sessions: Pick<SessionStore, "setWorkstreamBinding">;
|
|
10
|
+
readonly aliasToLabel: ReadonlyMap<string, string>;
|
|
11
|
+
readonly log?: (msg: string) => void;
|
|
12
|
+
readonly source?: BindingSource;
|
|
13
|
+
}
|
|
14
|
+
export interface BindInput {
|
|
15
|
+
readonly sessionId: string;
|
|
16
|
+
readonly label: string;
|
|
17
|
+
readonly summary: string;
|
|
18
|
+
readonly body?: string;
|
|
19
|
+
readonly entities: ReadonlyArray<string>;
|
|
20
|
+
readonly startedAt: string;
|
|
21
|
+
}
|
|
22
|
+
export interface BindResult {
|
|
23
|
+
readonly workstreamId: string;
|
|
24
|
+
}
|
|
25
|
+
export declare function bindSessionToWorkstream(deps: BindDeps, input: BindInput): Promise<BindResult | null>;
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { decideWorkstreamByName } from "./name-match.js";
|
|
2
|
+
export const NAMING_CONTENT_CHARS = 8000;
|
|
3
|
+
export async function bindSessionToWorkstream(deps, input) {
|
|
4
|
+
try {
|
|
5
|
+
const ws = await deps.workstreams.listAll();
|
|
6
|
+
const hints = ws.map((w) => ({ label: w.label }));
|
|
7
|
+
const content = `${input.label}\n${(input.body || input.summary).slice(0, NAMING_CONTENT_CHARS)}`;
|
|
8
|
+
const named = await deps.namer.nameWorkstream(content, hints);
|
|
9
|
+
const decision = decideWorkstreamByName(named, ws, deps.aliasToLabel);
|
|
10
|
+
if (decision.kind === "abstain")
|
|
11
|
+
return null;
|
|
12
|
+
const { workstreamId } = decision;
|
|
13
|
+
await deps.sessions.setWorkstreamBinding(input.sessionId, workstreamId, deps.source ?? "classifier", null);
|
|
14
|
+
await deps.workstreams.upsertEntities(workstreamId, input.entities);
|
|
15
|
+
await deps.workstreams.touchLastSession(workstreamId, input.startedAt);
|
|
16
|
+
return { workstreamId };
|
|
17
|
+
}
|
|
18
|
+
catch (e) {
|
|
19
|
+
deps.log?.(`[workstream] bind failed for ${input.sessionId}: ${e instanceof Error ? e.message : String(e)}`);
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
//# sourceMappingURL=bind.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bind.js","sourceRoot":"","sources":["../../../src/core/workstream/bind.ts"],"names":[],"mappings":"AAKA,OAAO,EAAE,sBAAsB,EAAE,MAAM,iBAAiB,CAAC;AAEzD,MAAM,CAAC,MAAM,oBAAoB,GAAG,IAAI,CAAC;AAwBzC,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAAC,IAAc,EAAE,KAAgB;IAC5E,IAAI,CAAC;QACH,MAAM,EAAE,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,OAAO,EAAE,CAAC;QAC5C,MAAM,KAAK,GAAG,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;QAClD,MAAM,OAAO,GAAG,GAAG,KAAK,CAAC,KAAK,KAAK,CAAC,KAAK,CAAC,IAAI,IAAI,KAAK,CAAC,OAAO,CAAC,CAAC,KAAK,CAAC,CAAC,EAAE,oBAAoB,CAAC,EAAE,CAAC;QAClG,MAAM,KAAK,GAAG,MAAM,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,OAAO,EAAE,KAAK,CAAC,CAAC;QAC9D,MAAM,QAAQ,GAAG,sBAAsB,CAAC,KAAK,EAAE,EAAE,EAAE,IAAI,CAAC,YAAY,CAAC,CAAC;QAEtE,IAAI,QAAQ,CAAC,IAAI,KAAK,SAAS;YAAE,OAAO,IAAI,CAAC;QAE7C,MAAM,EAAE,YAAY,EAAE,GAAG,QAAQ,CAAC;QAClC,MAAM,IAAI,CAAC,QAAQ,CAAC,oBAAoB,CAAC,KAAK,CAAC,SAAS,EAAE,YAAY,EAAE,IAAI,CAAC,MAAM,IAAI,YAAY,EAAE,IAAI,CAAC,CAAC;QAC3G,MAAM,IAAI,CAAC,WAAW,CAAC,cAAc,CAAC,YAAY,EAAE,KAAK,CAAC,QAAQ,CAAC,CAAC;QACpE,MAAM,IAAI,CAAC,WAAW,CAAC,gBAAgB,CAAC,YAAY,EAAE,KAAK,CAAC,SAAS,CAAC,CAAC;QACvE,OAAO,EAAE,YAAY,EAAE,CAAC;IAC1B,CAAC;IAAC,OAAO,CAAC,EAAE,CAAC;QACX,IAAI,CAAC,GAAG,EAAE,CAAC,gCAAgC,KAAK,CAAC,SAAS,KAAK,CAAC,YAAY,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;QAC7G,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
export function composeWorkstreamRecall(view) {
|
|
2
|
+
const { workstream, sessionIds, facts, exemplars } = view;
|
|
3
|
+
const lines = [];
|
|
4
|
+
lines.push(`WORKSTREAM: ${workstream.label}`);
|
|
5
|
+
lines.push(`(${sessionIds.length} sessions${workstream.lastSessionAt ? `, last active ${workstream.lastSessionAt.slice(0, 10)}` : ""})`);
|
|
6
|
+
lines.push("");
|
|
7
|
+
const decisions = facts.filter((f) => f.kind === "decision");
|
|
8
|
+
const open = facts.filter((f) => f.kind === "open");
|
|
9
|
+
const attrs = facts.filter((f) => f.kind === "attribute");
|
|
10
|
+
if (decisions.length) {
|
|
11
|
+
lines.push("DECISIONS:");
|
|
12
|
+
for (const f of decisions)
|
|
13
|
+
lines.push(` - ${f.value}`);
|
|
14
|
+
lines.push("");
|
|
15
|
+
}
|
|
16
|
+
if (open.length) {
|
|
17
|
+
lines.push("OPEN LOOPS:");
|
|
18
|
+
for (const f of open)
|
|
19
|
+
lines.push(` - ${f.value}`);
|
|
20
|
+
lines.push("");
|
|
21
|
+
}
|
|
22
|
+
if (attrs.length) {
|
|
23
|
+
lines.push("FACTS:");
|
|
24
|
+
for (const f of attrs)
|
|
25
|
+
lines.push(` - ${f.subject} ${f.predicate} ${f.value}`);
|
|
26
|
+
lines.push("");
|
|
27
|
+
}
|
|
28
|
+
if (exemplars.length) {
|
|
29
|
+
lines.push("CODE EXEMPLARS:");
|
|
30
|
+
for (const e of exemplars)
|
|
31
|
+
lines.push(` - [${e.outcome}] ${e.repo}: ${e.taskContext}`);
|
|
32
|
+
lines.push("");
|
|
33
|
+
}
|
|
34
|
+
if (!decisions.length && !open.length && !attrs.length && !exemplars.length) {
|
|
35
|
+
lines.push("(no accumulated facts or exemplars yet)");
|
|
36
|
+
}
|
|
37
|
+
return lines.join("\n").trimEnd();
|
|
38
|
+
}
|
|
39
|
+
//# sourceMappingURL=compose-recall.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"compose-recall.js","sourceRoot":"","sources":["../../../src/core/workstream/compose-recall.ts"],"names":[],"mappings":"AAGA,MAAM,UAAU,uBAAuB,CAAC,IAAsB;IAC5D,MAAM,EAAE,UAAU,EAAE,UAAU,EAAE,KAAK,EAAE,SAAS,EAAE,GAAG,IAAI,CAAC;IAC1D,MAAM,KAAK,GAAa,EAAE,CAAC;IAC3B,KAAK,CAAC,IAAI,CAAC,eAAe,UAAU,CAAC,KAAK,EAAE,CAAC,CAAC;IAC9C,KAAK,CAAC,IAAI,CAAC,IAAI,UAAU,CAAC,MAAM,YAAY,UAAU,CAAC,aAAa,CAAC,CAAC,CAAC,iBAAiB,UAAU,CAAC,aAAa,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,CAAC,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IACzI,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IAEf,MAAM,SAAS,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,UAAU,CAAC,CAAC;IAC7D,MAAM,IAAI,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,MAAM,CAAC,CAAC;IACpD,MAAM,KAAK,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,IAAI,KAAK,WAAW,CAAC,CAAC;IAE1D,IAAI,SAAS,CAAC,MAAM,EAAE,CAAC;QACrB,KAAK,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;QACzB,KAAK,MAAM,CAAC,IAAI,SAAS;YAAE,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC;QACxD,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACjB,CAAC;IACD,IAAI,IAAI,CAAC,MAAM,EAAE,CAAC;QAChB,KAAK,CAAC,IAAI,CAAC,aAAa,CAAC,CAAC;QAC1B,KAAK,MAAM,CAAC,IAAI,IAAI;YAAE,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC;QACnD,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACjB,CAAC;IACD,IAAI,KAAK,CAAC,MAAM,EAAE,CAAC;QACjB,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC;QACrB,KAAK,MAAM,CAAC,IAAI,KAAK;YAAE,KAAK,CAAC,IAAI,CAAC,OAAO,CAAC,CAAC,OAAO,IAAI,CAAC,CAAC,SAAS,IAAI,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC;QAChF,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACjB,CAAC;IACD,IAAI,SAAS,CAAC,MAAM,EAAE,CAAC;QACrB,KAAK,CAAC,IAAI,CAAC,iBAAiB,CAAC,CAAC;QAC9B,KAAK,MAAM,CAAC,IAAI,SAAS;YAAE,KAAK,CAAC,IAAI,CAAC,QAAQ,CAAC,CAAC,OAAO,KAAK,CAAC,CAAC,IAAI,KAAK,CAAC,CAAC,WAAW,EAAE,CAAC,CAAC;QACxF,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACjB,CAAC;IACD,IAAI,CAAC,SAAS,CAAC,MAAM,IAAI,CAAC,IAAI,CAAC,MAAM,IAAI,CAAC,KAAK,CAAC,MAAM,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,CAAC;QAC5E,KAAK,CAAC,IAAI,CAAC,yCAAyC,CAAC,CAAC;IACxD,CAAC;IACD,OAAO,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC,OAAO,EAAE,CAAC;AACpC,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
export interface MergeSuggestInputItem {
|
|
2
|
+
readonly id: string;
|
|
3
|
+
readonly label: string;
|
|
4
|
+
readonly entities: ReadonlyArray<string>;
|
|
5
|
+
readonly sessionIds: ReadonlyArray<string>;
|
|
6
|
+
}
|
|
7
|
+
export interface MergeSuggestion {
|
|
8
|
+
readonly aId: string;
|
|
9
|
+
readonly aLabel: string;
|
|
10
|
+
readonly bId: string;
|
|
11
|
+
readonly bLabel: string;
|
|
12
|
+
readonly score: number;
|
|
13
|
+
readonly sharedEntities: number;
|
|
14
|
+
readonly sharedSessions: number;
|
|
15
|
+
readonly labelSimilarity: number;
|
|
16
|
+
}
|
|
17
|
+
export declare function scoreMergePair(a: MergeSuggestInputItem, b: MergeSuggestInputItem): MergeSuggestion;
|
|
18
|
+
/** All unordered pairs scoring >= minScore, ranked desc. Pure; O(n^2) over the (small) workstream set. */
|
|
19
|
+
export declare function suggestMerges(items: ReadonlyArray<MergeSuggestInputItem>, minScore: number): ReadonlyArray<MergeSuggestion>;
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
import { normalizeLabel } from "./model.js";
|
|
2
|
+
function jaccard(a, b) {
|
|
3
|
+
const sa = new Set(a);
|
|
4
|
+
const sb = new Set(b);
|
|
5
|
+
let shared = 0;
|
|
6
|
+
for (const x of sa)
|
|
7
|
+
if (sb.has(x))
|
|
8
|
+
shared++;
|
|
9
|
+
const union = sa.size + sb.size - shared;
|
|
10
|
+
return { shared, score: union === 0 ? 0 : shared / union };
|
|
11
|
+
}
|
|
12
|
+
function levenshtein(a, b) {
|
|
13
|
+
const m = a.length;
|
|
14
|
+
const n = b.length;
|
|
15
|
+
if (m === 0)
|
|
16
|
+
return n;
|
|
17
|
+
if (n === 0)
|
|
18
|
+
return m;
|
|
19
|
+
let prev = Array.from({ length: n + 1 }, (_, i) => i);
|
|
20
|
+
let cur = new Array(n + 1);
|
|
21
|
+
for (let i = 1; i <= m; i++) {
|
|
22
|
+
cur[0] = i;
|
|
23
|
+
for (let j = 1; j <= n; j++) {
|
|
24
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
25
|
+
cur[j] = Math.min(prev[j] + 1, cur[j - 1] + 1, prev[j - 1] + cost);
|
|
26
|
+
}
|
|
27
|
+
[prev, cur] = [cur, prev];
|
|
28
|
+
}
|
|
29
|
+
return prev[n];
|
|
30
|
+
}
|
|
31
|
+
function labelSimilarity(a, b) {
|
|
32
|
+
const na = normalizeLabel(a);
|
|
33
|
+
const nb = normalizeLabel(b);
|
|
34
|
+
const maxLen = Math.max(na.length, nb.length);
|
|
35
|
+
if (maxLen === 0)
|
|
36
|
+
return 1;
|
|
37
|
+
return 1 - levenshtein(na, nb) / maxLen;
|
|
38
|
+
}
|
|
39
|
+
export function scoreMergePair(a, b) {
|
|
40
|
+
const ent = jaccard(a.entities, b.entities);
|
|
41
|
+
const sess = jaccard(a.sessionIds, b.sessionIds);
|
|
42
|
+
const lab = labelSimilarity(a.label, b.label);
|
|
43
|
+
const score = (ent.score + sess.score + lab) / 3;
|
|
44
|
+
return {
|
|
45
|
+
aId: a.id, aLabel: a.label, bId: b.id, bLabel: b.label,
|
|
46
|
+
score, sharedEntities: ent.shared, sharedSessions: sess.shared, labelSimilarity: lab,
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
/** All unordered pairs scoring >= minScore, ranked desc. Pure; O(n^2) over the (small) workstream set. */
|
|
50
|
+
export function suggestMerges(items, minScore) {
|
|
51
|
+
const out = [];
|
|
52
|
+
for (let i = 0; i < items.length; i++) {
|
|
53
|
+
for (let j = i + 1; j < items.length; j++) {
|
|
54
|
+
const s = scoreMergePair(items[i], items[j]);
|
|
55
|
+
if (s.score >= minScore)
|
|
56
|
+
out.push(s);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return out.sort((x, y) => y.score - x.score);
|
|
60
|
+
}
|
|
61
|
+
//# sourceMappingURL=merge-suggest.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"merge-suggest.js","sourceRoot":"","sources":["../../../src/core/workstream/merge-suggest.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Fact, CodeExemplar } from "../../shared/types.js";
|
|
2
|
+
export type WorkstreamStatus = "active" | "merged" | "retired";
|
|
3
|
+
export interface Workstream {
|
|
4
|
+
readonly id: string;
|
|
5
|
+
readonly label: string;
|
|
6
|
+
readonly status: WorkstreamStatus;
|
|
7
|
+
readonly mergedInto: string | null;
|
|
8
|
+
readonly createdAt: string;
|
|
9
|
+
readonly updatedAt: string;
|
|
10
|
+
readonly lastSessionAt: string | null;
|
|
11
|
+
}
|
|
12
|
+
export type BindingSource = "classifier" | "operator" | "backfill";
|
|
13
|
+
export interface WorkstreamRollup {
|
|
14
|
+
readonly workstream: Workstream;
|
|
15
|
+
readonly sessionIds: ReadonlyArray<string>;
|
|
16
|
+
readonly facts: ReadonlyArray<Fact>;
|
|
17
|
+
readonly exemplars: ReadonlyArray<CodeExemplar>;
|
|
18
|
+
}
|
|
19
|
+
export declare function makeWorkstreamId(): string;
|
|
20
|
+
export declare function normalizeLabel(label: string): string;
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
// src/core/workstream/model.ts
|
|
2
|
+
import { randomUUID } from "node:crypto";
|
|
3
|
+
export function makeWorkstreamId() {
|
|
4
|
+
return `ws_${randomUUID()}`;
|
|
5
|
+
}
|
|
6
|
+
export function normalizeLabel(label) {
|
|
7
|
+
return label.trim().toLowerCase().replace(/\s+/g, " ");
|
|
8
|
+
}
|
|
9
|
+
//# sourceMappingURL=model.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"model.js","sourceRoot":"","sources":["../../../src/core/workstream/model.ts"],"names":[],"mappings":"AAAA,+BAA+B;AAC/B,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAwBzC,MAAM,UAAU,gBAAgB;IAC9B,OAAO,MAAM,UAAU,EAAE,EAAE,CAAC;AAC9B,CAAC;AAED,MAAM,UAAU,cAAc,CAAC,KAAa;IAC1C,OAAO,KAAK,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC,OAAO,CAAC,MAAM,EAAE,GAAG,CAAC,CAAC;AACzD,CAAC"}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type Workstream } from "./model.js";
|
|
2
|
+
export type NameDecision = {
|
|
3
|
+
kind: "bind";
|
|
4
|
+
workstreamId: string;
|
|
5
|
+
} | {
|
|
6
|
+
kind: "abstain";
|
|
7
|
+
};
|
|
8
|
+
export declare function decideWorkstreamByName(named: string | null, workstreams: ReadonlyArray<Pick<Workstream, "id" | "label">>, aliasToLabel: ReadonlyMap<string, string>): NameDecision;
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
// src/core/workstream/name-match.ts
|
|
2
|
+
import { normalizeLabel } from "./model.js";
|
|
3
|
+
export function decideWorkstreamByName(named, workstreams, aliasToLabel) {
|
|
4
|
+
if (!named || !named.trim())
|
|
5
|
+
return { kind: "abstain" };
|
|
6
|
+
const byLabel = new Map(workstreams.map((w) => [normalizeLabel(w.label), w.id]));
|
|
7
|
+
const direct = byLabel.get(normalizeLabel(named));
|
|
8
|
+
if (direct)
|
|
9
|
+
return { kind: "bind", workstreamId: direct };
|
|
10
|
+
const canonical = aliasToLabel.get(normalizeLabel(named));
|
|
11
|
+
if (canonical) {
|
|
12
|
+
const viaAlias = byLabel.get(normalizeLabel(canonical));
|
|
13
|
+
if (viaAlias)
|
|
14
|
+
return { kind: "bind", workstreamId: viaAlias };
|
|
15
|
+
}
|
|
16
|
+
return { kind: "abstain" };
|
|
17
|
+
}
|
|
18
|
+
//# sourceMappingURL=name-match.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"name-match.js","sourceRoot":"","sources":["../../../src/core/workstream/name-match.ts"],"names":[],"mappings":"AAAA,oCAAoC;AACpC,OAAO,EAAE,cAAc,EAAmB,MAAM,YAAY,CAAC;AAI7D,MAAM,UAAU,sBAAsB,CACpC,KAAoB,EACpB,WAA4D,EAC5D,YAAyC;IAEzC,IAAI,CAAC,KAAK,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE;QAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;IACxD,MAAM,OAAO,GAAG,IAAI,GAAG,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,cAAc,CAAC,CAAC,CAAC,KAAK,CAAC,EAAE,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC;IACjF,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC,CAAC;IAClD,IAAI,MAAM;QAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,EAAE,CAAC;IAC1D,MAAM,SAAS,GAAG,YAAY,CAAC,GAAG,CAAC,cAAc,CAAC,KAAK,CAAC,CAAC,CAAC;IAC1D,IAAI,SAAS,EAAE,CAAC;QACd,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,SAAS,CAAC,CAAC,CAAC;QACxD,IAAI,QAAQ;YAAE,OAAO,EAAE,IAAI,EAAE,MAAM,EAAE,YAAY,EAAE,QAAQ,EAAE,CAAC;IAChE,CAAC;IACD,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;AAC7B,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Walk the merged_into chain to the live survivor. Mirrors the replaces-chain
|
|
3
|
+
* resolution in ui/lib/thread-groups.ts. Iterative + visited-set guarded so a
|
|
4
|
+
* (data-corrupt) cycle terminates instead of looping. Fail-open: an id absent
|
|
5
|
+
* from the map resolves to itself.
|
|
6
|
+
*/
|
|
7
|
+
export declare function resolveWorkstreamId(id: string, byId: ReadonlyMap<string, {
|
|
8
|
+
id: string;
|
|
9
|
+
mergedInto: string | null;
|
|
10
|
+
}>): string;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
// src/core/workstream/resolve.ts
|
|
2
|
+
/**
|
|
3
|
+
* Walk the merged_into chain to the live survivor. Mirrors the replaces-chain
|
|
4
|
+
* resolution in ui/lib/thread-groups.ts. Iterative + visited-set guarded so a
|
|
5
|
+
* (data-corrupt) cycle terminates instead of looping. Fail-open: an id absent
|
|
6
|
+
* from the map resolves to itself.
|
|
7
|
+
*/
|
|
8
|
+
export function resolveWorkstreamId(id, byId) {
|
|
9
|
+
const seen = new Set();
|
|
10
|
+
let cur = id;
|
|
11
|
+
while (true) {
|
|
12
|
+
if (seen.has(cur))
|
|
13
|
+
return cur;
|
|
14
|
+
seen.add(cur);
|
|
15
|
+
const node = byId.get(cur);
|
|
16
|
+
if (!node || node.mergedInto === null)
|
|
17
|
+
return cur;
|
|
18
|
+
cur = node.mergedInto;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=resolve.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"resolve.js","sourceRoot":"","sources":["../../../src/core/workstream/resolve.ts"],"names":[],"mappings":"AAAA,iCAAiC;AAEjC;;;;;GAKG;AACH,MAAM,UAAU,mBAAmB,CACjC,EAAU,EACV,IAAoE;IAEpE,MAAM,IAAI,GAAG,IAAI,GAAG,EAAU,CAAC;IAC/B,IAAI,GAAG,GAAG,EAAE,CAAC;IACb,OAAO,IAAI,EAAE,CAAC;QACZ,IAAI,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC;YAAE,OAAO,GAAG,CAAC;QAC9B,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACd,MAAM,IAAI,GAAG,IAAI,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QAC3B,IAAI,CAAC,IAAI,IAAI,IAAI,CAAC,UAAU,KAAK,IAAI;YAAE,OAAO,GAAG,CAAC;QAClD,GAAG,GAAG,IAAI,CAAC,UAAU,CAAC;IACxB,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { WorkstreamStore } from "../../ports/workstream-store.js";
|
|
2
|
+
import type { SessionStore } from "../../ports/session-store.js";
|
|
3
|
+
import type { FactStore } from "../../ports/fact-store.js";
|
|
4
|
+
import type { CodeExemplarStore } from "../../ports/code-exemplar-store.js";
|
|
5
|
+
import type { WorkstreamRollup } from "./model.js";
|
|
6
|
+
export interface RollupDeps {
|
|
7
|
+
readonly workstreams: Pick<WorkstreamStore, "listAll" | "getById">;
|
|
8
|
+
readonly sessions: Pick<SessionStore, "listSessionIdsByWorkstreams">;
|
|
9
|
+
readonly facts: Pick<FactStore, "listBySessions">;
|
|
10
|
+
readonly exemplars: Pick<CodeExemplarStore, "listBySessions">;
|
|
11
|
+
}
|
|
12
|
+
export declare function rollupWorkstream(deps: RollupDeps, workstreamId: string): Promise<WorkstreamRollup | null>;
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { resolveWorkstreamId } from "./resolve.js";
|
|
2
|
+
export async function rollupWorkstream(deps, workstreamId) {
|
|
3
|
+
const all = await deps.workstreams.listAll();
|
|
4
|
+
const byId = new Map(all.map((w) => [w.id, { id: w.id, mergedInto: w.mergedInto }]));
|
|
5
|
+
const survivorId = resolveWorkstreamId(workstreamId, byId);
|
|
6
|
+
const workstream = await deps.workstreams.getById(survivorId);
|
|
7
|
+
if (!workstream)
|
|
8
|
+
return null;
|
|
9
|
+
const memberIds = all.filter((w) => resolveWorkstreamId(w.id, byId) === survivorId).map((w) => w.id);
|
|
10
|
+
const sessionIds = await deps.sessions.listSessionIdsByWorkstreams(memberIds);
|
|
11
|
+
const [facts, exemplars] = await Promise.all([
|
|
12
|
+
deps.facts.listBySessions(sessionIds),
|
|
13
|
+
deps.exemplars.listBySessions(sessionIds),
|
|
14
|
+
]);
|
|
15
|
+
return { workstream, sessionIds, facts, exemplars };
|
|
16
|
+
}
|
|
17
|
+
//# sourceMappingURL=rollup.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rollup.js","sourceRoot":"","sources":["../../../src/core/workstream/rollup.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,mBAAmB,EAAE,MAAM,cAAc,CAAC;AASnD,MAAM,CAAC,KAAK,UAAU,gBAAgB,CAAC,IAAgB,EAAE,YAAoB;IAC3E,MAAM,GAAG,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,OAAO,EAAE,CAAC;IAC7C,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,EAAE,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,UAAU,EAAE,CAAC,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,CAAC;IACrF,MAAM,UAAU,GAAG,mBAAmB,CAAC,YAAY,EAAE,IAAI,CAAC,CAAC;IAC3D,MAAM,UAAU,GAAG,MAAM,IAAI,CAAC,WAAW,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;IAC9D,IAAI,CAAC,UAAU;QAAE,OAAO,IAAI,CAAC;IAE7B,MAAM,SAAS,GAAG,GAAG,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,mBAAmB,CAAC,CAAC,CAAC,EAAE,EAAE,IAAI,CAAC,KAAK,UAAU,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC;IACrG,MAAM,UAAU,GAAG,MAAM,IAAI,CAAC,QAAQ,CAAC,2BAA2B,CAAC,SAAS,CAAC,CAAC;IAC9E,MAAM,CAAC,KAAK,EAAE,SAAS,CAAC,GAAG,MAAM,OAAO,CAAC,GAAG,CAAC;QAC3C,IAAI,CAAC,KAAK,CAAC,cAAc,CAAC,UAAU,CAAC;QACrC,IAAI,CAAC,SAAS,CAAC,cAAc,CAAC,UAAU,CAAC;KAC1C,CAAC,CAAC;IACH,OAAO,EAAE,UAAU,EAAE,UAAU,EAAE,KAAK,EAAE,SAAS,EAAE,CAAC;AACtD,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
export interface WorkTopic {
|
|
2
|
+
readonly label: string;
|
|
3
|
+
readonly entities: ReadonlyArray<string>;
|
|
4
|
+
}
|
|
5
|
+
export declare function parseWorkTopics(raw: unknown): ReadonlyArray<WorkTopic>;
|
|
6
|
+
/**
|
|
7
|
+
* Build a map from normalized alias -> canonical label for all topics.
|
|
8
|
+
* Used by the binding path to resolve classifier output through aliases.
|
|
9
|
+
*/
|
|
10
|
+
export declare function aliasToLabelMap(topics: ReadonlyArray<WorkTopic>): Map<string, string>;
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
// src/core/workstream/work-topics.ts
|
|
2
|
+
import { normalizeLabel } from "./model.js";
|
|
3
|
+
export function parseWorkTopics(raw) {
|
|
4
|
+
if (Array.isArray(raw)) {
|
|
5
|
+
return raw.map((t) => {
|
|
6
|
+
if (!t ||
|
|
7
|
+
typeof t !== "object" ||
|
|
8
|
+
typeof t.label !== "string" ||
|
|
9
|
+
!Array.isArray(t.entities)) {
|
|
10
|
+
throw new Error(`work-topics: array item is not {label, entities[]}: ${JSON.stringify(t)}`);
|
|
11
|
+
}
|
|
12
|
+
return {
|
|
13
|
+
label: t.label,
|
|
14
|
+
entities: (t.entities).map(String),
|
|
15
|
+
};
|
|
16
|
+
});
|
|
17
|
+
}
|
|
18
|
+
if (raw && typeof raw === "object") {
|
|
19
|
+
const entries = Object.entries(raw);
|
|
20
|
+
// Alias-map shape { "<alias>": "<canonical>" }: group aliases under their
|
|
21
|
+
// canonical label. Canonical = workstream label; aliases (+ the canonical itself)
|
|
22
|
+
// = its entities.
|
|
23
|
+
if (entries.length > 0 && entries.every(([, v]) => typeof v === "string")) {
|
|
24
|
+
const byCanonical = new Map();
|
|
25
|
+
for (const [alias, canonical] of entries) {
|
|
26
|
+
const set = byCanonical.get(canonical) ?? new Set([canonical]);
|
|
27
|
+
set.add(alias);
|
|
28
|
+
byCanonical.set(canonical, set);
|
|
29
|
+
}
|
|
30
|
+
return [...byCanonical].map(([label, entities]) => ({ label, entities: [...entities] }));
|
|
31
|
+
}
|
|
32
|
+
// Label-to-entities map { "<label>": ["<entity>", ...] }.
|
|
33
|
+
return entries.map(([label, ents]) => {
|
|
34
|
+
if (!Array.isArray(ents)) {
|
|
35
|
+
throw new Error(`work-topics: value for "${label}" is neither a string (alias map) nor an array (entity map)`);
|
|
36
|
+
}
|
|
37
|
+
return { label, entities: ents.map(String) };
|
|
38
|
+
});
|
|
39
|
+
}
|
|
40
|
+
throw new Error("work-topics: expected an object map or an array of {label, entities[]}");
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Build a map from normalized alias -> canonical label for all topics.
|
|
44
|
+
* Used by the binding path to resolve classifier output through aliases.
|
|
45
|
+
*/
|
|
46
|
+
export function aliasToLabelMap(topics) {
|
|
47
|
+
const out = new Map();
|
|
48
|
+
for (const topic of topics) {
|
|
49
|
+
for (const entity of topic.entities) {
|
|
50
|
+
out.set(normalizeLabel(entity), topic.label);
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
return out;
|
|
54
|
+
}
|
|
55
|
+
//# sourceMappingURL=work-topics.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"work-topics.js","sourceRoot":"","sources":["../../../src/core/workstream/work-topics.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare function readStdin(): Promise<string>;
|
|
2
|
+
export declare function fetchWithTimeout(url: string, init: RequestInit, ms: number): Promise<Response>;
|
|
3
|
+
export declare function hookModeFromEnv(): "shadow" | "live";
|
|
4
|
+
export declare function appendHookEvent(data: Record<string, unknown>): void;
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import { appendFileSync, mkdirSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { dirname, join } from "node:path";
|
|
4
|
+
export function readStdin() {
|
|
5
|
+
return new Promise((resolve) => {
|
|
6
|
+
let data = "";
|
|
7
|
+
process.stdin.setEncoding("utf8");
|
|
8
|
+
process.stdin.on("data", (chunk) => (data += chunk));
|
|
9
|
+
process.stdin.on("end", () => resolve(data));
|
|
10
|
+
process.stdin.on("error", () => resolve(data));
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
export async function fetchWithTimeout(url, init, ms) {
|
|
14
|
+
const controller = new AbortController();
|
|
15
|
+
const timer = setTimeout(() => controller.abort(), ms);
|
|
16
|
+
try {
|
|
17
|
+
return await fetch(url, { ...init, signal: controller.signal });
|
|
18
|
+
}
|
|
19
|
+
finally {
|
|
20
|
+
clearTimeout(timer);
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export function hookModeFromEnv() {
|
|
24
|
+
return process.env["NLM_HOOK_MODE"] === "live" ? "live" : "shadow";
|
|
25
|
+
}
|
|
26
|
+
export function appendHookEvent(data) {
|
|
27
|
+
try {
|
|
28
|
+
const path = process.env["NLM_HOOK_LOG"] ?? join(homedir(), ".nlm", "hook-log.jsonl");
|
|
29
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
30
|
+
appendFileSync(path, `${JSON.stringify(data)}\n`, "utf8");
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
// Telemetry failure must never break the hook.
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=hook-helpers.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"hook-helpers.js","sourceRoot":"","sources":["../../src/hook/hook-helpers.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpD,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAE1C,MAAM,UAAU,SAAS;IACvB,OAAO,IAAI,OAAO,CAAC,CAAC,OAAO,EAAE,EAAE;QAC7B,IAAI,IAAI,GAAG,EAAE,CAAC;QACd,OAAO,CAAC,KAAK,CAAC,WAAW,CAAC,MAAM,CAAC,CAAC;QAClC,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,KAAK,EAAE,EAAE,CAAC,CAAC,IAAI,IAAI,KAAK,CAAC,CAAC,CAAC;QACrD,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;QAC7C,OAAO,CAAC,KAAK,CAAC,EAAE,CAAC,OAAO,EAAE,GAAG,EAAE,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC;IACjD,CAAC,CAAC,CAAC;AACL,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,gBAAgB,CACpC,GAAW,EACX,IAAiB,EACjB,EAAU;IAEV,MAAM,UAAU,GAAG,IAAI,eAAe,EAAE,CAAC;IACzC,MAAM,KAAK,GAAG,UAAU,CAAC,GAAG,EAAE,CAAC,UAAU,CAAC,KAAK,EAAE,EAAE,EAAE,CAAC,CAAC;IACvD,IAAI,CAAC;QACH,OAAO,MAAM,KAAK,CAAC,GAAG,EAAE,EAAE,GAAG,IAAI,EAAE,MAAM,EAAE,UAAU,CAAC,MAAM,EAAE,CAAC,CAAC;IAClE,CAAC;YAAS,CAAC;QACT,YAAY,CAAC,KAAK,CAAC,CAAC;IACtB,CAAC;AACH,CAAC;AAED,MAAM,UAAU,eAAe;IAC7B,OAAO,OAAO,CAAC,GAAG,CAAC,eAAe,CAAC,KAAK,MAAM,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,QAAQ,CAAC;AACrE,CAAC;AAED,MAAM,UAAU,eAAe,CAAC,IAA6B;IAC3D,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC,IAAI,IAAI,CAAC,OAAO,EAAE,EAAE,MAAM,EAAE,gBAAgB,CAAC,CAAC;QACtF,SAAS,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;QAC9C,cAAc,CAAC,IAAI,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,IAAI,EAAE,MAAM,CAAC,CAAC;IAC5D,CAAC;IAAC,MAAM,CAAC;QACP,+CAA+C;IACjD,CAAC;AACH,CAAC"}
|