akm-cli 0.9.17-alpha.2 → 0.9.17-alpha.4
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 +756 -0
- package/dist/akm +94 -196
- package/dist/cli/shared.js +6 -2
- package/dist/cli.js +22 -9
- 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 +2 -2
- package/dist/commands/health/checks.js +30 -75
- package/dist/commands/health/config-skew.js +38 -0
- package/dist/commands/health/egress.js +54 -0
- package/dist/commands/health/html-report.js +0 -38
- package/dist/commands/health/improve-metrics.js +123 -562
- 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 +11 -106
- package/dist/commands/health/types-improve.js +4 -19
- package/dist/commands/health/windows.js +64 -73
- package/dist/commands/health.js +122 -143
- package/dist/commands/improve/consolidate/chunking.js +25 -100
- package/dist/commands/improve/consolidate/sanitize.js +54 -149
- package/dist/commands/improve/consolidate.js +538 -1075
- 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 -1075
- package/dist/commands/improve/eligibility.js +126 -400
- package/dist/commands/improve/execution.js +3 -5
- package/dist/commands/improve/extract.js +487 -1046
- package/dist/commands/improve/feedback-valence.js +0 -25
- package/dist/commands/improve/improve-cli.js +29 -166
- package/dist/commands/improve/improve-result-file.js +10 -66
- package/dist/commands/improve/improve-strategies.js +12 -7
- package/dist/commands/improve/improve-usage-report.js +18 -64
- package/dist/commands/improve/improve.js +443 -1063
- package/dist/commands/improve/ledger.js +114 -0
- package/dist/commands/improve/locks.js +2 -8
- package/dist/commands/improve/loop-stages.js +459 -1172
- package/dist/commands/improve/memory/derived-ref.js +12 -77
- package/dist/commands/improve/memory/memory-belief.js +14 -118
- package/dist/commands/improve/memory/memory-improve.js +4 -3
- package/dist/commands/improve/outcome-loop.js +28 -156
- package/dist/commands/improve/planner.js +5 -10
- package/dist/commands/improve/preparation.js +851 -2339
- package/dist/commands/improve/proactive-maintenance.js +34 -101
- package/dist/commands/improve/reflect-noise.js +104 -280
- package/dist/commands/improve/reflect.js +621 -1367
- package/dist/commands/improve/salience.js +46 -232
- package/dist/commands/improve/session-asset.js +19 -100
- package/dist/commands/improve/stage.js +323 -0
- package/dist/commands/proposal/drain.js +251 -644
- package/dist/commands/proposal/proposal-cli.js +3 -18
- package/dist/commands/proposal/proposal-types.js +20 -41
- package/dist/commands/proposal/proposal.js +1 -2
- package/dist/commands/proposal/propose.js +134 -160
- package/dist/commands/proposal/repository.js +502 -1487
- 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 +13 -89
- package/dist/commands/read/curate.js +63 -413
- package/dist/commands/read/search-cli.js +16 -33
- package/dist/commands/read/search.js +17 -23
- package/dist/commands/read/show.js +2 -13
- package/dist/commands/sources/bundle-cli.js +25 -2
- package/dist/commands/sources/bundle-config-ops.js +7 -0
- package/dist/commands/sources/dangerous-env-audit.js +1 -2
- package/dist/commands/sources/info.js +2 -11
- 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/stash-cli.js +1 -1
- package/dist/commands/tasks/explain.js +10 -13
- package/dist/commands/tasks/tasks-cli.js +9 -8
- package/dist/commands/tasks/tasks.js +326 -930
- package/dist/commands/tasks/validate.js +42 -21
- package/dist/commands/workflow/plan.js +22 -29
- package/dist/commands/workflow-cli.js +4 -4
- package/dist/core/adapter/adapters/akm-adapter.js +0 -1
- package/dist/core/adapter/adapters/akm-lint.js +2 -3
- package/dist/core/adapter/adapters/akm-metadata.js +11 -12
- package/dist/core/adapter/adapters/akm-workflow-adapter.js +1 -1
- package/dist/core/adapter/execution-source.js +17 -29
- package/dist/core/asset/resolve-ref.js +1 -1
- package/dist/core/bundle-id.js +42 -5
- package/dist/core/bundle-rename.js +291 -0
- package/dist/core/config/config-io.js +1 -2
- package/dist/core/config/config-schema.js +1 -33
- package/dist/core/config/config-walker.js +1 -1
- package/dist/core/config/config.js +163 -68
- 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 +21 -95
- package/dist/core/config/schema/improve.js +4 -42
- 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-lock.js +76 -173
- package/dist/core/logs-db.js +2 -2
- package/dist/core/paths.js +0 -27
- 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 +108 -61
- package/dist/core/state-db-scope.js +2 -4
- package/dist/core/state-db.js +126 -692
- package/dist/core/type-presentation.js +1 -9
- package/dist/core/write-source.js +293 -1012
- 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/graph-db.js +106 -46
- package/dist/indexer/ensure-index.js +44 -85
- package/dist/indexer/graph/graph-extraction.js +340 -562
- package/dist/indexer/graph/graph-related.js +130 -0
- 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 +139 -151
- package/dist/indexer/indexer.js +524 -846
- package/dist/indexer/materialize-embeddings.js +60 -397
- package/dist/indexer/passes/memory-inference.js +81 -90
- package/dist/indexer/passes/metadata.js +132 -200
- package/dist/indexer/read-preflight.js +0 -7
- package/dist/indexer/scan/doc-to-entry.js +1 -3
- package/dist/indexer/scan/drain-dir.js +1 -1
- package/dist/indexer/search/db-search.js +181 -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 +2 -7
- 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/embedders/deterministic.js +2 -3
- package/dist/llm/embedders/profile.js +71 -0
- package/dist/llm/embedders/remote.js +10 -15
- package/dist/llm/graph-extract.js +3 -12
- package/dist/llm/index-passes.js +3 -5
- package/dist/llm/memory-infer.js +1 -2
- package/dist/llm/metadata-enhance.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 +2 -34
- package/dist/output/shapes/passthrough.js +1 -9
- package/dist/{indexer/search/ranking-types.js → output/text/bundle-rename.js} +4 -1
- package/dist/output/text/command-format.js +60 -23
- package/dist/output/text/helpers.js +1 -1
- package/dist/output/text/migrate.js +5 -14
- package/dist/output/text/proposal-format.js +1 -2
- 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 +43937 -93313
- package/dist/scripts/akm-migrate.js +43697 -93071
- 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/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 +0 -17
- 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 +168 -0
- package/dist/storage/repositories/index-connection.js +34 -70
- package/dist/storage/repositories/index-entries-repository.js +69 -111
- package/dist/storage/repositories/index-entry-mapper.js +1 -2
- package/dist/storage/repositories/index-entry-schema.js +83 -269
- package/dist/storage/repositories/index-fts-repository.js +86 -256
- package/dist/storage/repositories/index-llm-cache-repository.js +17 -0
- package/dist/storage/repositories/index-meta-repository.js +6 -4
- package/dist/storage/repositories/index-schema.js +192 -220
- 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 +35 -0
- package/dist/storage/repositories/registry-index-cache-repository.js +100 -0
- 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 +34 -27
- package/dist/tasks/activation-config.js +134 -62
- package/dist/tasks/backends/cron.js +129 -277
- package/dist/tasks/backends/exec-utils.js +2 -5
- package/dist/tasks/backends/launchd.js +125 -745
- package/dist/tasks/backends/schtasks.js +101 -620
- 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/scheduler-binding.js +18 -238
- package/dist/tasks/scheduler-invocation.js +52 -52
- package/dist/tasks/scheduler-lock.js +53 -0
- package/dist/tasks/scheduler-sync.js +363 -679
- package/dist/tasks/source/parse-task-source.js +160 -10
- package/dist/tasks/source/task-source-v3-frozen.js +3 -4
- package/dist/tasks/source/task-to-v4.js +2 -2
- 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 +41 -0
- package/docs/migration/v0.9.1-to-v0.9.2.md +19 -7
- package/docs/reference/cli.md +182 -125
- package/docs/reference/configuration.md +49 -56
- package/docs/reference/data-and-telemetry.md +19 -20
- package/docs/reference/tasks.md +86 -38
- 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 +87 -406
- 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 -146
- package/dist/commands/improve/distill/promote-memory.js +0 -329
- package/dist/commands/improve/distill/quality-gate.js +0 -500
- 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 -21
- 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/config/retired-experimental-keys-shim.js +0 -62
- 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/graph/graph-boost.js +0 -427
- package/dist/indexer/graph/graph-dedup.js +0 -95
- package/dist/indexer/search/name-match.js +0 -35
- package/dist/indexer/search/ranking-contributors.js +0 -515
- 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/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/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 -78
- 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
|
@@ -1,465 +1,184 @@
|
|
|
1
1
|
// This Source Code Form is subject to the terms of the Mozilla Public
|
|
2
2
|
// License, v. 2.0. If a copy of the MPL was not distributed with this
|
|
3
3
|
// file, You can obtain one at https://mozilla.org/MPL/2.0/.
|
|
4
|
-
|
|
5
|
-
* `index.db` vector + embedding repository.
|
|
6
|
-
*
|
|
7
|
-
* Owns the sqlite-vec extension load/availability probe, the BLOB `embeddings`
|
|
8
|
-
* table, the `entries_vec` virtual table, and the JS-cosine fallback path.
|
|
9
|
-
*/
|
|
10
|
-
import { createRequire } from "node:module";
|
|
11
|
-
import { bestEffort } from "../../core/best-effort.js";
|
|
12
|
-
import { warn } from "../../core/warn.js";
|
|
13
|
-
import { cosineSimilarity } from "../../llm/embedders/types.js";
|
|
4
|
+
import { buildSearchText } from "../../indexer/search/search-fields.js";
|
|
14
5
|
import { getMeta, setMeta } from "./index-meta-repository.js";
|
|
15
6
|
import { SQLITE_CHUNK_SIZE } from "./index-sql.js";
|
|
16
|
-
// ── sqlite-vec extension ────────────────────────────────────────────────────
|
|
17
|
-
const vecStatus = new WeakMap();
|
|
18
|
-
/**
|
|
19
|
-
* Attempt to load the sqlite-vec extension into `db`, recording availability.
|
|
20
|
-
* Exported so the connection lifecycle can arm it at open time.
|
|
21
|
-
*/
|
|
22
|
-
export function loadVecExtension(db) {
|
|
23
|
-
try {
|
|
24
|
-
const esmRequire = createRequire(import.meta.url);
|
|
25
|
-
const sqliteVec = esmRequire("sqlite-vec");
|
|
26
|
-
// `db` is the storage boundary's handle. On Bun that IS the bun:sqlite
|
|
27
|
-
// handle; on Node it is a wrapper, which must forward `loadExtension` for
|
|
28
|
-
// this call to work at all (see openNodeDatabase in storage/database.ts —
|
|
29
|
-
// it did not, so vec could never load on the entire npm distribution).
|
|
30
|
-
sqliteVec.load(db);
|
|
31
|
-
vecStatus.set(db, true);
|
|
32
|
-
}
|
|
33
|
-
catch {
|
|
34
|
-
vecStatus.set(db, false);
|
|
35
|
-
}
|
|
36
|
-
}
|
|
37
|
-
export function isVecAvailable(db) {
|
|
38
|
-
return vecStatus.get(db) ?? false;
|
|
39
|
-
}
|
|
40
7
|
/**
|
|
41
|
-
*
|
|
42
|
-
*
|
|
43
|
-
*
|
|
44
|
-
* while their BLOB rows still wrote — so semantic search reads the complete
|
|
45
|
-
* BLOB table via the JS-cosine fallback instead of a partial/mismatched vec
|
|
46
|
-
* table. Absent (legacy indexes) and "1" both mean the fast path is trusted.
|
|
8
|
+
* The embedding model whose vectors this index currently serves: the provider
|
|
9
|
+
* fingerprint the last embedding pass targeted (`index_meta.embeddingFingerprint`).
|
|
10
|
+
* `undefined` on an index that has never run an embedding pass.
|
|
47
11
|
*/
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
export function setVecFastPathReady(db, ready) {
|
|
51
|
-
setMeta(db, VEC_FAST_PATH_READY_META, ready ? "1" : "0");
|
|
12
|
+
function currentEmbeddingModel(db) {
|
|
13
|
+
return getMeta(db, "embeddingFingerprint");
|
|
52
14
|
}
|
|
15
|
+
const modelColumnPresent = new WeakMap();
|
|
53
16
|
/**
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
*
|
|
57
|
-
*
|
|
17
|
+
* Whether `embeddings` carries the per-row `model` column. A read-only open of
|
|
18
|
+
* an index the writable opener has not migrated yet does not; its rows are
|
|
19
|
+
* then all served as the current model. Only a positive answer is memoized:
|
|
20
|
+
* the column can appear on a live connection (`ensureSchema`), never vanish.
|
|
58
21
|
*/
|
|
59
|
-
|
|
60
|
-
if (
|
|
61
|
-
return false;
|
|
62
|
-
// The meta flag alone is not sufficient. An index built while sqlite-vec was
|
|
63
|
-
// unavailable wrote only BLOB rows, and because "unavailable" outcomes were
|
|
64
|
-
// not counted as failures the flag was still set to "1" against a table that
|
|
65
|
-
// is empty or absent. If sqlite-vec later becomes loadable — the user installs
|
|
66
|
-
// it, or the same index is opened under the other runtime — the fast path
|
|
67
|
-
// would then be trusted and return zero neighbours while the BLOB table holds
|
|
68
|
-
// every embedding. Indexes written by earlier versions still carry that stale
|
|
69
|
-
// flag, so the read path has to verify the table really exists.
|
|
70
|
-
return hasVecTable(db);
|
|
71
|
-
}
|
|
72
|
-
/**
|
|
73
|
-
* Verify that the vec fast-path table mirrors the complete durable BLOB set.
|
|
74
|
-
*
|
|
75
|
-
* A targeted embedding write preserves the prior readiness decision because
|
|
76
|
-
* its subset cannot prove an older degraded generation is healed. Global
|
|
77
|
-
* materialization uses this aggregate check before promoting the persisted
|
|
78
|
-
* flag; search itself still reads the cheap flag and does not repeat the check
|
|
79
|
-
* per query.
|
|
80
|
-
*/
|
|
81
|
-
export function isVecFastPathComplete(db) {
|
|
82
|
-
if (!isVecAvailable(db) || !hasVecTable(db))
|
|
83
|
-
return false;
|
|
84
|
-
try {
|
|
85
|
-
const missingVecRows = db
|
|
86
|
-
.prepare(`
|
|
87
|
-
SELECT id FROM embeddings
|
|
88
|
-
EXCEPT
|
|
89
|
-
SELECT id FROM entries_vec
|
|
90
|
-
LIMIT 1
|
|
91
|
-
`)
|
|
92
|
-
.all();
|
|
93
|
-
if (missingVecRows.length > 0)
|
|
94
|
-
return false;
|
|
95
|
-
const orphanVecRows = db
|
|
96
|
-
.prepare(`
|
|
97
|
-
SELECT id FROM entries_vec
|
|
98
|
-
EXCEPT
|
|
99
|
-
SELECT id FROM embeddings
|
|
100
|
-
LIMIT 1
|
|
101
|
-
`)
|
|
102
|
-
.all();
|
|
103
|
-
return orphanVecRows.length === 0;
|
|
104
|
-
}
|
|
105
|
-
catch {
|
|
106
|
-
return false;
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
/**
|
|
110
|
-
* Reconcile sqlite-vec's derived mirror from the durable BLOB embeddings.
|
|
111
|
-
*
|
|
112
|
-
* This never calls an embedding provider and never mutates the BLOB table.
|
|
113
|
-
* The readiness flag is lowered before the first mutation and is promoted only
|
|
114
|
-
* after a bidirectional aggregate check proves both ID sets match exactly.
|
|
115
|
-
*/
|
|
116
|
-
export function repairVecFastPath(db, embeddingDim) {
|
|
117
|
-
let repaired = 0;
|
|
118
|
-
let removedOrphans = 0;
|
|
119
|
-
let rejected = 0;
|
|
120
|
-
setVecFastPathReady(db, false);
|
|
121
|
-
if (!isVecAvailable(db) || !hasVecTable(db)) {
|
|
122
|
-
return { available: false, repaired, removedOrphans, rejected, complete: false };
|
|
123
|
-
}
|
|
124
|
-
if (!Number.isInteger(embeddingDim) || embeddingDim <= 0) {
|
|
125
|
-
return {
|
|
126
|
-
available: true,
|
|
127
|
-
repaired,
|
|
128
|
-
removedOrphans,
|
|
129
|
-
rejected,
|
|
130
|
-
complete: false,
|
|
131
|
-
error: `Invalid embedding dimension ${embeddingDim}.`,
|
|
132
|
-
};
|
|
133
|
-
}
|
|
134
|
-
try {
|
|
135
|
-
while (true) {
|
|
136
|
-
const orphanIds = db
|
|
137
|
-
.prepare(`
|
|
138
|
-
SELECT id FROM entries_vec
|
|
139
|
-
EXCEPT
|
|
140
|
-
SELECT id FROM embeddings
|
|
141
|
-
ORDER BY id
|
|
142
|
-
LIMIT ?
|
|
143
|
-
`)
|
|
144
|
-
.all(SQLITE_CHUNK_SIZE);
|
|
145
|
-
if (orphanIds.length === 0)
|
|
146
|
-
break;
|
|
147
|
-
db.transaction(() => {
|
|
148
|
-
const remove = db.prepare("DELETE FROM entries_vec WHERE id = ?");
|
|
149
|
-
for (const { id } of orphanIds) {
|
|
150
|
-
remove.run(id);
|
|
151
|
-
removedOrphans++;
|
|
152
|
-
}
|
|
153
|
-
})();
|
|
154
|
-
}
|
|
155
|
-
let afterId = -1;
|
|
156
|
-
while (true) {
|
|
157
|
-
const missingIds = db
|
|
158
|
-
.prepare(`
|
|
159
|
-
SELECT id FROM (
|
|
160
|
-
SELECT id FROM embeddings
|
|
161
|
-
EXCEPT
|
|
162
|
-
SELECT id FROM entries_vec
|
|
163
|
-
) AS missing
|
|
164
|
-
WHERE id > ?
|
|
165
|
-
ORDER BY id
|
|
166
|
-
LIMIT ?
|
|
167
|
-
`)
|
|
168
|
-
.all(afterId, SQLITE_CHUNK_SIZE);
|
|
169
|
-
if (missingIds.length === 0)
|
|
170
|
-
break;
|
|
171
|
-
afterId = missingIds[missingIds.length - 1].id;
|
|
172
|
-
const placeholders = missingIds.map(() => "?").join(",");
|
|
173
|
-
const rows = db
|
|
174
|
-
.prepare(`SELECT id, embedding FROM embeddings WHERE id IN (${placeholders}) ORDER BY id`)
|
|
175
|
-
.all(...missingIds.map(({ id }) => id));
|
|
176
|
-
db.transaction(() => {
|
|
177
|
-
const insert = db.prepare("INSERT INTO entries_vec (id, embedding) VALUES (?, ?)");
|
|
178
|
-
for (const row of rows) {
|
|
179
|
-
if (row.embedding.byteLength !== embeddingDim * 4) {
|
|
180
|
-
rejected++;
|
|
181
|
-
continue;
|
|
182
|
-
}
|
|
183
|
-
try {
|
|
184
|
-
insert.run(row.id, Buffer.from(row.embedding));
|
|
185
|
-
repaired++;
|
|
186
|
-
}
|
|
187
|
-
catch {
|
|
188
|
-
rejected++;
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
})();
|
|
192
|
-
}
|
|
193
|
-
const complete = rejected === 0 && isVecFastPathComplete(db);
|
|
194
|
-
setVecFastPathReady(db, complete);
|
|
195
|
-
return { available: true, repaired, removedOrphans, rejected, complete };
|
|
196
|
-
}
|
|
197
|
-
catch (error) {
|
|
198
|
-
setVecFastPathReady(db, false);
|
|
199
|
-
return {
|
|
200
|
-
available: true,
|
|
201
|
-
repaired,
|
|
202
|
-
removedOrphans,
|
|
203
|
-
rejected,
|
|
204
|
-
complete: false,
|
|
205
|
-
error: error instanceof Error ? error.message : String(error),
|
|
206
|
-
};
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
const vecTablePresent = new WeakMap();
|
|
210
|
-
/**
|
|
211
|
-
* Whether `entries_vec` exists on this connection, memoized per handle.
|
|
212
|
-
*
|
|
213
|
-
* openExistingDatabase loads the vec extension but deliberately does not run
|
|
214
|
-
* ensureSchema, so the table is not created on read paths — its absence is a
|
|
215
|
-
* normal state, not an error.
|
|
216
|
-
*/
|
|
217
|
-
function hasVecTable(db) {
|
|
218
|
-
// Only a POSITIVE result is memoized. The table cannot vanish from a live
|
|
219
|
-
// connection, but it CAN appear — ensureSchema creates it partway through an
|
|
220
|
-
// index run — so caching "absent" would pin a stale answer for the rest of
|
|
221
|
-
// the handle's life.
|
|
222
|
-
if (vecTablePresent.get(db) === true)
|
|
22
|
+
function hasModelColumn(db) {
|
|
23
|
+
if (modelColumnPresent.get(db) === true)
|
|
223
24
|
return true;
|
|
224
25
|
let present = false;
|
|
225
26
|
try {
|
|
226
|
-
present =
|
|
227
|
-
db.prepare("SELECT 1 FROM sqlite_master WHERE type IN ('table','view') AND name = 'entries_vec'").get() !==
|
|
228
|
-
undefined;
|
|
27
|
+
present = db.prepare("PRAGMA table_info(embeddings)").all().some((column) => column.name === "model");
|
|
229
28
|
}
|
|
230
29
|
catch {
|
|
231
30
|
present = false;
|
|
232
31
|
}
|
|
233
32
|
if (present)
|
|
234
|
-
|
|
33
|
+
modelColumnPresent.set(db, true);
|
|
235
34
|
return present;
|
|
236
35
|
}
|
|
237
|
-
/**
|
|
36
|
+
/**
|
|
37
|
+
* SQL predicate selecting `embeddings` rows usable for `model`. A NULL model
|
|
38
|
+
* predates per-row model tracking and is trusted as the current model; when
|
|
39
|
+
* no model is known at all, or the index predates the column, every row
|
|
40
|
+
* qualifies.
|
|
41
|
+
*/
|
|
42
|
+
function modelPredicate(db, model, alias = "embeddings") {
|
|
43
|
+
if (model === undefined || !hasModelColumn(db))
|
|
44
|
+
return { sql: "1", params: [] };
|
|
45
|
+
return { sql: `(${alias}.model IS NULL OR ${alias}.model = ?)`, params: [model] };
|
|
46
|
+
}
|
|
47
|
+
/** Remove the vector of an entry whose embedding input changed. */
|
|
238
48
|
export function deleteEntryVectors(db, id) {
|
|
239
49
|
db.prepare("DELETE FROM embeddings WHERE id = ?").run(id);
|
|
240
|
-
if (isVecAvailable(db))
|
|
241
|
-
db.prepare("DELETE FROM entries_vec WHERE id = ?").run(id);
|
|
242
50
|
}
|
|
243
|
-
const VEC_DOCS_URL = "https://github.com/itlackey/akm/blob/main/docs/reference/configuration.md#sqlite-vec-extension";
|
|
244
|
-
const VEC_FALLBACK_THRESHOLD = 10_000;
|
|
245
|
-
// Per-database warning state: tracks which databases have already emitted the
|
|
246
|
-
// vec-missing warning so we don't spam on every openDatabase() call.
|
|
247
|
-
const vecInitWarnedDbs = new WeakSet();
|
|
248
51
|
/**
|
|
249
|
-
*
|
|
250
|
-
*
|
|
52
|
+
* Purge every stored vector and mark the index as embedding-free. Only the
|
|
53
|
+
* explicit `akm index --reembed` override calls this: a model or dimension
|
|
54
|
+
* change keeps every stored row (each carries its own model) and re-embeds
|
|
55
|
+
* incrementally.
|
|
251
56
|
*/
|
|
252
|
-
export function
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
if (once && vecInitWarnedDbs.has(db))
|
|
256
|
-
return;
|
|
257
|
-
bestEffort(() => {
|
|
258
|
-
const row = db.prepare("SELECT COUNT(*) AS cnt FROM embeddings").get();
|
|
259
|
-
const count = row?.cnt ?? 0;
|
|
260
|
-
if (count >= VEC_FALLBACK_THRESHOLD) {
|
|
261
|
-
warn("Semantic search is using JS fallback for %d entries. Install sqlite-vec for faster performance.\n See: %s", count, VEC_DOCS_URL);
|
|
262
|
-
if (once)
|
|
263
|
-
vecInitWarnedDbs.add(db);
|
|
264
|
-
}
|
|
265
|
-
}, "embeddings table may not exist yet during init");
|
|
57
|
+
export function purgeEmbeddings(db) {
|
|
58
|
+
db.exec("DELETE FROM embeddings");
|
|
59
|
+
setMeta(db, "hasEmbeddings", "0");
|
|
266
60
|
}
|
|
267
61
|
/**
|
|
268
|
-
*
|
|
269
|
-
*
|
|
270
|
-
*
|
|
271
|
-
*
|
|
272
|
-
*
|
|
273
|
-
* No backup: embeddings are a derived cache, fully regenerable from the markdown
|
|
274
|
-
* by the next `akm index`. (Recovery model decided 2026-06-25.)
|
|
275
|
-
*
|
|
276
|
-
* `dropVecTable: true` DROPs `entries_vec` — used on a DIMENSION change, where
|
|
277
|
-
* the vec0 table must be recreated at the new width by the caller. The default
|
|
278
|
-
* clears its rows in place (same dimension, stale vectors).
|
|
62
|
+
* Store one entry's vector. `model` is the provider fingerprint it was
|
|
63
|
+
* generated under (`deriveSemanticProviderFingerprint`); a row written without
|
|
64
|
+
* one is trusted as the current model. Returns false, writing nothing, when
|
|
65
|
+
* the entry no longer exists.
|
|
279
66
|
*/
|
|
280
|
-
export function
|
|
281
|
-
bestEffort(() => db.exec("DELETE FROM embeddings"), "purge embeddings");
|
|
282
|
-
if (isVecAvailable(db)) {
|
|
283
|
-
bestEffort(() => db.exec(opts?.dropVecTable ? "DROP TABLE IF EXISTS entries_vec" : "DELETE FROM entries_vec"), "purge entries_vec");
|
|
284
|
-
}
|
|
285
|
-
setMeta(db, "hasEmbeddings", "0");
|
|
286
|
-
}
|
|
287
|
-
export function upsertEmbedding(db, entryId, embedding) {
|
|
67
|
+
export function upsertEmbedding(db, entryId, embedding, model) {
|
|
288
68
|
// Pre-flight FK guard: when an entry is deleted between when its id is queued
|
|
289
69
|
// for embedding and when this INSERT runs (e.g. consolidation deletes during
|
|
290
70
|
// a concurrent improve cycle), the INSERT throws "FOREIGN KEY constraint failed"
|
|
291
71
|
// and rolls back the entire batch transaction in the caller, losing every
|
|
292
72
|
// embedding for that run. A cheap SELECT here turns the race into a clean skip.
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
// Always write to BLOB table (works without sqlite-vec; the JS-cosine fallback
|
|
298
|
-
// reads it, so semantic search survives a vec fast-path failure).
|
|
299
|
-
db.prepare("INSERT OR REPLACE INTO embeddings (id, embedding) VALUES (?, ?)").run(entryId, buf);
|
|
300
|
-
if (!isVecAvailable(db))
|
|
301
|
-
return { stored: true, vec: "unavailable" };
|
|
302
|
-
// Fast path: mirror into the sqlite-vec table. Wrapped in a transaction so a
|
|
303
|
-
// crash between DELETE and INSERT does not leave the entry missing. A THROW
|
|
304
|
-
// here — previously swallowed silently by bestEffort — is now surfaced to the
|
|
305
|
-
// caller so the embedding phase can count it, warn, and route search to the
|
|
306
|
-
// (complete) BLOB table rather than a partial/mismatched vec table.
|
|
307
|
-
try {
|
|
308
|
-
db.transaction(() => {
|
|
309
|
-
db.prepare("DELETE FROM entries_vec WHERE id = ?").run(entryId);
|
|
310
|
-
db.prepare("INSERT INTO entries_vec (id, embedding) VALUES (?, ?)").run(entryId, buf);
|
|
311
|
-
})();
|
|
312
|
-
return { stored: true, vec: "ok" };
|
|
313
|
-
}
|
|
314
|
-
catch {
|
|
315
|
-
return { stored: true, vec: "failed" };
|
|
316
|
-
}
|
|
317
|
-
}
|
|
318
|
-
export function searchVec(db, queryEmbedding, k) {
|
|
319
|
-
// Fast path: sqlite-vec, but ONLY when the extension is loaded AND the
|
|
320
|
-
// embedding phase did not record a vec insert failure. A degraded fast-path
|
|
321
|
-
// table (partial or dimension-mismatched) would return wrong or missing
|
|
322
|
-
// neighbours, so we honestly fall back to the JS-cosine scan over the
|
|
323
|
-
// complete BLOB table instead.
|
|
324
|
-
if (isVecAvailable(db) && isVecFastPathReady(db)) {
|
|
325
|
-
const buf = float32Buffer(queryEmbedding);
|
|
326
|
-
try {
|
|
327
|
-
return db
|
|
328
|
-
.prepare("SELECT id, distance FROM entries_vec WHERE embedding MATCH ? AND k = ?")
|
|
329
|
-
.all(buf, k);
|
|
330
|
-
}
|
|
331
|
-
catch (err) {
|
|
332
|
-
// A dimension mismatch (e.g. the embedding provider/model changed since
|
|
333
|
-
// the fast-path table was built) is a real, expected reason this query
|
|
334
|
-
// specifically cannot use the vec table — the complete BLOB table below
|
|
335
|
-
// is unaffected, so fall back to it rather than either silently
|
|
336
|
-
// returning [] (masking a genuinely corrupt index) or failing the whole
|
|
337
|
-
// search over one degraded index.
|
|
338
|
-
warn("[db] searchVec (sqlite-vec path) failed, falling back to JS-cosine scan:", err instanceof Error ? err.message : String(err));
|
|
339
|
-
return searchBlobVec(db, queryEmbedding, k);
|
|
340
|
-
}
|
|
341
|
-
}
|
|
342
|
-
// Fallback: JS-based cosine similarity over BLOB table
|
|
343
|
-
return searchBlobVec(db, queryEmbedding, k);
|
|
73
|
+
if (!db.prepare("SELECT 1 FROM entries WHERE id = ?").get(entryId))
|
|
74
|
+
return false;
|
|
75
|
+
db.prepare("INSERT OR REPLACE INTO embeddings (id, embedding, model) VALUES (?, ?, ?)").run(entryId, Buffer.from(new Float32Array(embedding).buffer), model ?? null);
|
|
76
|
+
return true;
|
|
344
77
|
}
|
|
345
78
|
/**
|
|
346
|
-
*
|
|
347
|
-
*
|
|
348
|
-
*
|
|
349
|
-
*
|
|
350
|
-
*
|
|
351
|
-
* ~0 — callers should filter it out by id.
|
|
79
|
+
* The `k` stored vectors nearest to `query` by cosine similarity, best first
|
|
80
|
+
* (ties by id): an exact scan of the current model's rows. `distance` is
|
|
81
|
+
* `sqrt(2 * (1 - cosine))`, the L2 distance between the unit-normalised
|
|
82
|
+
* vectors. Rows of another width, left by a model that is no longer current,
|
|
83
|
+
* never match.
|
|
352
84
|
*/
|
|
353
|
-
export function
|
|
354
|
-
const
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
85
|
+
export function searchVec(db, query, k) {
|
|
86
|
+
const dim = query.length;
|
|
87
|
+
const q = Float64Array.from(query);
|
|
88
|
+
let queryNorm = 0;
|
|
89
|
+
for (let i = 0; i < dim; i++)
|
|
90
|
+
queryNorm += q[i] * q[i];
|
|
91
|
+
if (k <= 0 || queryNorm === 0)
|
|
359
92
|
return [];
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
const
|
|
364
|
-
|
|
93
|
+
queryNorm = Math.sqrt(queryNorm);
|
|
94
|
+
const current = modelPredicate(db, currentEmbeddingModel(db));
|
|
95
|
+
const ids = [];
|
|
96
|
+
const similarities = [];
|
|
97
|
+
const rows = db
|
|
98
|
+
.prepare(`SELECT id, embedding FROM embeddings WHERE ${current.sql}`)
|
|
99
|
+
.iterate(...current.params);
|
|
100
|
+
for (const { id, embedding } of rows) {
|
|
101
|
+
if (embedding.byteLength !== dim * 4)
|
|
102
|
+
continue;
|
|
103
|
+
// A Float32Array view needs 4-byte alignment; copy the rare row that lacks it.
|
|
104
|
+
const vector = embedding.byteOffset % 4 === 0
|
|
105
|
+
? new Float32Array(embedding.buffer, embedding.byteOffset, dim)
|
|
106
|
+
: new Float32Array(embedding.slice().buffer);
|
|
107
|
+
let dot = 0;
|
|
108
|
+
let norm = 0;
|
|
109
|
+
for (let i = 0; i < dim; i++) {
|
|
110
|
+
const x = vector[i];
|
|
111
|
+
dot += x * q[i];
|
|
112
|
+
norm += x * x;
|
|
113
|
+
}
|
|
114
|
+
if (norm === 0)
|
|
115
|
+
continue;
|
|
116
|
+
ids.push(id);
|
|
117
|
+
similarities.push(dot / (Math.sqrt(norm) * queryNorm));
|
|
118
|
+
}
|
|
119
|
+
const order = ids.map((_, index) => index);
|
|
120
|
+
order.sort((a, b) => similarities[b] - similarities[a] || ids[a] - ids[b]);
|
|
121
|
+
return order.slice(0, k).map((index) => ({
|
|
122
|
+
id: ids[index],
|
|
123
|
+
distance: Math.sqrt(2 * Math.max(0, 1 - similarities[index])),
|
|
124
|
+
}));
|
|
365
125
|
}
|
|
366
126
|
/**
|
|
367
|
-
*
|
|
368
|
-
*
|
|
369
|
-
*
|
|
370
|
-
*
|
|
371
|
-
*
|
|
372
|
-
* BUG-M2: the previous `buf.byteLength / 4` divide would truncate any
|
|
373
|
-
* trailing partial float and a misaligned `byteOffset` would throw — both
|
|
374
|
-
* surfaced as opaque generic errors caught upstream.
|
|
127
|
+
* The k nearest neighbours of an already-indexed entry, by its stored vector —
|
|
128
|
+
* no re-embedding, no network. Returns [] when the entry has no stored
|
|
129
|
+
* vector. The entry itself is typically returned with distance ~0; callers
|
|
130
|
+
* filter it out by id.
|
|
375
131
|
*/
|
|
376
|
-
function
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
return null;
|
|
380
|
-
}
|
|
381
|
-
// Copy into a fresh ArrayBuffer to sidestep any byteOffset alignment
|
|
382
|
-
// requirements imposed by Float32Array's typed-array view contract.
|
|
383
|
-
const aligned = new ArrayBuffer(buf.byteLength);
|
|
384
|
-
new Uint8Array(aligned).set(buf);
|
|
385
|
-
const f32 = new Float32Array(aligned);
|
|
386
|
-
return Array.from(f32);
|
|
387
|
-
}
|
|
388
|
-
function searchBlobVec(db, queryEmbedding, k) {
|
|
389
|
-
const rows = db.prepare("SELECT id, embedding FROM embeddings").all();
|
|
390
|
-
if (rows.length === 0)
|
|
132
|
+
export function getNeighborsByEntryId(db, id, k) {
|
|
133
|
+
const row = db.prepare("SELECT embedding FROM embeddings WHERE id = ?").get(id);
|
|
134
|
+
if (!row || row.embedding.byteLength % 4 !== 0)
|
|
391
135
|
return [];
|
|
392
|
-
|
|
393
|
-
const scored = [];
|
|
394
|
-
for (const row of rows) {
|
|
395
|
-
const embedding = bufferToFloat32(row.embedding, expectedDim);
|
|
396
|
-
if (embedding === null)
|
|
397
|
-
continue;
|
|
398
|
-
const similarity = cosineSimilarity(queryEmbedding, embedding);
|
|
399
|
-
scored.push({ id: row.id, similarity });
|
|
400
|
-
}
|
|
401
|
-
scored.sort((a, b) => b.similarity - a.similarity);
|
|
402
|
-
// Convert cosine similarity to L2 distance for compatibility with sqlite-vec interface
|
|
403
|
-
// For normalized vectors: L2² = 2(1 - cos_sim)
|
|
404
|
-
return scored.slice(0, k).map(({ id, similarity }) => ({
|
|
405
|
-
id,
|
|
406
|
-
distance: Math.sqrt(2 * Math.max(0, 1 - similarity)),
|
|
407
|
-
}));
|
|
136
|
+
return searchVec(db, new Float32Array(row.embedding.slice().buffer), k);
|
|
408
137
|
}
|
|
409
138
|
/**
|
|
410
|
-
*
|
|
411
|
-
*
|
|
139
|
+
* Every entry that has no embedding row for `model` (any row when no model is
|
|
140
|
+
* given), with its embedding input derived from the stored document
|
|
141
|
+
* (`buildSearchText`, whose hash `upsertEntry` keeps in `entries.embed_hash`).
|
|
142
|
+
* This is the embedding pass's cursor: a row generated under another model
|
|
143
|
+
* counts as missing and is replaced when the entry is re-embedded, so a model
|
|
144
|
+
* change re-embeds incrementally and an interrupted pass resumes where it
|
|
145
|
+
* stopped. A row whose `document_json` does not parse has no text to embed
|
|
146
|
+
* and is left out.
|
|
412
147
|
*/
|
|
413
|
-
export function getAllEntriesForEmbedding(db, entryIds) {
|
|
414
|
-
const select =
|
|
415
|
-
|
|
416
|
-
`;
|
|
417
|
-
const
|
|
148
|
+
export function getAllEntriesForEmbedding(db, entryIds, model) {
|
|
149
|
+
const select = "SELECT e.id, e.document_json AS documentJson, e.item_ref AS itemRef, e.file_path AS filePath FROM entries e";
|
|
150
|
+
const current = modelPredicate(db, model, "b");
|
|
151
|
+
const missing = `NOT EXISTS (SELECT 1 FROM embeddings b WHERE b.id = e.id AND ${current.sql})`;
|
|
152
|
+
const rows = [];
|
|
418
153
|
if (entryIds === undefined) {
|
|
419
|
-
|
|
154
|
+
rows.push(...db.prepare(`${select} WHERE ${missing} ORDER BY e.id`).all(...current.params));
|
|
155
|
+
}
|
|
156
|
+
else {
|
|
157
|
+
const targets = [...new Set(entryIds)].sort((left, right) => left - right);
|
|
158
|
+
for (let offset = 0; offset < targets.length; offset += SQLITE_CHUNK_SIZE) {
|
|
159
|
+
const chunk = targets.slice(offset, offset + SQLITE_CHUNK_SIZE);
|
|
160
|
+
const placeholders = chunk.map(() => "?").join(",");
|
|
161
|
+
rows.push(...db
|
|
162
|
+
.prepare(`${select} WHERE e.id IN (${placeholders}) AND ${missing} ORDER BY e.id`)
|
|
163
|
+
.all(...chunk, ...current.params));
|
|
164
|
+
}
|
|
420
165
|
}
|
|
421
|
-
const
|
|
422
|
-
const rows
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
166
|
+
const entries = [];
|
|
167
|
+
for (const { documentJson, ...row } of rows) {
|
|
168
|
+
let document;
|
|
169
|
+
try {
|
|
170
|
+
document = JSON.parse(documentJson);
|
|
171
|
+
}
|
|
172
|
+
catch {
|
|
426
173
|
continue;
|
|
427
|
-
|
|
428
|
-
|
|
174
|
+
}
|
|
175
|
+
entries.push({ ...row, searchText: buildSearchText(document) });
|
|
429
176
|
}
|
|
430
|
-
return
|
|
177
|
+
return entries;
|
|
431
178
|
}
|
|
432
|
-
|
|
433
|
-
|
|
179
|
+
/** Stored embedding rows — for `model` when given, otherwise every row. */
|
|
180
|
+
export function getEmbeddingCount(db, model) {
|
|
181
|
+
const current = modelPredicate(db, model);
|
|
182
|
+
const row = db.prepare(`SELECT COUNT(*) AS cnt FROM embeddings WHERE ${current.sql}`).get(...current.params);
|
|
434
183
|
return row.cnt;
|
|
435
184
|
}
|
|
436
|
-
/**
|
|
437
|
-
* Sample up to `limit` already-embedded entries (id, search text, and the
|
|
438
|
-
* stored vector) for the embedding-fingerprint canary check: re-embedding
|
|
439
|
-
* these texts with the CURRENT config and comparing against `vector` is how
|
|
440
|
-
* a model-string rename is told apart from a genuine model/dimension change
|
|
441
|
-
* (#955), without trusting the config string alone.
|
|
442
|
-
*
|
|
443
|
-
* Ordered by `id` for a deterministic, cheap sample (no `ORDER BY RANDOM()`)
|
|
444
|
-
* — the canary only needs "some" already-verified vectors, not a
|
|
445
|
-
* statistically representative one. A corrupt stored BLOB (see
|
|
446
|
-
* `bufferToFloat32`) is skipped rather than failing the whole sample.
|
|
447
|
-
*/
|
|
448
|
-
export function sampleEmbeddedEntriesForCanary(db, limit) {
|
|
449
|
-
const rows = db
|
|
450
|
-
.prepare(`
|
|
451
|
-
SELECT e.id, e.search_text AS searchText, em.embedding AS embedding
|
|
452
|
-
FROM entries e
|
|
453
|
-
JOIN embeddings em ON em.id = e.id
|
|
454
|
-
ORDER BY e.id
|
|
455
|
-
LIMIT ?
|
|
456
|
-
`)
|
|
457
|
-
.all(limit);
|
|
458
|
-
const samples = [];
|
|
459
|
-
for (const row of rows) {
|
|
460
|
-
const vector = bufferToFloat32(row.embedding, Math.floor(row.embedding.byteLength / 4));
|
|
461
|
-
if (vector)
|
|
462
|
-
samples.push({ id: row.id, searchText: row.searchText, vector });
|
|
463
|
-
}
|
|
464
|
-
return samples;
|
|
465
|
-
}
|
|
@@ -40,7 +40,8 @@ export function getAssetOutcome(db, ref) {
|
|
|
40
40
|
return row == null ? undefined : row;
|
|
41
41
|
}
|
|
42
42
|
/**
|
|
43
|
-
* Load ALL asset_outcome rows. Used for the
|
|
43
|
+
* Load ALL asset_outcome rows. Used for the stash-wide outcome-score
|
|
44
|
+
* normalisation in improve preparation.
|
|
44
45
|
*/
|
|
45
46
|
export function getAllAssetOutcomes(db) {
|
|
46
47
|
return db
|
|
@@ -402,3 +402,38 @@ export function listStateProposalIdsByPrefix(db, stashDir, idPrefix) {
|
|
|
402
402
|
.all(stashDir, `${escaped}%`);
|
|
403
403
|
return rows.map((r) => r.id);
|
|
404
404
|
}
|
|
405
|
+
/**
|
|
406
|
+
* Preview counterpart of {@link renameProposalsBundleRef} for `akm bundle
|
|
407
|
+
* rename --dry-run`: the same two `WHERE` predicates, read-only.
|
|
408
|
+
*/
|
|
409
|
+
export function countProposalsForBundleRename(db, oldBundleId) {
|
|
410
|
+
const prefix = `${escapeLikePattern(oldBundleId)}//`;
|
|
411
|
+
const refs = db.prepare(`SELECT COUNT(*) AS n FROM proposals WHERE ref LIKE ? ESCAPE '\\'`).get(`${prefix}%`).n;
|
|
412
|
+
const targets = db
|
|
413
|
+
.prepare(`SELECT COUNT(*) AS n FROM proposals WHERE json_extract(metadata_json, '$.proposedTarget.source') = ?`)
|
|
414
|
+
.get(oldBundleId).n;
|
|
415
|
+
return { refs, targets };
|
|
416
|
+
}
|
|
417
|
+
/**
|
|
418
|
+
* Rewrite every proposal that names `oldBundleId` from `akm bundle rename`
|
|
419
|
+
* (D6): the fully-qualified `ref` column (`<bundle>//conceptId` — an
|
|
420
|
+
* unqualified legacy `ref` resolves against `defaultBundle` at use time and
|
|
421
|
+
* never names a bundle explicitly, so it is left alone), and
|
|
422
|
+
* `metadata_json.proposedTarget.source`, which names the write-target bundle
|
|
423
|
+
* a still-pending proposal would apply to (`resolveRecordedProposalTarget`,
|
|
424
|
+
* `commands/proposal/repository.ts`). Returns how many rows of each were
|
|
425
|
+
* rewritten.
|
|
426
|
+
*/
|
|
427
|
+
export function renameProposalsBundleRef(db, oldBundleId, newBundleId) {
|
|
428
|
+
const prefix = `${escapeLikePattern(oldBundleId)}//`;
|
|
429
|
+
return db.transaction(() => {
|
|
430
|
+
const refs = Number(db
|
|
431
|
+
.prepare(`UPDATE proposals SET ref = ? || substr(ref, ?) WHERE ref LIKE ? ESCAPE '\\'`)
|
|
432
|
+
.run(newBundleId, oldBundleId.length + 1, `${prefix}%`).changes);
|
|
433
|
+
const targets = Number(db
|
|
434
|
+
.prepare(`UPDATE proposals SET metadata_json = json_set(metadata_json, '$.proposedTarget.source', ?)
|
|
435
|
+
WHERE json_extract(metadata_json, '$.proposedTarget.source') = ?`)
|
|
436
|
+
.run(newBundleId, oldBundleId).changes);
|
|
437
|
+
return { refs, targets };
|
|
438
|
+
})();
|
|
439
|
+
}
|