akm-cli 0.9.16 → 0.9.17-alpha.10
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/CHANGELOG.md +2101 -0
- package/STABILITY.md +11 -10
- package/dist/akm +124 -193
- package/dist/akm-migrate +38 -19
- package/dist/assets/hints/cli-hints-full.md +6 -7
- package/dist/assets/improve-strategies/catchup.json +0 -3
- package/dist/assets/improve-strategies/consolidate.json +0 -1
- package/dist/assets/improve-strategies/default.json +1 -2
- package/dist/assets/improve-strategies/proactive-maintenance.json +1 -2
- package/dist/assets/improve-strategies/quick.json +1 -2
- package/dist/assets/improve-strategies/reflect-distill.json +1 -2
- package/dist/assets/improve-strategies/thorough.json +0 -3
- package/dist/assets/prompts/consolidate-pair.md +20 -0
- package/dist/assets/prompts/consolidate-system.md +4 -11
- package/dist/assets/prompts/retrieval-relevance-judge.md +6 -0
- package/dist/assets/stash-skeleton/facts/conventions/backlinks.md +20 -20
- package/dist/assets/stash-skeleton/facts/conventions/domains.md +2 -2
- package/dist/assets/templates/html/health.html +3 -5
- package/dist/cli/retired-commands.js +1 -1
- package/dist/cli/shared.js +6 -2
- package/dist/cli/unknown-flags.js +24 -1
- package/dist/cli.js +68 -10
- package/dist/commands/agent/agent-dispatch.js +1 -1
- package/dist/commands/command/command-execution.js +24 -62
- package/dist/commands/feedback-cli.js +0 -1
- package/dist/commands/health/accept-rate.js +6 -0
- package/dist/commands/health/archive-usage.js +92 -0
- package/dist/commands/health/checks.js +83 -74
- package/dist/commands/health/config-skew.js +38 -0
- package/dist/commands/health/data-dir-usage.js +25 -13
- package/dist/commands/health/egress.js +54 -0
- package/dist/commands/health/html-report.js +1 -42
- package/dist/commands/health/improve-metrics.js +136 -591
- package/dist/commands/health/md-report.js +1 -6
- package/dist/commands/health/plugin-staleness.js +53 -3
- package/dist/commands/health/renderers.js +12 -4
- package/dist/commands/health/report-view-model.js +14 -120
- package/dist/commands/health/types-improve.js +4 -19
- package/dist/commands/health/windows.js +64 -74
- package/dist/commands/health.js +145 -143
- package/dist/commands/improve/consolidate/chunking.js +26 -117
- package/dist/commands/improve/consolidate/continuity-check.js +137 -0
- package/dist/commands/improve/consolidate/pair-pass.js +791 -0
- package/dist/commands/improve/consolidate/sanitize.js +54 -149
- package/dist/commands/improve/consolidate.js +589 -1127
- package/dist/commands/improve/content-hash.js +16 -24
- package/dist/commands/improve/distill/content-repair.js +18 -100
- package/dist/commands/improve/distill-guards.js +20 -81
- package/dist/commands/improve/distill-promotion-policy.js +23 -243
- package/dist/commands/improve/distill.js +608 -1041
- package/dist/commands/improve/eligibility.js +126 -390
- package/dist/commands/improve/execution.js +8 -10
- package/dist/commands/improve/extract-prompt.js +1 -2
- package/dist/commands/improve/extract.js +487 -1046
- package/dist/commands/improve/feedback-valence.js +0 -25
- package/dist/commands/improve/improve-cli.js +75 -169
- package/dist/commands/improve/improve-result-file.js +10 -66
- package/dist/commands/improve/improve-strategies.js +52 -4
- package/dist/commands/improve/improve-usage-report.js +18 -64
- package/dist/commands/improve/improve.js +480 -1074
- package/dist/commands/improve/ledger.js +119 -0
- package/dist/commands/improve/locks.js +2 -8
- package/dist/commands/improve/loop-stages.js +415 -1073
- package/dist/commands/improve/memory/derived-ref.js +12 -77
- package/dist/commands/improve/memory/memory-belief.js +16 -118
- package/dist/commands/improve/memory/memory-improve.js +266 -14
- package/dist/commands/improve/outcome-loop.js +28 -156
- package/dist/commands/improve/planner.js +5 -15
- package/dist/commands/improve/preparation.js +779 -2319
- package/dist/commands/improve/proactive-maintenance.js +34 -101
- package/dist/commands/improve/reflect-noise.js +104 -280
- package/dist/commands/improve/reflect.js +642 -1353
- package/dist/commands/improve/retrieval-gate.js +127 -0
- package/dist/commands/improve/retrieval-scope.js +92 -0
- package/dist/commands/improve/salience.js +41 -240
- package/dist/commands/improve/session-asset.js +19 -100
- package/dist/commands/improve/stage.js +322 -0
- package/dist/commands/lint/base-linter.js +37 -15
- package/dist/commands/proposal/drain.js +261 -578
- package/dist/commands/proposal/proposal-cli.js +19 -20
- package/dist/commands/proposal/proposal-types.js +31 -24
- package/dist/commands/proposal/proposal.js +38 -8
- package/dist/commands/proposal/propose.js +134 -160
- package/dist/commands/proposal/repository.js +1097 -1394
- package/dist/commands/proposal/validators/proposal-quality-validators.js +71 -174
- package/dist/commands/proposal/validators/proposal-validators.js +1 -1
- package/dist/commands/proposal/validators/proposals.js +22 -89
- package/dist/commands/read/curate.js +105 -462
- package/dist/commands/read/knowledge.js +3 -2
- package/dist/commands/read/search-cli.js +16 -33
- package/dist/commands/read/search.js +17 -23
- package/dist/commands/read/show.js +57 -108
- package/dist/commands/sources/bundle-cli.js +25 -2
- package/dist/commands/sources/bundle-config-ops.js +4 -0
- package/dist/commands/sources/dangerous-env-audit.js +1 -2
- package/dist/commands/sources/info.js +127 -29
- package/dist/commands/sources/installed-stashes.js +197 -746
- package/dist/commands/sources/schema-repair.js +98 -129
- package/dist/commands/sources/source-add.js +62 -12
- package/dist/commands/sources/source-manage.js +9 -2
- package/dist/commands/sources/stash-cli.js +24 -4
- package/dist/commands/tasks/explain.js +10 -13
- package/dist/commands/tasks/tasks-cli.js +12 -13
- package/dist/commands/tasks/tasks.js +350 -936
- package/dist/commands/tasks/validate.js +26 -24
- package/dist/commands/workflow/plan.js +22 -29
- package/dist/commands/workflow-cli.js +4 -4
- package/dist/core/adapter/adapters/akm-adapter.js +2 -1
- package/dist/core/adapter/adapters/akm-lint.js +2 -3
- package/dist/core/adapter/adapters/akm-metadata.js +42 -12
- package/dist/core/adapter/adapters/akm-task-adapter.js +29 -8
- package/dist/core/adapter/adapters/akm-workflow-adapter.js +1 -1
- package/dist/core/adapter/execution-source.js +17 -29
- package/dist/core/asset/asset-placement.js +4 -13
- package/dist/core/asset/frontmatter.js +106 -1
- package/dist/core/asset/resolve-ref.js +1 -1
- package/dist/core/bundle-id.js +42 -5
- package/dist/core/bundle-rename.js +285 -0
- package/dist/core/config/config-io.js +1 -2
- package/dist/core/config/config-schema.js +9 -34
- package/dist/core/config/config-walker.js +1 -1
- package/dist/core/config/config.js +184 -111
- package/dist/core/config/engine-semantics.js +0 -2
- package/dist/core/config/legacy-source-shape-shim.js +38 -9
- package/dist/core/config/schema/embedding.js +20 -5
- package/dist/core/config/schema/engines.js +5 -0
- package/dist/core/config/schema/execution.js +1 -1
- package/dist/core/config/schema/experimental.js +1 -1
- package/dist/core/config/schema/improve-processes.js +54 -125
- package/dist/core/config/schema/improve.js +4 -42
- package/dist/core/config/schema/index-config.js +9 -48
- package/dist/core/config/schema/scheduler.js +12 -12
- package/dist/core/config/schema/search.js +6 -22
- package/dist/core/env-secret-ref.js +0 -1
- package/dist/core/errors.js +8 -9
- package/dist/core/file-change.js +13 -5
- package/dist/core/file-lock.js +76 -173
- package/dist/core/improve-result.js +35 -7
- package/dist/core/improve-types.js +0 -1
- package/dist/core/logs-db.js +2 -2
- package/dist/core/loopback.js +7 -12
- package/dist/core/non-task-input.js +20 -0
- package/dist/core/parse.js +13 -16
- package/dist/core/paths.js +0 -24
- package/dist/core/redaction.js +109 -2
- package/dist/core/run-lock.js +2 -5
- package/dist/core/spawn-env.js +1 -1
- package/dist/core/state/migrations.js +123 -61
- package/dist/core/state-db-scope.js +2 -4
- package/dist/core/state-db.js +126 -692
- package/dist/core/time.js +0 -20
- package/dist/core/type-presentation.js +1 -9
- package/dist/core/write-source.js +294 -1005
- package/dist/execution/input-contract.js +1 -1
- package/dist/execution/resolved-request.js +135 -689
- package/dist/execution/source.js +63 -257
- package/dist/execution/target-ref.js +1 -1
- package/dist/indexer/bundle-identity-guard.js +2 -2
- package/dist/indexer/db/llm-cache.js +2 -2
- package/dist/indexer/ensure-index.js +77 -73
- package/dist/indexer/index-rebuild-lock.js +3 -11
- package/dist/indexer/index-writer-lock.js +8 -17
- package/dist/indexer/index-written-assets.js +141 -154
- package/dist/indexer/indexer.js +400 -1124
- package/dist/indexer/links/declared-links.js +90 -0
- package/dist/indexer/materialize-embeddings.js +60 -397
- package/dist/indexer/passes/memory-inference.js +96 -90
- package/dist/indexer/passes/metadata.js +132 -219
- package/dist/indexer/read-preflight.js +0 -7
- package/dist/indexer/scan/doc-to-entry.js +2 -3
- package/dist/indexer/scan/drain-dir.js +1 -1
- package/dist/indexer/search/db-search.js +190 -590
- package/dist/indexer/search/fts-query.js +30 -41
- package/dist/indexer/search/ranking.js +28 -154
- package/dist/indexer/search/search-attribution.js +12 -32
- package/dist/indexer/search/search-fields.js +11 -15
- package/dist/indexer/search/search-hit-enrichers.js +54 -85
- package/dist/indexer/search/search-source.js +1 -4
- package/dist/indexer/usage/usage-events.js +36 -7
- package/dist/indexer/walk/walker.js +3 -4
- package/dist/integrations/agent/engine-fallback.js +23 -40
- package/dist/integrations/agent/engine-resolution.js +93 -183
- package/dist/integrations/agent/execution.js +507 -0
- package/dist/integrations/agent/model-map.js +28 -156
- package/dist/integrations/agent/request-lowering.js +66 -141
- package/dist/integrations/agent/runner-dispatch.js +143 -321
- package/dist/integrations/agent/runner.js +54 -14
- package/dist/integrations/lockfile.js +53 -101
- package/dist/llm/client.js +18 -6
- package/dist/llm/embedders/deterministic.js +2 -3
- package/dist/llm/embedders/profile.js +71 -0
- package/dist/llm/embedders/remote.js +11 -17
- package/dist/llm/feature-gate.js +0 -8
- package/dist/llm/index-passes.js +3 -5
- package/dist/llm/memory-infer.js +1 -2
- package/dist/llm/structured-call.js +5 -24
- package/dist/output/generic-render.js +23 -11
- package/dist/output/html-render.js +13 -10
- package/dist/output/render-registry.js +3 -32
- package/dist/output/shapes/helpers.js +25 -38
- package/dist/output/shapes/passthrough.js +1 -9
- package/dist/{indexer/graph/graph-types.js → output/text/bundle-rename.js} +4 -1
- package/dist/output/text/command-format.js +69 -31
- package/dist/output/text/helpers.js +1 -1
- package/dist/output/text/migrate.js +5 -14
- package/dist/output/text/proposal-format.js +48 -3
- package/dist/output/text/show-format.js +13 -17
- package/dist/output/text/workflow-format.js +0 -32
- package/dist/output/text.js +2 -0
- package/dist/registry/factory.js +4 -19
- package/dist/registry/network.js +66 -220
- package/dist/registry/providers/index.js +0 -2
- package/dist/registry/providers/skills-sh.js +3 -14
- package/dist/registry/providers/static-index.js +24 -26
- package/dist/registry/resolve.js +55 -131
- package/dist/scripts/akm-migrate-node.js +42948 -92369
- package/dist/scripts/akm-migrate.js +42935 -92354
- package/dist/setup/registry-stash-loader.js +4 -13
- package/dist/setup/semantic-assets.js +3 -44
- package/dist/setup/setup.js +1 -1
- package/dist/setup/steps/connection.js +5 -6
- package/dist/setup/steps/platforms.js +2 -2
- package/dist/setup/steps/tasks.js +25 -15
- package/dist/sources/provider-factory.js +17 -18
- package/dist/sources/providers/filesystem.js +2 -3
- package/dist/sources/providers/git-install.js +7 -1
- package/dist/sources/providers/git-provider.js +0 -3
- package/dist/sources/providers/git-stash.js +83 -21
- package/dist/sources/providers/npm.js +2 -4
- package/dist/sources/providers/provider-utils.js +5 -10
- package/dist/sources/providers/website.js +0 -2
- package/dist/sources/snapshot-fetchers/website-ingest.js +1 -1
- package/dist/sources/website-url.js +2 -2
- package/dist/storage/database.js +9 -35
- package/dist/storage/repositories/improve-ledger-repository.js +209 -0
- package/dist/storage/repositories/index-connection.js +39 -72
- package/dist/storage/repositories/index-entries-repository.js +131 -129
- package/dist/storage/repositories/index-entry-mapper.js +1 -2
- package/dist/storage/repositories/index-entry-schema.js +101 -268
- package/dist/storage/repositories/index-fts-repository.js +86 -256
- package/dist/storage/repositories/index-links-repository.js +143 -0
- package/dist/storage/repositories/index-llm-cache-repository.js +7 -9
- package/dist/storage/repositories/index-meta-repository.js +6 -4
- package/dist/storage/repositories/index-schema.js +257 -325
- package/dist/storage/repositories/index-utility-repository.js +8 -29
- package/dist/storage/repositories/index-vec-repository.js +133 -414
- package/dist/storage/repositories/outcome-repository.js +2 -1
- package/dist/storage/repositories/proposals-repository.js +104 -1
- package/dist/storage/repositories/registry-index-cache-repository.js +100 -0
- package/dist/storage/repositories/salience-repository.js +1 -19
- package/dist/storage/repositories/task-history-repository.js +26 -4
- package/dist/storage/repositories/workflow-runs-repository.js +53 -244
- package/dist/storage/sqlite-migrations.js +136 -0
- package/dist/storage/sqlite-pragmas.js +11 -9
- package/dist/storage/sqlite-transaction.js +170 -0
- package/dist/storage/state-db-integrity.js +130 -0
- package/dist/tasks/activation-config.js +134 -62
- package/dist/tasks/backends/cron.js +191 -302
- package/dist/tasks/backends/exec-utils.js +2 -5
- package/dist/tasks/backends/launchd.js +141 -748
- package/dist/tasks/backends/schtasks.js +119 -623
- package/dist/tasks/prepare/prepare-support.js +5 -15
- package/dist/tasks/prepare/prepare.js +0 -2
- package/dist/tasks/resolve-akm-bin.js +20 -79
- package/dist/tasks/run/attempt-lifecycle.js +0 -1
- package/dist/tasks/run/load-task.js +1 -1
- package/dist/tasks/scheduler-binding.js +20 -238
- package/dist/tasks/scheduler-invocation.js +136 -244
- package/dist/tasks/scheduler-lock.js +53 -0
- package/dist/tasks/scheduler-sync.js +368 -679
- package/dist/tasks/source/parse-task-source.js +55 -9
- package/dist/tasks/source/task-source-v3-frozen.js +3 -4
- package/dist/tasks/source/task-to-v4.js +464 -88
- package/dist/workflows/authoring/authoring.js +3 -12
- package/dist/workflows/compile.js +211 -0
- package/dist/workflows/concurrency-policy.js +13 -74
- package/dist/workflows/exec/child-invocation.js +3 -17
- package/dist/workflows/exec/child-workflow.js +32 -141
- package/dist/workflows/exec/dispatch-redaction.js +13 -53
- package/dist/workflows/exec/environment.js +98 -0
- package/dist/workflows/exec/exec-unit.js +33 -140
- package/dist/workflows/exec/frozen-judge.js +7 -59
- package/dist/workflows/exec/native-executor.js +82 -341
- package/dist/workflows/exec/param-secrets.js +29 -47
- package/dist/workflows/exec/run-workflow.js +154 -387
- package/dist/workflows/exec/scheduler.js +9 -36
- package/dist/workflows/exec/step-work.js +127 -430
- package/dist/workflows/exec/unit-dispatch.js +11 -63
- package/dist/workflows/exec/unit-writer.js +8 -52
- package/dist/workflows/exec/worktree.js +39 -273
- package/dist/workflows/freeze/child-output-references.js +4 -15
- package/dist/workflows/freeze/environment.js +99 -92
- package/dist/workflows/freeze/freeze.js +172 -0
- package/dist/workflows/freeze/step-values.js +19 -21
- package/dist/workflows/freeze/targets/child-workflow.js +23 -92
- package/dist/workflows/freeze/targets/command.js +10 -33
- package/dist/workflows/freeze/targets/script.js +5 -12
- package/dist/workflows/freeze/targets/shell.js +3 -6
- package/dist/workflows/freeze/targets/task.js +25 -80
- package/dist/workflows/freeze/task-bindings.js +20 -67
- package/dist/workflows/{source-ir/github-yaml.js → github-yaml.js} +88 -206
- package/dist/workflows/ir/params.js +6 -51
- package/dist/workflows/ir/plan-hash.js +2 -34
- package/dist/workflows/parser.js +140 -43
- package/dist/{commands/improve/consolidate/types.js → workflows/plan.js} +2 -1
- package/dist/workflows/renderer.js +36 -69
- package/dist/workflows/resource-limits.js +12 -120
- package/dist/workflows/runtime/agent-identity.js +8 -40
- package/dist/workflows/runtime/run-outputs.js +3 -6
- package/dist/workflows/runtime/run-plan.js +316 -0
- package/dist/workflows/runtime/runs.js +48 -200
- package/dist/workflows/runtime/workflow-asset-loader.js +24 -57
- package/dist/workflows/{source-ir/semantics.js → source-semantics.js} +16 -20
- package/dist/workflows/validate-summary.js +2 -7
- package/docs/integration/bundling-akm.md +49 -42
- package/docs/migration/README.md +1 -0
- package/docs/migration/release-notes/0.9.17.md +43 -0
- package/docs/migration/v0.9.1-to-v0.9.2.md +23 -7
- package/docs/reference/cli.md +232 -135
- package/docs/reference/configuration.md +71 -57
- package/docs/reference/data-and-telemetry.md +20 -21
- package/docs/reference/tasks.md +105 -39
- package/docs/reference/workflow-schema.md +14 -18
- package/docs/reference/workflows.md +6 -9
- package/package.json +1 -1
- package/schemas/akm-config.json +115 -738
- package/schemas/akm-workflow.json +1 -0
- package/dist/assets/improve-strategies/graph-refresh.json +0 -15
- package/dist/assets/prompts/contradiction-judge.md +0 -33
- package/dist/assets/prompts/graph-extract-system.md +0 -1
- package/dist/assets/prompts/graph-extract-user-prompt.md +0 -35
- package/dist/assets/prompts/metadata-enhance-system.md +0 -1
- package/dist/assets/tasks/improve/akm-graph-refresh-weekly.yml +0 -4
- package/dist/commands/health/advisories.js +0 -150
- package/dist/commands/health/metrics.js +0 -329
- package/dist/commands/health/surfaces.js +0 -102
- package/dist/commands/improve/anti-collapse.js +0 -83
- package/dist/commands/improve/collapse-detector.js +0 -432
- package/dist/commands/improve/consolidate/eligibility.js +0 -48
- package/dist/commands/improve/consolidate/merge.js +0 -149
- package/dist/commands/improve/distill/promote-memory.js +0 -291
- package/dist/commands/improve/distill/quality-gate.js +0 -337
- package/dist/commands/improve/eval-cases.js +0 -52
- package/dist/commands/improve/memory/memory-contradiction-detect.js +0 -291
- package/dist/commands/improve/proposal-envelope.js +0 -31
- package/dist/commands/improve/run-context.js +0 -123
- package/dist/commands/improve/shared.js +0 -31
- package/dist/commands/improve/source-identity.js +0 -28
- package/dist/commands/improve/triage.js +0 -96
- package/dist/commands/proposal/drain-policies.js +0 -151
- package/dist/commands/sources/update-transaction.js +0 -220
- package/dist/core/action-contributors.js +0 -28
- package/dist/core/config/config-version-shim.js +0 -101
- package/dist/core/fs-txn.js +0 -405
- package/dist/core/lexical-score.js +0 -25
- package/dist/core/maintenance-barrier.js +0 -167
- package/dist/execution/executable-identity.js +0 -105
- package/dist/execution/guarded-source.js +0 -427
- package/dist/indexer/db/graph-db.js +0 -444
- package/dist/indexer/graph/graph-boost.js +0 -427
- package/dist/indexer/graph/graph-dedup.js +0 -95
- package/dist/indexer/graph/graph-extraction.js +0 -1108
- package/dist/indexer/search/name-match.js +0 -35
- package/dist/indexer/search/ranking-contributors.js +0 -515
- package/dist/indexer/search/ranking-types.js +0 -4
- package/dist/indexer/walk/project-context.js +0 -192
- package/dist/integrations/agent/execution-cascade.js +0 -566
- package/dist/integrations/agent/execution-definitions.js +0 -202
- package/dist/integrations/agent/execution-lowering.js +0 -841
- package/dist/integrations/agent/execution-preparation.js +0 -98
- package/dist/integrations/agent/inline-execution.js +0 -74
- package/dist/llm/graph-extract.js +0 -728
- package/dist/llm/metadata-enhance.js +0 -96
- package/dist/registry/create-provider-registry.js +0 -29
- package/dist/registry/pinned-request-helper.js +0 -247
- package/dist/registry/pinned-transport.js +0 -717
- package/dist/sources/providers/index.js +0 -14
- package/dist/storage/engines/sqlite-migrations.js +0 -271
- package/dist/storage/repositories/canaries-repository.js +0 -107
- package/dist/storage/repositories/embedding-salvage-repository.js +0 -184
- package/dist/storage/repositories/registry-cache.js +0 -113
- package/dist/tasks/scheduler-sync-preview.js +0 -52
- package/dist/tasks/source/task-to-v3.js +0 -507
- package/dist/workflows/freeze/resolve-steps.js +0 -86
- package/dist/workflows/freeze/source-freeze.js +0 -64
- package/dist/workflows/ir/compile.js +0 -321
- package/dist/workflows/ir/environment-v4.js +0 -330
- package/dist/workflows/ir/freeze-v4.js +0 -153
- package/dist/workflows/ir/schema-v4.js +0 -745
- package/dist/workflows/ir/schema.js +0 -354
- package/dist/workflows/program/schema.js +0 -77
- package/dist/workflows/runtime/checkin.js +0 -57
- package/dist/workflows/runtime/plan-classifier.js +0 -196
- package/dist/workflows/runtime/unit-checkin.js +0 -45
- package/dist/workflows/runtime/unit-phases.js +0 -20
- package/dist/workflows/schema.js +0 -4
- package/dist/workflows/source-ir/compile.js +0 -200
- package/dist/workflows/source-ir/program.js +0 -50
- package/dist/workflows/source-ir/result.js +0 -26
- package/dist/workflows/source-ir/schema.js +0 -786
- package/dist/workflows/source-ir/triggers.js +0 -79
- package/dist/workflows/source-ir/uses.js +0 -40
- package/dist/workflows/validator.js +0 -60
|
@@ -6,26 +6,29 @@ import { isVerbose, warn, warnVerbose } from "../core/warn.js";
|
|
|
6
6
|
import { embedBatch } from "../llm/embedder.js";
|
|
7
7
|
import { DETERMINISTIC_EMBED_MODEL_ID, isDeterministicEmbedEnabled } from "../llm/embedders/deterministic.js";
|
|
8
8
|
import { DEFAULT_LOCAL_MODEL } from "../llm/embedders/local.js";
|
|
9
|
-
import {
|
|
10
|
-
import {
|
|
11
|
-
import { purgeEmbeddingSalvage, relabelEmbeddingSalvageFingerprint, reuseSalvagedEmbeddings, } from "../storage/repositories/embedding-salvage-repository.js";
|
|
9
|
+
import { applyEmbeddingTemplate, documentTemplateFingerprint, resolveEmbeddingProfile } from "../llm/embedders/profile.js";
|
|
10
|
+
import { capEmbeddingText, DEFAULT_MAX_INPUT_TOKENS, describeEmbeddingCredential, estimateTokenCount, hasRemoteEndpoint, normalizeEmbeddingEndpoint, } from "../llm/embedders/remote.js";
|
|
12
11
|
import { getEmbeddableEntryCount } from "../storage/repositories/index-entries-repository.js";
|
|
13
|
-
import {
|
|
14
|
-
import {
|
|
15
|
-
import { getAllEntriesForEmbedding, getEmbeddingCount, isVecFastPathComplete, isVecFastPathReady, purgeEmbeddings, repairVecFastPath, sampleEmbeddedEntriesForCanary, setVecFastPathReady, upsertEmbedding, } from "../storage/repositories/index-vec-repository.js";
|
|
12
|
+
import { getMeta, setMeta } from "../storage/repositories/index-meta-repository.js";
|
|
13
|
+
import { getAllEntriesForEmbedding, getEmbeddingCount, purgeEmbeddings, upsertEmbedding, } from "../storage/repositories/index-vec-repository.js";
|
|
16
14
|
import { reclassifyIndexDbContention } from "./index-db-contention.js";
|
|
17
|
-
/**
|
|
15
|
+
/**
|
|
16
|
+
* Identifies what a stored vector was generated with: provider, model and
|
|
17
|
+
* dimension, plus the document template when the embedding profile has one
|
|
18
|
+
* (so changing it re-embeds the index).
|
|
19
|
+
*/
|
|
18
20
|
export function deriveSemanticProviderFingerprint(embedding) {
|
|
19
21
|
if (isDeterministicEmbedEnabled()) {
|
|
20
22
|
return `deterministic:${DETERMINISTIC_EMBED_MODEL_ID}`;
|
|
21
23
|
}
|
|
24
|
+
const template = documentTemplateFingerprint(embedding);
|
|
22
25
|
if (embedding?.endpoint) {
|
|
23
26
|
// Fingerprint keys on vector identity only (model + dimension). The endpoint
|
|
24
27
|
// is transport/routing and has no bearing on vector compatibility, so moving
|
|
25
|
-
// the same model+dimension to a different host must not force a
|
|
26
|
-
return `remote:${embedding.model}|${embedding.dimension ?? "default"}`;
|
|
28
|
+
// the same model+dimension to a different host must not force a re-embed.
|
|
29
|
+
return `remote:${embedding.model}|${embedding.dimension ?? "default"}${template}`;
|
|
27
30
|
}
|
|
28
|
-
return `local:${embedding?.localModel ?? DEFAULT_LOCAL_MODEL}`;
|
|
31
|
+
return `local:${embedding?.localModel ?? DEFAULT_LOCAL_MODEL}${template}`;
|
|
29
32
|
}
|
|
30
33
|
/**
|
|
31
34
|
* The heartbeat text emitted every 15s while a provider request is in
|
|
@@ -35,19 +38,6 @@ export function deriveSemanticProviderFingerprint(embedding) {
|
|
|
35
38
|
export function formatEmbeddingHeartbeat(storedCount, total, failedCount) {
|
|
36
39
|
return `Still generating embeddings: ${storedCount}/${total} stored, ${failedCount} failed; waiting on embedding provider.`;
|
|
37
40
|
}
|
|
38
|
-
/**
|
|
39
|
-
* Number of already-embedded entries sampled for the fingerprint-rename
|
|
40
|
-
* canary (#955) — small and cheap even against a slow local server; a
|
|
41
|
-
* handful of chunks is a strong compatibility signal (a different model
|
|
42
|
-
* cannot plausibly land near-identical vectors by chance).
|
|
43
|
-
*/
|
|
44
|
-
const CANARY_SAMPLE_SIZE = 8;
|
|
45
|
-
/**
|
|
46
|
-
* Minimum median cosine similarity between stored and freshly re-embedded
|
|
47
|
-
* canary vectors for a fingerprint-string change to be treated as a
|
|
48
|
-
* same-model rename rather than a real model change (#955).
|
|
49
|
-
*/
|
|
50
|
-
const CANARY_SIMILARITY_THRESHOLD = 0.999;
|
|
51
41
|
/**
|
|
52
42
|
* Consecutive transport failures after which the embedding pass stops
|
|
53
43
|
* dispatching further requests and ends the run as a failure rather than
|
|
@@ -65,152 +55,6 @@ const CANARY_SIMILARITY_THRESHOLD = 0.999;
|
|
|
65
55
|
* streaks instead.
|
|
66
56
|
*/
|
|
67
57
|
const CIRCUIT_BREAKER_THRESHOLD = 3;
|
|
68
|
-
/**
|
|
69
|
-
* Pure decision: do stored vectors remain valid against freshly re-embedded
|
|
70
|
-
* canary samples? The ONE place that computes the canary's similarity
|
|
71
|
-
* numbers — callers must use this result rather than recomputing it (#955).
|
|
72
|
-
*
|
|
73
|
-
* An empty sample means nothing is stored to lose or verify against, so
|
|
74
|
-
* there is nothing to decide — keep.
|
|
75
|
-
*
|
|
76
|
-
* A sample whose re-embed FAILED (`fresh === undefined`, e.g. a provider
|
|
77
|
-
* sub-batch that was skipped) is EXCLUDED from the similarity computation
|
|
78
|
-
* entirely, not scored as zero: a partial provider failure is not evidence
|
|
79
|
-
* of a different model (#955). A dimension mismatch on a successful
|
|
80
|
-
* re-embed still counts as zero similarity via {@link cosineSimilarity}'s
|
|
81
|
-
* own dimension-mismatch guard — that IS evidence. When half or fewer of
|
|
82
|
-
* the sampled entries re-embedded successfully, the sample is too thin to
|
|
83
|
-
* trust either verdict — the outcome is `unverifiable`, the same outcome a
|
|
84
|
-
* total canary failure already produces.
|
|
85
|
-
*
|
|
86
|
-
* Otherwise the MEDIAN pairwise cosine similarity of the verified samples
|
|
87
|
-
* must clear {@link CANARY_SIMILARITY_THRESHOLD}; the median (not the
|
|
88
|
-
* minimum or mean) tolerates one stale or lightly-edited sample without
|
|
89
|
-
* either discarding a real match or being fooled by it.
|
|
90
|
-
*/
|
|
91
|
-
export function decideEmbeddingCompatibility(pairs) {
|
|
92
|
-
if (pairs.length === 0)
|
|
93
|
-
return { outcome: "keep", medianSimilarity: undefined, verifiedSamples: 0 };
|
|
94
|
-
const verified = pairs.filter((pair) => pair.fresh !== undefined);
|
|
95
|
-
if (verified.length * 2 <= pairs.length) {
|
|
96
|
-
return { outcome: "unverifiable", medianSimilarity: undefined, verifiedSamples: verified.length };
|
|
97
|
-
}
|
|
98
|
-
const similarities = verified.map((pair) => cosineSimilarity(pair.stored, pair.fresh));
|
|
99
|
-
const medianSimilarity = medianOf(similarities);
|
|
100
|
-
return {
|
|
101
|
-
outcome: medianSimilarity >= CANARY_SIMILARITY_THRESHOLD ? "keep" : "rebuild",
|
|
102
|
-
medianSimilarity,
|
|
103
|
-
verifiedSamples: verified.length,
|
|
104
|
-
};
|
|
105
|
-
}
|
|
106
|
-
function medianOf(values) {
|
|
107
|
-
const sorted = [...values].sort((a, b) => a - b);
|
|
108
|
-
const mid = Math.floor(sorted.length / 2);
|
|
109
|
-
return sorted.length % 2 === 0
|
|
110
|
-
? (sorted[mid - 1] + sorted[mid]) / 2
|
|
111
|
-
: sorted[mid];
|
|
112
|
-
}
|
|
113
|
-
/**
|
|
114
|
-
* Identity of the embedding vectors actually observed on a run — as opposed
|
|
115
|
-
* to {@link deriveSemanticProviderFingerprint}'s CONFIG-derived string. Keys
|
|
116
|
-
* on what the server (or local model) actually reported plus the observed
|
|
117
|
-
* vector width, so a gateway/transport change that keeps returning the same
|
|
118
|
-
* underlying model can be told apart from a genuine model change without
|
|
119
|
-
* relying on the operator's config string (#955).
|
|
120
|
-
* Returns undefined when nothing was actually observed this call (no vector
|
|
121
|
-
* to measure yet).
|
|
122
|
-
*/
|
|
123
|
-
function deriveObservedEmbeddingIdentity(embedding, observedModel, observedVectorLen) {
|
|
124
|
-
if (isDeterministicEmbedEnabled()) {
|
|
125
|
-
return `deterministic:${DETERMINISTIC_EMBED_MODEL_ID}`;
|
|
126
|
-
}
|
|
127
|
-
if (observedVectorLen === undefined)
|
|
128
|
-
return undefined;
|
|
129
|
-
if (embedding?.endpoint) {
|
|
130
|
-
return `remote:${observedModel ?? embedding.model ?? "unknown"}|${observedVectorLen}`;
|
|
131
|
-
}
|
|
132
|
-
return `local:${embedding?.localModel ?? DEFAULT_LOCAL_MODEL}|${observedVectorLen}`;
|
|
133
|
-
}
|
|
134
|
-
/**
|
|
135
|
-
* Run the fingerprint-rename canary: re-embed a small sample of already-
|
|
136
|
-
* stored entries with the CURRENT config and decide whether the stored
|
|
137
|
-
* index survives. Goes through the standard {@link embedBatch} facade (not a
|
|
138
|
-
* direct `RemoteEmbedder`) so every embedder branch — remote, local,
|
|
139
|
-
* deterministic, and test overrides via `_setEmbedderForTests` — is
|
|
140
|
-
* exercised identically to the main embedding pass.
|
|
141
|
-
*
|
|
142
|
-
* `maxInputTokens` must be the SAME cap the main pass below applies via
|
|
143
|
-
* {@link capEmbeddingText} — the stored vector for each sampled entry was
|
|
144
|
-
* produced from its capped text, so comparing against a fresh vector of the
|
|
145
|
-
* uncapped text would compare unlike inputs for any entry over the cap
|
|
146
|
-
* (#955).
|
|
147
|
-
*/
|
|
148
|
-
async function runEmbeddingCanary(db, config, signal, maxInputTokens) {
|
|
149
|
-
const samples = sampleEmbeddedEntriesForCanary(db, CANARY_SAMPLE_SIZE);
|
|
150
|
-
if (samples.length === 0) {
|
|
151
|
-
return { outcome: "keep", verified: false, viaIdentityMatch: false };
|
|
152
|
-
}
|
|
153
|
-
let observedModel;
|
|
154
|
-
const skips = [];
|
|
155
|
-
let canaryVectors;
|
|
156
|
-
try {
|
|
157
|
-
canaryVectors = await embedBatch(
|
|
158
|
-
// #955: the stored vector for each sample was produced from
|
|
159
|
-
// capEmbeddingText(searchText, maxInputTokens) — the main pass below
|
|
160
|
-
// caps every document before embedding it. The canary must re-embed
|
|
161
|
-
// the SAME capped text, or an entry over the cap compares a fresh
|
|
162
|
-
// vector of a different input against a stored vector of the capped
|
|
163
|
-
// one, and a genuine model match can read as a rebuild-worthy
|
|
164
|
-
// mismatch for reasons unrelated to the model.
|
|
165
|
-
samples.map((sample) => capEmbeddingText(sample.searchText, maxInputTokens).text), config.embedding, signal, (skip) => skips.push(skip), (_indices, _embeddings, model) => {
|
|
166
|
-
if (model)
|
|
167
|
-
observedModel = model;
|
|
168
|
-
});
|
|
169
|
-
}
|
|
170
|
-
catch (error) {
|
|
171
|
-
const message = error instanceof Error ? error.message : String(error);
|
|
172
|
-
return {
|
|
173
|
-
outcome: "unverifiable",
|
|
174
|
-
message: `could not verify embedding compatibility (${message}); keeping existing vectors — rerun akm index when the endpoint is reachable`,
|
|
175
|
-
};
|
|
176
|
-
}
|
|
177
|
-
const observedVectorLen = canaryVectors.find((vector) => vector !== undefined)?.length;
|
|
178
|
-
const observedIdentity = deriveObservedEmbeddingIdentity(config.embedding, observedModel, observedVectorLen);
|
|
179
|
-
const storedIdentity = getMeta(db, "embeddingIdentity");
|
|
180
|
-
if (storedIdentity && observedIdentity && storedIdentity === observedIdentity) {
|
|
181
|
-
// The server reports the same model identity as last time — no need to
|
|
182
|
-
// even look at the cosines; the config string alone was misleading.
|
|
183
|
-
return { outcome: "keep", verified: true, identity: observedIdentity, viaIdentityMatch: true };
|
|
184
|
-
}
|
|
185
|
-
const pairs = samples.map((sample, i) => ({ stored: sample.vector, fresh: canaryVectors[i] }));
|
|
186
|
-
const decision = decideEmbeddingCompatibility(pairs);
|
|
187
|
-
if (decision.outcome === "unverifiable") {
|
|
188
|
-
// Covers both a total provider failure (RemoteEmbedder skips a failing
|
|
189
|
-
// request rather than throwing, #874, so an unreachable endpoint
|
|
190
|
-
// surfaces here as an all-`undefined` canary result, not a caught
|
|
191
|
-
// exception) and a partial one thin enough that neither verdict can be
|
|
192
|
-
// trusted (#955) — same message path either way.
|
|
193
|
-
const message = skips[0]?.message ?? "embedding provider returned no vectors for the canary sample";
|
|
194
|
-
return {
|
|
195
|
-
outcome: "unverifiable",
|
|
196
|
-
message: `could not verify embedding compatibility (${message}); keeping existing vectors — rerun akm index when the endpoint is reachable`,
|
|
197
|
-
};
|
|
198
|
-
}
|
|
199
|
-
if (decision.outcome === "keep") {
|
|
200
|
-
return {
|
|
201
|
-
outcome: "keep",
|
|
202
|
-
verified: true,
|
|
203
|
-
identity: observedIdentity,
|
|
204
|
-
viaIdentityMatch: false,
|
|
205
|
-
medianSimilarity: decision.medianSimilarity,
|
|
206
|
-
};
|
|
207
|
-
}
|
|
208
|
-
return {
|
|
209
|
-
outcome: "rebuild",
|
|
210
|
-
identity: observedIdentity,
|
|
211
|
-
reason: `vectors differ (median similarity ${decision.medianSimilarity?.toFixed(3)})`,
|
|
212
|
-
};
|
|
213
|
-
}
|
|
214
58
|
function throwIfAborted(signal) {
|
|
215
59
|
if (signal?.aborted) {
|
|
216
60
|
throw signal.reason instanceof Error ? signal.reason : new Error("index interrupted");
|
|
@@ -223,10 +67,9 @@ export async function generateEmbeddingsForDb(db, config, onProgress, signal, en
|
|
|
223
67
|
// unobservable SAVEPOINT instead, so an interruption (competing-process
|
|
224
68
|
// collision, SIGKILL) could lose the whole pass rather than only the batch
|
|
225
69
|
// in flight. This is an internal contract error (a caller bug), not a
|
|
226
|
-
// user-facing failure class:
|
|
227
|
-
//
|
|
228
|
-
//
|
|
229
|
-
// `runEmbeddingPass` in `src/indexer/indexer.ts`.
|
|
70
|
+
// user-facing failure class: a caller with its own transaction must run the
|
|
71
|
+
// embedding phase on a separate connection AFTER that transaction commits —
|
|
72
|
+
// see `runEmbeddingPass` in `src/indexer/indexer.ts`.
|
|
230
73
|
if (db.inTransaction) {
|
|
231
74
|
throw new Error("generateEmbeddingsForDb was called with an ambient transaction already open on `db`: per-batch commits " +
|
|
232
75
|
"would become SAVEPOINTs inside it, losing the crash-durability contract per-batch commit exists for. " +
|
|
@@ -234,11 +77,6 @@ export async function generateEmbeddingsForDb(db, config, onProgress, signal, en
|
|
|
234
77
|
}
|
|
235
78
|
throwIfAborted(signal);
|
|
236
79
|
if (config.semanticSearchMode === "off") {
|
|
237
|
-
// #955: salvage is self-emptying only if every path that skips reuse
|
|
238
|
-
// also drains it — otherwise a full rebuild performed with semantic
|
|
239
|
-
// search disabled leaves permanent orphaned rows behind (nothing will
|
|
240
|
-
// ever consume them, since this path never reaches the reuse step).
|
|
241
|
-
purgeEmbeddingSalvage(db);
|
|
242
80
|
onProgress({ phase: "embeddings", message: "Semantic search disabled; skipping embeddings." });
|
|
243
81
|
return { success: false, message: "Semantic search is disabled." };
|
|
244
82
|
}
|
|
@@ -246,8 +84,7 @@ export async function generateEmbeddingsForDb(db, config, onProgress, signal, en
|
|
|
246
84
|
// fix (every RemoteEmbedder path already resolves secret:// through one
|
|
247
85
|
// boundary — a keyless request can only mean embedding.apiKey was absent
|
|
248
86
|
// from the config THIS run loaded). One default-level line, before the
|
|
249
|
-
// first provider request of the phase
|
|
250
|
-
// pass, whichever runs first below), naming the endpoint/model/credential
|
|
87
|
+
// first provider request of the phase, naming the endpoint/model/credential
|
|
251
88
|
// SOURCE — never the credential value.
|
|
252
89
|
if (hasRemoteEndpoint(config.embedding ?? {})) {
|
|
253
90
|
const endpoint = normalizeEmbeddingEndpoint(config.embedding?.endpoint ?? "");
|
|
@@ -258,167 +95,56 @@ export async function generateEmbeddingsForDb(db, config, onProgress, signal, en
|
|
|
258
95
|
message: `[embed] endpoint ${endpoint}, model ${config.embedding?.model ?? "unknown"}; credential: ${credential}${configFileSuffix}`,
|
|
259
96
|
});
|
|
260
97
|
}
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
// healthy fast path healthy, but can never promote a generation already
|
|
264
|
-
// marked degraded. Global runs can afford to verify the entire derived set.
|
|
265
|
-
const vecFastPathWasReady = isVecFastPathReady(db);
|
|
266
|
-
const currentFingerprint = deriveSemanticProviderFingerprint(config.embedding);
|
|
267
|
-
const storedFingerprint = getMeta(db, "embeddingFingerprint");
|
|
98
|
+
const model = deriveSemanticProviderFingerprint(config.embedding);
|
|
99
|
+
const storedModel = getMeta(db, "embeddingFingerprint");
|
|
268
100
|
let targetEntryIds = entryIds;
|
|
269
|
-
/** Set only
|
|
101
|
+
/** Set only when every entry is (re)embedded, so the up-front "Re-embedding N entries" line names why. */
|
|
270
102
|
let rebuildReason;
|
|
271
|
-
// Resolved once and reused by both the canary (below) and the main pass's
|
|
272
|
-
// cap loop (further down) — the same cap must apply to both, or the canary
|
|
273
|
-
// compares a differently-capped text against the stored vector (#955).
|
|
274
103
|
const maxInputTokens = config.embedding?.maxInputTokens ?? DEFAULT_MAX_INPUT_TOKENS;
|
|
275
104
|
if (opts?.forceReembed) {
|
|
276
|
-
//
|
|
277
|
-
//
|
|
278
|
-
//
|
|
279
|
-
//
|
|
280
|
-
// then sees a matching fingerprint and only heals what is still missing
|
|
281
|
-
// instead of purging again from zero (#955/#956).
|
|
105
|
+
// The explicit operator override: purge and re-embed everything. The model
|
|
106
|
+
// is recorded in the SAME transaction as the purge, before any embedding
|
|
107
|
+
// request, so a restart heals only what is still missing instead of
|
|
108
|
+
// purging again from zero (#955/#956).
|
|
282
109
|
db.transaction(() => {
|
|
283
|
-
purgeEmbeddings(db
|
|
284
|
-
|
|
285
|
-
// quietly satisfy some of it from stale salvage.
|
|
286
|
-
purgeEmbeddingSalvage(db);
|
|
287
|
-
deleteMeta(db, "embeddingDim");
|
|
288
|
-
setMeta(db, "embeddingFingerprint", currentFingerprint);
|
|
289
|
-
deleteMeta(db, "embeddingIdentity");
|
|
110
|
+
purgeEmbeddings(db);
|
|
111
|
+
setMeta(db, "embeddingFingerprint", model);
|
|
290
112
|
})();
|
|
291
113
|
targetEntryIds = undefined;
|
|
292
114
|
rebuildReason = "forced by --reembed";
|
|
293
115
|
}
|
|
294
|
-
else if (
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
return { success: false, message: decision.message };
|
|
304
|
-
}
|
|
305
|
-
if (decision.outcome === "rebuild") {
|
|
306
|
-
db.transaction(() => {
|
|
307
|
-
purgeEmbeddings(db, { dropVecTable: true });
|
|
308
|
-
// #955: the stored vectors AND any leftover salvage both belong to
|
|
309
|
-
// a different model now — neither is reusable, so both go.
|
|
310
|
-
purgeEmbeddingSalvage(db);
|
|
311
|
-
deleteMeta(db, "embeddingDim");
|
|
312
|
-
setMeta(db, "embeddingFingerprint", currentFingerprint);
|
|
313
|
-
if (decision.identity)
|
|
314
|
-
setMeta(db, "embeddingIdentity", decision.identity);
|
|
315
|
-
else
|
|
316
|
-
deleteMeta(db, "embeddingIdentity");
|
|
317
|
-
})();
|
|
318
|
-
targetEntryIds = undefined;
|
|
319
|
-
rebuildReason = decision.reason;
|
|
320
|
-
}
|
|
321
|
-
else {
|
|
322
|
-
// Keep: adopt the new fingerprint (and identity, when observed)
|
|
323
|
-
// immediately rather than deferring to end-of-run — nothing was
|
|
324
|
-
// purged, so there is nothing an interruption could lose, and an
|
|
325
|
-
// immediate write means a crash right after this decision does not
|
|
326
|
-
// re-run the canary needlessly on the next attempt.
|
|
327
|
-
setMeta(db, "embeddingFingerprint", currentFingerprint);
|
|
328
|
-
// #955: the model did not actually change, only the fingerprint
|
|
329
|
-
// STRING did (e.g. a gateway rename) — any leftover salvage rows
|
|
330
|
-
// tagged with the OLD string are still valid vectors. Relabel them so
|
|
331
|
-
// the reuse step below (and any later pass) can still find them.
|
|
332
|
-
relabelEmbeddingSalvageFingerprint(db, storedFingerprint, currentFingerprint);
|
|
333
|
-
if (decision.identity)
|
|
334
|
-
setMeta(db, "embeddingIdentity", decision.identity);
|
|
335
|
-
if (decision.verified) {
|
|
336
|
-
const keptCount = getEmbeddingCount(db);
|
|
337
|
-
const detail = decision.viaIdentityMatch
|
|
338
|
-
? "server-reported model unchanged"
|
|
339
|
-
: `stored vectors are compatible (median similarity ${decision.medianSimilarity?.toFixed(3)})`;
|
|
340
|
-
const message = `[embed] embedding model renamed (${storedFingerprint} → ${currentFingerprint}); ${detail}, keeping ${keptCount} embedding${keptCount === 1 ? "" : "s"}.`;
|
|
341
|
-
warn(message);
|
|
342
|
-
onProgress({ phase: "embeddings", message });
|
|
343
|
-
}
|
|
344
|
-
// Empty-sample case (decision.verified === false): nothing stored to
|
|
345
|
-
// lose or verify against — adopt the label silently, no purge line.
|
|
346
|
-
}
|
|
116
|
+
else if (storedModel && storedModel !== model) {
|
|
117
|
+
// The configured model changed. Nothing is purged: each stored row keeps
|
|
118
|
+
// the model that produced it until the pass below replaces it, so an
|
|
119
|
+
// interrupted pass resumes with only the rows still on the old model.
|
|
120
|
+
// Readers serve the current model's rows only, so nothing mixes vectors
|
|
121
|
+
// from two models.
|
|
122
|
+
setMeta(db, "embeddingFingerprint", model);
|
|
123
|
+
targetEntryIds = undefined;
|
|
124
|
+
rebuildReason = `the embedding model changed (${storedModel} → ${model}); stored vectors are kept until each entry is re-embedded`;
|
|
347
125
|
}
|
|
348
126
|
else {
|
|
349
|
-
//
|
|
350
|
-
//
|
|
351
|
-
//
|
|
352
|
-
|
|
353
|
-
// (#955/#956). Without this, an interrupted FIRST-EVER pass left
|
|
354
|
-
// `embeddingFingerprint` unset despite a per-batch commit below (#954)
|
|
355
|
-
// already having durably written real vectors — a later `akm index
|
|
356
|
-
// --full`'s salvage-before-discard step tags rows by this meta
|
|
357
|
-
// (`salvageEmbeddingsBeforeDiscard`) and treats an unset fingerprint as
|
|
358
|
-
// "nothing was ever verified", silently turning genuinely-embedded
|
|
359
|
-
// vectors into a full re-embed instead of a salvage-and-reuse.
|
|
360
|
-
setMeta(db, "embeddingFingerprint", currentFingerprint);
|
|
127
|
+
// First pass ever for this index, or the model already matches — record
|
|
128
|
+
// it NOW rather than after a fully successful pass (#955/#956), so an
|
|
129
|
+
// interrupted first pass still labels the vectors it durably committed.
|
|
130
|
+
setMeta(db, "embeddingFingerprint", model);
|
|
361
131
|
}
|
|
362
132
|
try {
|
|
363
133
|
throwIfAborted(signal);
|
|
364
|
-
|
|
365
|
-
const storedDim = Number(getMeta(db, "embeddingDim"));
|
|
366
|
-
const expectedDim = Number.isInteger(storedDim) && storedDim > 0 ? storedDim : (config.embedding?.dimension ?? EMBEDDING_DIM);
|
|
367
|
-
const repair = repairVecFastPath(db, expectedDim);
|
|
368
|
-
if (repair.available &&
|
|
369
|
-
(repair.repaired > 0 || repair.removedOrphans > 0 || repair.rejected > 0 || repair.error !== undefined)) {
|
|
370
|
-
const detail = repair.error ? `; repair stopped: ${repair.error}` : "";
|
|
371
|
-
onProgress({
|
|
372
|
-
phase: "embeddings",
|
|
373
|
-
message: `[embed] Repaired ${repair.repaired} missing sqlite-vec row${repair.repaired === 1 ? "" : "s"}; removed ${repair.removedOrphans} orphan${repair.removedOrphans === 1 ? "" : "s"}; ${repair.rejected} rejected${detail}.`,
|
|
374
|
-
});
|
|
375
|
-
}
|
|
376
|
-
}
|
|
377
|
-
const allEntries = getAllEntriesForEmbedding(db, targetEntryIds);
|
|
378
|
-
let vecFailedCount = 0;
|
|
379
|
-
let vecUnavailableCount = 0;
|
|
380
|
-
// #955: before any provider call, hand back vectors salvaged from a
|
|
381
|
-
// full rebuild or a generation bump for entries whose search_text is
|
|
382
|
-
// byte-identical to what was salvaged under the SAME fingerprint — a
|
|
383
|
-
// fingerprint mismatch or a single-byte content change both correctly
|
|
384
|
-
// fall through to the provider below instead.
|
|
385
|
-
const { reusedCount, remaining: candidateEntries } = reuseSalvagedEmbeddings(db, allEntries, currentFingerprint, (entry, embedding) => {
|
|
386
|
-
const result = upsertEmbedding(db, entry.id, embedding);
|
|
387
|
-
if (result.vec === "failed")
|
|
388
|
-
vecFailedCount++;
|
|
389
|
-
if (result.vec === "unavailable")
|
|
390
|
-
vecUnavailableCount++;
|
|
391
|
-
return result.stored;
|
|
392
|
-
});
|
|
393
|
-
if (reusedCount > 0) {
|
|
394
|
-
onProgress({
|
|
395
|
-
phase: "embeddings",
|
|
396
|
-
message: `Reused ${reusedCount} embedding${reusedCount === 1 ? "" : "s"} from the previous generation; embedding ${candidateEntries.length} new.`,
|
|
397
|
-
});
|
|
398
|
-
}
|
|
134
|
+
const candidateEntries = getAllEntriesForEmbedding(db, targetEntryIds, model);
|
|
399
135
|
if (candidateEntries.length === 0) {
|
|
400
136
|
onProgress({ phase: "embeddings", message: "Embeddings already up to date." });
|
|
401
|
-
|
|
402
|
-
if (reusedCount > 0) {
|
|
403
|
-
const vecGenerationComplete = targetEntryIds === undefined ? isVecFastPathComplete(db) : vecFastPathWasReady;
|
|
404
|
-
setVecFastPathReady(db, vecFailedCount === 0 && vecUnavailableCount === 0 && vecGenerationComplete);
|
|
405
|
-
}
|
|
406
|
-
// A pass that completes (even one that did nothing but reuse) purges
|
|
407
|
-
// whatever is left — salvage is consumed by the NEXT pass, never kept
|
|
408
|
-
// around as a second cache.
|
|
409
|
-
purgeEmbeddingSalvage(db);
|
|
410
|
-
return reusedCount > 0 ? { success: true, vecInsertFailures: vecFailedCount } : { success: true };
|
|
137
|
+
return { success: true };
|
|
411
138
|
}
|
|
412
|
-
// Cap each document's embedded text at
|
|
413
|
-
//
|
|
414
|
-
//
|
|
415
|
-
//
|
|
416
|
-
//
|
|
417
|
-
// head is empty (the impossible case: nothing left to embed), never
|
|
418
|
-
// merely for being long.
|
|
139
|
+
// Cap each document's embedded text at embedding.maxInputTokens (default
|
|
140
|
+
// DEFAULT_MAX_INPUT_TOKENS) instead of ever failing a whole batch over one
|
|
141
|
+
// oversized entry — truncation keeps the head of the text, unicode-safe. A
|
|
142
|
+
// document is skipped only when its head is empty (the impossible case:
|
|
143
|
+
// nothing left to embed), never merely for being long.
|
|
419
144
|
let truncatedCount = 0;
|
|
420
145
|
const texts = [];
|
|
421
146
|
const pendingEntries = [];
|
|
147
|
+
const { documentTemplate } = resolveEmbeddingProfile(config.embedding);
|
|
422
148
|
for (const entry of candidateEntries) {
|
|
423
149
|
const capped = capEmbeddingText(entry.searchText, maxInputTokens);
|
|
424
150
|
if (capped.text.length === 0)
|
|
@@ -426,7 +152,7 @@ export async function generateEmbeddingsForDb(db, config, onProgress, signal, en
|
|
|
426
152
|
if (capped.truncated)
|
|
427
153
|
truncatedCount++;
|
|
428
154
|
pendingEntries.push(entry);
|
|
429
|
-
texts.push(capped.text);
|
|
155
|
+
texts.push(applyEmbeddingTemplate(documentTemplate, capped.text));
|
|
430
156
|
}
|
|
431
157
|
if (truncatedCount > 0) {
|
|
432
158
|
// Through onProgress ONLY, not warn() too — onProgress already reaches
|
|
@@ -446,38 +172,11 @@ export async function generateEmbeddingsForDb(db, config, onProgress, signal, en
|
|
|
446
172
|
phase: "embeddings",
|
|
447
173
|
message: `Generating embeddings for ${pendingEntries.length} entr${pendingEntries.length === 1 ? "y" : "ies"}.`,
|
|
448
174
|
});
|
|
175
|
+
// Per-document sizes; the provider's actual request grouping is reported
|
|
176
|
+
// per batch below (and each skipped document is listed at the end).
|
|
449
177
|
if (isVerbose()) {
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
// 100 that no longer matches what gets sent over the wire. Local runs
|
|
453
|
-
// don't batch by size at all (LocalEmbedder chunks by a fixed count
|
|
454
|
-
// for inference throughput only, never fails/skips), so there's
|
|
455
|
-
// nothing meaningful to report per-batch for them.
|
|
456
|
-
if (hasRemoteEndpoint(config.embedding ?? {})) {
|
|
457
|
-
// Mirrors RemoteEmbedder.embedBatch's own tokenBudget resolution
|
|
458
|
-
// (#956: contextLength no longer feeds this).
|
|
459
|
-
const tokenBudget = config.embedding?.maxTokens ?? DEFAULT_TOKEN_BUDGET;
|
|
460
|
-
const maxCount = config.embedding?.batchSize ?? DEFAULT_REMOTE_BATCH_SIZE;
|
|
461
|
-
const batches = buildTokenBoundedBatches(texts, tokenBudget, maxCount);
|
|
462
|
-
const batchNumberByIndex = new Map();
|
|
463
|
-
batches.forEach((batch, batchIdx) => {
|
|
464
|
-
for (const i of batch.indices)
|
|
465
|
-
batchNumberByIndex.set(i, batchIdx + 1);
|
|
466
|
-
});
|
|
467
|
-
for (const [i, entry] of pendingEntries.entries()) {
|
|
468
|
-
const chars = entry.searchText.length;
|
|
469
|
-
const tokens = estimateTokenCount(entry.searchText);
|
|
470
|
-
const batch = batches[batchNumberByIndex.get(i) - 1];
|
|
471
|
-
const label = batch?.oversized
|
|
472
|
-
? "oversized (skipped)"
|
|
473
|
-
: `batch ${batchNumberByIndex.get(i)}/${batches.length}`;
|
|
474
|
-
warnVerbose(`[embed] ${entry.itemRef} (${chars} chars, est. ${tokens} tokens) → ${label}`);
|
|
475
|
-
}
|
|
476
|
-
}
|
|
477
|
-
else {
|
|
478
|
-
for (const entry of pendingEntries) {
|
|
479
|
-
warnVerbose(`[embed] ${entry.itemRef} (${entry.searchText.length} chars, est. ${estimateTokenCount(entry.searchText)} tokens)`);
|
|
480
|
-
}
|
|
178
|
+
for (const entry of pendingEntries) {
|
|
179
|
+
warnVerbose(`[embed] ${entry.itemRef} (${entry.searchText.length} chars, est. ${estimateTokenCount(entry.searchText)} tokens)`);
|
|
481
180
|
}
|
|
482
181
|
}
|
|
483
182
|
let heartbeatTimer;
|
|
@@ -538,17 +237,11 @@ export async function generateEmbeddingsForDb(db, config, onProgress, signal, en
|
|
|
538
237
|
// the very end (#954) — a competing-process lock error or any other
|
|
539
238
|
// interruption partway through now keeps whatever already committed
|
|
540
239
|
// instead of losing the entire pass.
|
|
541
|
-
// Tracks what this run actually observed, so a successful pass can
|
|
542
|
-
// record `embeddingIdentity` from real data rather than the config
|
|
543
|
-
// string alone (#955) — only the first non-empty batch's vector width
|
|
544
|
-
// is kept; every batch from one run shares the same provider/model.
|
|
545
|
-
let observedModel;
|
|
546
|
-
let observedVectorLen;
|
|
547
240
|
// Whether the remote provider's endpoint/model/token language is
|
|
548
241
|
// meaningful for this run — the per-batch diagnostic line below is
|
|
549
242
|
// remote-only, same gate the credential diagnostic (#953) above uses.
|
|
550
243
|
const reportPerBatchLine = hasRemoteEndpoint(config.embedding ?? {});
|
|
551
|
-
const onBatch = (indices, batchEmbeddings,
|
|
244
|
+
const onBatch = (indices, batchEmbeddings, outcome) => {
|
|
552
245
|
// #954 field-report follow-up: a "retrying" event carries nothing to
|
|
553
246
|
// commit — the request hasn't settled yet — only the notice that a
|
|
554
247
|
// back-off is about to be waited out, default-level so a run is
|
|
@@ -579,8 +272,6 @@ export async function generateEmbeddingsForDb(db, config, onProgress, signal, en
|
|
|
579
272
|
}
|
|
580
273
|
return;
|
|
581
274
|
}
|
|
582
|
-
if (model)
|
|
583
|
-
observedModel = model;
|
|
584
275
|
// A batch that delivered at least one real embedding proves the
|
|
585
276
|
// provider is currently answering — reset both circuit-breaker
|
|
586
277
|
// streaks. (A wholly failed batch's `batchEmbeddings` are all
|
|
@@ -601,10 +292,7 @@ export async function generateEmbeddingsForDb(db, config, onProgress, signal, en
|
|
|
601
292
|
embedFailedCount++;
|
|
602
293
|
continue;
|
|
603
294
|
}
|
|
604
|
-
if (
|
|
605
|
-
observedVectorLen = embedding.length;
|
|
606
|
-
const result = upsertEmbedding(db, entry.id, embedding);
|
|
607
|
-
if (result.stored) {
|
|
295
|
+
if (upsertEmbedding(db, entry.id, embedding, model)) {
|
|
608
296
|
storedCount++;
|
|
609
297
|
// #954: sum the estimate of the text actually sent —
|
|
610
298
|
// `texts[index]` is the capped string `embedBatch` was handed,
|
|
@@ -617,10 +305,6 @@ export async function generateEmbeddingsForDb(db, config, onProgress, signal, en
|
|
|
617
305
|
else {
|
|
618
306
|
skippedCount++;
|
|
619
307
|
}
|
|
620
|
-
if (result.vec === "failed")
|
|
621
|
-
vecFailedCount++;
|
|
622
|
-
if (result.vec === "unavailable")
|
|
623
|
-
vecUnavailableCount++;
|
|
624
308
|
}
|
|
625
309
|
})();
|
|
626
310
|
// Default level, one line per provider batch (#954, field-report
|
|
@@ -653,16 +337,8 @@ export async function generateEmbeddingsForDb(db, config, onProgress, signal, en
|
|
|
653
337
|
if (skippedCount > 0) {
|
|
654
338
|
warn(`[embed] ${skippedCount} embedding${skippedCount === 1 ? "" : "s"} skipped (entry deleted between queue and write)`);
|
|
655
339
|
}
|
|
656
|
-
const vecGenerationComplete = targetEntryIds === undefined ? isVecFastPathComplete(db) : vecFastPathWasReady;
|
|
657
|
-
setVecFastPathReady(db, vecFailedCount === 0 && vecUnavailableCount === 0 && vecGenerationComplete);
|
|
658
|
-
if (vecFailedCount > 0) {
|
|
659
|
-
warn(`[embed] ${vecFailedCount} sqlite-vec fast-path insert${vecFailedCount === 1 ? "" : "s"} failed — ` +
|
|
660
|
-
"semantic search will use the slower JS-cosine fallback over stored embeddings. " +
|
|
661
|
-
"Rebuild with 'akm index --full' after resolving the vec table (often a vector-dimension mismatch).");
|
|
662
|
-
}
|
|
663
340
|
const entriesPerSec = storedCount / elapsedSeconds;
|
|
664
341
|
const tokensPerSec = storedTokens / elapsedSeconds;
|
|
665
|
-
const totalStored = storedCount + reusedCount;
|
|
666
342
|
// #954, field-report follow-up: the final line
|
|
667
343
|
// reports every outcome, not just what was stored — counts come from
|
|
668
344
|
// the same collected `skips` the circuit breaker already uses,
|
|
@@ -675,16 +351,11 @@ export async function generateEmbeddingsForDb(db, config, onProgress, signal, en
|
|
|
675
351
|
const oversizedSkips = skips.filter((skip) => skip.reason === "context-window-exceeded");
|
|
676
352
|
const timedOutSkips = skips.filter((skip) => skip.reason === "batch-request-failed" && skip.failureKind === "timeout");
|
|
677
353
|
const failedSkips = skips.filter((skip) => skip.reason === "batch-request-failed" && skip.failureKind !== "timeout");
|
|
678
|
-
const throughputLine = reusedCount > 0
|
|
679
|
-
? // #955: report reused and newly-embedded counts separately — the
|
|
680
|
-
// rate figures below are provider throughput only (reuse is a
|
|
681
|
-
// plain DB write, not provider work) and would be misleadingly
|
|
682
|
-
// inflated if reused entries were folded into them.
|
|
683
|
-
`Stored ${totalStored} embedding${totalStored === 1 ? "" : "s"} (${reusedCount} reused, ${storedCount} newly embedded) in ${elapsedSeconds.toFixed(1)}s (${entriesPerSec.toFixed(1)} entries/s, ~${Math.round(tokensPerSec)} tokens/s)`
|
|
684
|
-
: `Stored ${storedCount} embedding${storedCount === 1 ? "" : "s"} in ${elapsedSeconds.toFixed(1)}s (${entriesPerSec.toFixed(1)} entries/s, ~${Math.round(tokensPerSec)} tokens/s)`;
|
|
685
354
|
onProgress({
|
|
686
355
|
phase: "embeddings",
|
|
687
|
-
message:
|
|
356
|
+
message: `Stored ${storedCount} embedding${storedCount === 1 ? "" : "s"} in ${elapsedSeconds.toFixed(1)}s ` +
|
|
357
|
+
`(${entriesPerSec.toFixed(1)} entries/s, ~${Math.round(tokensPerSec)} tokens/s); ` +
|
|
358
|
+
`${oversizedSkips.length} oversized skipped, ${timedOutSkips.length} timed out, ${failedSkips.length} failed.`,
|
|
688
359
|
});
|
|
689
360
|
// Bounded itemRef-level detail for every skip category, not just
|
|
690
361
|
// oversized — the aggregate counts above say HOW MANY documents timed
|
|
@@ -707,10 +378,6 @@ export async function generateEmbeddingsForDb(db, config, onProgress, signal, en
|
|
|
707
378
|
printSkipList("oversized documents", oversizedSkips);
|
|
708
379
|
printSkipList("timed-out documents", timedOutSkips);
|
|
709
380
|
printSkipList("failed documents", failedSkips);
|
|
710
|
-
setMeta(db, "embeddingFingerprint", currentFingerprint);
|
|
711
|
-
const observedIdentity = deriveObservedEmbeddingIdentity(config.embedding, observedModel, observedVectorLen);
|
|
712
|
-
if (observedIdentity)
|
|
713
|
-
setMeta(db, "embeddingIdentity", observedIdentity);
|
|
714
381
|
// Circuit breaker tripped (#954): committed batches are
|
|
715
382
|
// kept (nothing above discards them), but the pass is not a success —
|
|
716
383
|
// the provider looks dead, not just occasionally flaky.
|
|
@@ -720,7 +387,7 @@ export async function generateEmbeddingsForDb(db, config, onProgress, signal, en
|
|
|
720
387
|
"were stored — rerun akm index when the endpoint is healthy";
|
|
721
388
|
warn(`[embed] ${message}`);
|
|
722
389
|
onProgress({ phase: "embeddings", message });
|
|
723
|
-
return { success: false, message
|
|
390
|
+
return { success: false, message };
|
|
724
391
|
}
|
|
725
392
|
// Only a total failure (nothing at all embedded, despite having entries
|
|
726
393
|
// to embed) turns into a phase failure. Any partial success — the vast
|
|
@@ -735,11 +402,7 @@ export async function generateEmbeddingsForDb(db, config, onProgress, signal, en
|
|
|
735
402
|
message: `All ${embedFailedCount} embedding batch(es) failed: ${firstMessage}`,
|
|
736
403
|
};
|
|
737
404
|
}
|
|
738
|
-
|
|
739
|
-
// whatever salvage is left — consumed by this pass's reuse step
|
|
740
|
-
// above, or superseded by what it just embedded.
|
|
741
|
-
purgeEmbeddingSalvage(db);
|
|
742
|
-
return { success: true, vecInsertFailures: vecFailedCount };
|
|
405
|
+
return { success: true };
|
|
743
406
|
}
|
|
744
407
|
finally {
|
|
745
408
|
if (heartbeatTimer)
|
|
@@ -779,7 +442,7 @@ export function publishTargetedEmbeddingMeta(db, config) {
|
|
|
779
442
|
return;
|
|
780
443
|
}
|
|
781
444
|
const entryCount = getEmbeddableEntryCount(db);
|
|
782
|
-
const embeddingCount = getEmbeddingCount(db);
|
|
445
|
+
const embeddingCount = getEmbeddingCount(db, deriveSemanticProviderFingerprint(config.embedding));
|
|
783
446
|
const ready = entryCount > 0 && embeddingCount >= entryCount;
|
|
784
447
|
setMeta(db, "hasEmbeddings", ready ? "1" : "0");
|
|
785
448
|
}
|