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.
Files changed (343) hide show
  1. package/CHANGELOG.md +756 -0
  2. package/dist/akm +94 -196
  3. package/dist/cli/shared.js +6 -2
  4. package/dist/cli.js +22 -9
  5. package/dist/commands/agent/agent-dispatch.js +1 -1
  6. package/dist/commands/command/command-execution.js +24 -62
  7. package/dist/commands/feedback-cli.js +0 -1
  8. package/dist/commands/health/accept-rate.js +2 -2
  9. package/dist/commands/health/checks.js +30 -75
  10. package/dist/commands/health/config-skew.js +38 -0
  11. package/dist/commands/health/egress.js +54 -0
  12. package/dist/commands/health/html-report.js +0 -38
  13. package/dist/commands/health/improve-metrics.js +123 -562
  14. package/dist/commands/health/plugin-staleness.js +53 -3
  15. package/dist/commands/health/renderers.js +12 -4
  16. package/dist/commands/health/report-view-model.js +11 -106
  17. package/dist/commands/health/types-improve.js +4 -19
  18. package/dist/commands/health/windows.js +64 -73
  19. package/dist/commands/health.js +122 -143
  20. package/dist/commands/improve/consolidate/chunking.js +25 -100
  21. package/dist/commands/improve/consolidate/sanitize.js +54 -149
  22. package/dist/commands/improve/consolidate.js +538 -1075
  23. package/dist/commands/improve/content-hash.js +16 -24
  24. package/dist/commands/improve/distill/content-repair.js +18 -100
  25. package/dist/commands/improve/distill-guards.js +20 -81
  26. package/dist/commands/improve/distill-promotion-policy.js +23 -243
  27. package/dist/commands/improve/distill.js +608 -1075
  28. package/dist/commands/improve/eligibility.js +126 -400
  29. package/dist/commands/improve/execution.js +3 -5
  30. package/dist/commands/improve/extract.js +487 -1046
  31. package/dist/commands/improve/feedback-valence.js +0 -25
  32. package/dist/commands/improve/improve-cli.js +29 -166
  33. package/dist/commands/improve/improve-result-file.js +10 -66
  34. package/dist/commands/improve/improve-strategies.js +12 -7
  35. package/dist/commands/improve/improve-usage-report.js +18 -64
  36. package/dist/commands/improve/improve.js +443 -1063
  37. package/dist/commands/improve/ledger.js +114 -0
  38. package/dist/commands/improve/locks.js +2 -8
  39. package/dist/commands/improve/loop-stages.js +459 -1172
  40. package/dist/commands/improve/memory/derived-ref.js +12 -77
  41. package/dist/commands/improve/memory/memory-belief.js +14 -118
  42. package/dist/commands/improve/memory/memory-improve.js +4 -3
  43. package/dist/commands/improve/outcome-loop.js +28 -156
  44. package/dist/commands/improve/planner.js +5 -10
  45. package/dist/commands/improve/preparation.js +851 -2339
  46. package/dist/commands/improve/proactive-maintenance.js +34 -101
  47. package/dist/commands/improve/reflect-noise.js +104 -280
  48. package/dist/commands/improve/reflect.js +621 -1367
  49. package/dist/commands/improve/salience.js +46 -232
  50. package/dist/commands/improve/session-asset.js +19 -100
  51. package/dist/commands/improve/stage.js +323 -0
  52. package/dist/commands/proposal/drain.js +251 -644
  53. package/dist/commands/proposal/proposal-cli.js +3 -18
  54. package/dist/commands/proposal/proposal-types.js +20 -41
  55. package/dist/commands/proposal/proposal.js +1 -2
  56. package/dist/commands/proposal/propose.js +134 -160
  57. package/dist/commands/proposal/repository.js +502 -1487
  58. package/dist/commands/proposal/validators/proposal-quality-validators.js +71 -174
  59. package/dist/commands/proposal/validators/proposal-validators.js +1 -1
  60. package/dist/commands/proposal/validators/proposals.js +13 -89
  61. package/dist/commands/read/curate.js +63 -413
  62. package/dist/commands/read/search-cli.js +16 -33
  63. package/dist/commands/read/search.js +17 -23
  64. package/dist/commands/read/show.js +2 -13
  65. package/dist/commands/sources/bundle-cli.js +25 -2
  66. package/dist/commands/sources/bundle-config-ops.js +7 -0
  67. package/dist/commands/sources/dangerous-env-audit.js +1 -2
  68. package/dist/commands/sources/info.js +2 -11
  69. package/dist/commands/sources/installed-stashes.js +197 -746
  70. package/dist/commands/sources/schema-repair.js +98 -129
  71. package/dist/commands/sources/source-add.js +62 -12
  72. package/dist/commands/sources/stash-cli.js +1 -1
  73. package/dist/commands/tasks/explain.js +10 -13
  74. package/dist/commands/tasks/tasks-cli.js +9 -8
  75. package/dist/commands/tasks/tasks.js +326 -930
  76. package/dist/commands/tasks/validate.js +42 -21
  77. package/dist/commands/workflow/plan.js +22 -29
  78. package/dist/commands/workflow-cli.js +4 -4
  79. package/dist/core/adapter/adapters/akm-adapter.js +0 -1
  80. package/dist/core/adapter/adapters/akm-lint.js +2 -3
  81. package/dist/core/adapter/adapters/akm-metadata.js +11 -12
  82. package/dist/core/adapter/adapters/akm-workflow-adapter.js +1 -1
  83. package/dist/core/adapter/execution-source.js +17 -29
  84. package/dist/core/asset/resolve-ref.js +1 -1
  85. package/dist/core/bundle-id.js +42 -5
  86. package/dist/core/bundle-rename.js +291 -0
  87. package/dist/core/config/config-io.js +1 -2
  88. package/dist/core/config/config-schema.js +1 -33
  89. package/dist/core/config/config-walker.js +1 -1
  90. package/dist/core/config/config.js +163 -68
  91. package/dist/core/config/legacy-source-shape-shim.js +38 -9
  92. package/dist/core/config/schema/embedding.js +20 -5
  93. package/dist/core/config/schema/engines.js +5 -0
  94. package/dist/core/config/schema/execution.js +1 -1
  95. package/dist/core/config/schema/experimental.js +1 -1
  96. package/dist/core/config/schema/improve-processes.js +21 -95
  97. package/dist/core/config/schema/improve.js +4 -42
  98. package/dist/core/config/schema/scheduler.js +12 -12
  99. package/dist/core/config/schema/search.js +6 -22
  100. package/dist/core/env-secret-ref.js +0 -1
  101. package/dist/core/errors.js +8 -9
  102. package/dist/core/file-lock.js +76 -173
  103. package/dist/core/logs-db.js +2 -2
  104. package/dist/core/paths.js +0 -27
  105. package/dist/core/redaction.js +109 -2
  106. package/dist/core/run-lock.js +2 -5
  107. package/dist/core/spawn-env.js +1 -1
  108. package/dist/core/state/migrations.js +108 -61
  109. package/dist/core/state-db-scope.js +2 -4
  110. package/dist/core/state-db.js +126 -692
  111. package/dist/core/type-presentation.js +1 -9
  112. package/dist/core/write-source.js +293 -1012
  113. package/dist/execution/input-contract.js +1 -1
  114. package/dist/execution/resolved-request.js +135 -689
  115. package/dist/execution/source.js +63 -257
  116. package/dist/execution/target-ref.js +1 -1
  117. package/dist/indexer/bundle-identity-guard.js +2 -2
  118. package/dist/indexer/db/graph-db.js +106 -46
  119. package/dist/indexer/ensure-index.js +44 -85
  120. package/dist/indexer/graph/graph-extraction.js +340 -562
  121. package/dist/indexer/graph/graph-related.js +130 -0
  122. package/dist/indexer/index-rebuild-lock.js +3 -11
  123. package/dist/indexer/index-writer-lock.js +8 -17
  124. package/dist/indexer/index-written-assets.js +139 -151
  125. package/dist/indexer/indexer.js +524 -846
  126. package/dist/indexer/materialize-embeddings.js +60 -397
  127. package/dist/indexer/passes/memory-inference.js +81 -90
  128. package/dist/indexer/passes/metadata.js +132 -200
  129. package/dist/indexer/read-preflight.js +0 -7
  130. package/dist/indexer/scan/doc-to-entry.js +1 -3
  131. package/dist/indexer/scan/drain-dir.js +1 -1
  132. package/dist/indexer/search/db-search.js +181 -590
  133. package/dist/indexer/search/fts-query.js +30 -41
  134. package/dist/indexer/search/ranking.js +28 -154
  135. package/dist/indexer/search/search-attribution.js +12 -32
  136. package/dist/indexer/search/search-fields.js +11 -15
  137. package/dist/indexer/search/search-hit-enrichers.js +54 -85
  138. package/dist/indexer/search/search-source.js +1 -4
  139. package/dist/indexer/usage/usage-events.js +2 -7
  140. package/dist/integrations/agent/engine-fallback.js +23 -40
  141. package/dist/integrations/agent/engine-resolution.js +93 -183
  142. package/dist/integrations/agent/execution.js +507 -0
  143. package/dist/integrations/agent/model-map.js +28 -156
  144. package/dist/integrations/agent/request-lowering.js +66 -141
  145. package/dist/integrations/agent/runner-dispatch.js +143 -321
  146. package/dist/integrations/agent/runner.js +54 -14
  147. package/dist/integrations/lockfile.js +53 -101
  148. package/dist/llm/embedders/deterministic.js +2 -3
  149. package/dist/llm/embedders/profile.js +71 -0
  150. package/dist/llm/embedders/remote.js +10 -15
  151. package/dist/llm/graph-extract.js +3 -12
  152. package/dist/llm/index-passes.js +3 -5
  153. package/dist/llm/memory-infer.js +1 -2
  154. package/dist/llm/metadata-enhance.js +1 -2
  155. package/dist/llm/structured-call.js +5 -24
  156. package/dist/output/generic-render.js +23 -11
  157. package/dist/output/html-render.js +13 -10
  158. package/dist/output/render-registry.js +3 -32
  159. package/dist/output/shapes/helpers.js +2 -34
  160. package/dist/output/shapes/passthrough.js +1 -9
  161. package/dist/{indexer/search/ranking-types.js → output/text/bundle-rename.js} +4 -1
  162. package/dist/output/text/command-format.js +60 -23
  163. package/dist/output/text/helpers.js +1 -1
  164. package/dist/output/text/migrate.js +5 -14
  165. package/dist/output/text/proposal-format.js +1 -2
  166. package/dist/output/text/workflow-format.js +0 -32
  167. package/dist/output/text.js +2 -0
  168. package/dist/registry/factory.js +4 -19
  169. package/dist/registry/network.js +66 -220
  170. package/dist/registry/providers/index.js +0 -2
  171. package/dist/registry/providers/skills-sh.js +3 -14
  172. package/dist/registry/providers/static-index.js +24 -26
  173. package/dist/registry/resolve.js +55 -131
  174. package/dist/scripts/akm-migrate-node.js +43937 -93313
  175. package/dist/scripts/akm-migrate.js +43697 -93071
  176. package/dist/setup/registry-stash-loader.js +4 -13
  177. package/dist/setup/semantic-assets.js +3 -44
  178. package/dist/setup/setup.js +1 -1
  179. package/dist/setup/steps/tasks.js +25 -15
  180. package/dist/sources/provider-factory.js +17 -18
  181. package/dist/sources/providers/filesystem.js +2 -3
  182. package/dist/sources/providers/git-install.js +7 -1
  183. package/dist/sources/providers/git-provider.js +0 -3
  184. package/dist/sources/providers/git-stash.js +0 -17
  185. package/dist/sources/providers/npm.js +2 -4
  186. package/dist/sources/providers/provider-utils.js +5 -10
  187. package/dist/sources/providers/website.js +0 -2
  188. package/dist/sources/snapshot-fetchers/website-ingest.js +1 -1
  189. package/dist/sources/website-url.js +2 -2
  190. package/dist/storage/database.js +9 -35
  191. package/dist/storage/repositories/improve-ledger-repository.js +168 -0
  192. package/dist/storage/repositories/index-connection.js +34 -70
  193. package/dist/storage/repositories/index-entries-repository.js +69 -111
  194. package/dist/storage/repositories/index-entry-mapper.js +1 -2
  195. package/dist/storage/repositories/index-entry-schema.js +83 -269
  196. package/dist/storage/repositories/index-fts-repository.js +86 -256
  197. package/dist/storage/repositories/index-llm-cache-repository.js +17 -0
  198. package/dist/storage/repositories/index-meta-repository.js +6 -4
  199. package/dist/storage/repositories/index-schema.js +192 -220
  200. package/dist/storage/repositories/index-utility-repository.js +8 -29
  201. package/dist/storage/repositories/index-vec-repository.js +133 -414
  202. package/dist/storage/repositories/outcome-repository.js +2 -1
  203. package/dist/storage/repositories/proposals-repository.js +35 -0
  204. package/dist/storage/repositories/registry-index-cache-repository.js +100 -0
  205. package/dist/storage/repositories/task-history-repository.js +26 -4
  206. package/dist/storage/repositories/workflow-runs-repository.js +53 -244
  207. package/dist/storage/sqlite-migrations.js +136 -0
  208. package/dist/storage/sqlite-pragmas.js +11 -9
  209. package/dist/storage/sqlite-transaction.js +170 -0
  210. package/dist/storage/state-db-integrity.js +34 -27
  211. package/dist/tasks/activation-config.js +134 -62
  212. package/dist/tasks/backends/cron.js +129 -277
  213. package/dist/tasks/backends/exec-utils.js +2 -5
  214. package/dist/tasks/backends/launchd.js +125 -745
  215. package/dist/tasks/backends/schtasks.js +101 -620
  216. package/dist/tasks/prepare/prepare-support.js +5 -15
  217. package/dist/tasks/prepare/prepare.js +0 -2
  218. package/dist/tasks/resolve-akm-bin.js +20 -79
  219. package/dist/tasks/run/attempt-lifecycle.js +0 -1
  220. package/dist/tasks/scheduler-binding.js +18 -238
  221. package/dist/tasks/scheduler-invocation.js +52 -52
  222. package/dist/tasks/scheduler-lock.js +53 -0
  223. package/dist/tasks/scheduler-sync.js +363 -679
  224. package/dist/tasks/source/parse-task-source.js +160 -10
  225. package/dist/tasks/source/task-source-v3-frozen.js +3 -4
  226. package/dist/tasks/source/task-to-v4.js +2 -2
  227. package/dist/workflows/authoring/authoring.js +3 -12
  228. package/dist/workflows/compile.js +211 -0
  229. package/dist/workflows/concurrency-policy.js +13 -74
  230. package/dist/workflows/exec/child-invocation.js +3 -17
  231. package/dist/workflows/exec/child-workflow.js +32 -141
  232. package/dist/workflows/exec/dispatch-redaction.js +13 -53
  233. package/dist/workflows/exec/environment.js +98 -0
  234. package/dist/workflows/exec/exec-unit.js +33 -140
  235. package/dist/workflows/exec/frozen-judge.js +7 -59
  236. package/dist/workflows/exec/native-executor.js +82 -341
  237. package/dist/workflows/exec/param-secrets.js +29 -47
  238. package/dist/workflows/exec/run-workflow.js +154 -387
  239. package/dist/workflows/exec/scheduler.js +9 -36
  240. package/dist/workflows/exec/step-work.js +127 -430
  241. package/dist/workflows/exec/unit-dispatch.js +11 -63
  242. package/dist/workflows/exec/unit-writer.js +8 -52
  243. package/dist/workflows/exec/worktree.js +39 -273
  244. package/dist/workflows/freeze/child-output-references.js +4 -15
  245. package/dist/workflows/freeze/environment.js +99 -92
  246. package/dist/workflows/freeze/freeze.js +172 -0
  247. package/dist/workflows/freeze/step-values.js +19 -21
  248. package/dist/workflows/freeze/targets/child-workflow.js +23 -92
  249. package/dist/workflows/freeze/targets/command.js +10 -33
  250. package/dist/workflows/freeze/targets/script.js +5 -12
  251. package/dist/workflows/freeze/targets/shell.js +3 -6
  252. package/dist/workflows/freeze/targets/task.js +25 -80
  253. package/dist/workflows/freeze/task-bindings.js +20 -67
  254. package/dist/workflows/{source-ir/github-yaml.js → github-yaml.js} +88 -206
  255. package/dist/workflows/ir/params.js +6 -51
  256. package/dist/workflows/ir/plan-hash.js +2 -34
  257. package/dist/workflows/parser.js +140 -43
  258. package/dist/{commands/improve/consolidate/types.js → workflows/plan.js} +2 -1
  259. package/dist/workflows/renderer.js +36 -69
  260. package/dist/workflows/resource-limits.js +12 -120
  261. package/dist/workflows/runtime/agent-identity.js +8 -40
  262. package/dist/workflows/runtime/run-outputs.js +3 -6
  263. package/dist/workflows/runtime/run-plan.js +316 -0
  264. package/dist/workflows/runtime/runs.js +48 -200
  265. package/dist/workflows/runtime/workflow-asset-loader.js +24 -57
  266. package/dist/workflows/{source-ir/semantics.js → source-semantics.js} +16 -20
  267. package/dist/workflows/validate-summary.js +2 -7
  268. package/docs/integration/bundling-akm.md +49 -42
  269. package/docs/migration/README.md +1 -0
  270. package/docs/migration/release-notes/0.9.17.md +41 -0
  271. package/docs/migration/v0.9.1-to-v0.9.2.md +19 -7
  272. package/docs/reference/cli.md +182 -125
  273. package/docs/reference/configuration.md +49 -56
  274. package/docs/reference/data-and-telemetry.md +19 -20
  275. package/docs/reference/tasks.md +86 -38
  276. package/docs/reference/workflow-schema.md +14 -18
  277. package/docs/reference/workflows.md +6 -9
  278. package/package.json +1 -1
  279. package/schemas/akm-config.json +87 -406
  280. package/dist/commands/health/advisories.js +0 -150
  281. package/dist/commands/health/metrics.js +0 -329
  282. package/dist/commands/health/surfaces.js +0 -102
  283. package/dist/commands/improve/anti-collapse.js +0 -83
  284. package/dist/commands/improve/collapse-detector.js +0 -432
  285. package/dist/commands/improve/consolidate/eligibility.js +0 -48
  286. package/dist/commands/improve/consolidate/merge.js +0 -146
  287. package/dist/commands/improve/distill/promote-memory.js +0 -329
  288. package/dist/commands/improve/distill/quality-gate.js +0 -500
  289. package/dist/commands/improve/memory/memory-contradiction-detect.js +0 -291
  290. package/dist/commands/improve/proposal-envelope.js +0 -31
  291. package/dist/commands/improve/run-context.js +0 -123
  292. package/dist/commands/improve/shared.js +0 -21
  293. package/dist/commands/improve/source-identity.js +0 -28
  294. package/dist/commands/improve/triage.js +0 -96
  295. package/dist/commands/proposal/drain-policies.js +0 -151
  296. package/dist/commands/sources/update-transaction.js +0 -220
  297. package/dist/core/action-contributors.js +0 -28
  298. package/dist/core/config/config-version-shim.js +0 -101
  299. package/dist/core/config/retired-experimental-keys-shim.js +0 -62
  300. package/dist/core/fs-txn.js +0 -405
  301. package/dist/core/lexical-score.js +0 -25
  302. package/dist/core/maintenance-barrier.js +0 -167
  303. package/dist/execution/executable-identity.js +0 -105
  304. package/dist/execution/guarded-source.js +0 -427
  305. package/dist/indexer/graph/graph-boost.js +0 -427
  306. package/dist/indexer/graph/graph-dedup.js +0 -95
  307. package/dist/indexer/search/name-match.js +0 -35
  308. package/dist/indexer/search/ranking-contributors.js +0 -515
  309. package/dist/indexer/walk/project-context.js +0 -192
  310. package/dist/integrations/agent/execution-cascade.js +0 -566
  311. package/dist/integrations/agent/execution-definitions.js +0 -202
  312. package/dist/integrations/agent/execution-lowering.js +0 -841
  313. package/dist/integrations/agent/execution-preparation.js +0 -98
  314. package/dist/integrations/agent/inline-execution.js +0 -74
  315. package/dist/registry/create-provider-registry.js +0 -29
  316. package/dist/registry/pinned-request-helper.js +0 -247
  317. package/dist/registry/pinned-transport.js +0 -717
  318. package/dist/sources/providers/index.js +0 -14
  319. package/dist/storage/engines/sqlite-migrations.js +0 -271
  320. package/dist/storage/repositories/canaries-repository.js +0 -107
  321. package/dist/storage/repositories/embedding-salvage-repository.js +0 -184
  322. package/dist/storage/repositories/registry-cache.js +0 -113
  323. package/dist/tasks/scheduler-sync-preview.js +0 -52
  324. package/dist/workflows/freeze/resolve-steps.js +0 -86
  325. package/dist/workflows/freeze/source-freeze.js +0 -64
  326. package/dist/workflows/ir/compile.js +0 -321
  327. package/dist/workflows/ir/environment-v4.js +0 -330
  328. package/dist/workflows/ir/freeze-v4.js +0 -153
  329. package/dist/workflows/ir/schema-v4.js +0 -745
  330. package/dist/workflows/ir/schema.js +0 -354
  331. package/dist/workflows/program/schema.js +0 -78
  332. package/dist/workflows/runtime/checkin.js +0 -57
  333. package/dist/workflows/runtime/plan-classifier.js +0 -196
  334. package/dist/workflows/runtime/unit-checkin.js +0 -45
  335. package/dist/workflows/runtime/unit-phases.js +0 -20
  336. package/dist/workflows/schema.js +0 -4
  337. package/dist/workflows/source-ir/compile.js +0 -200
  338. package/dist/workflows/source-ir/program.js +0 -50
  339. package/dist/workflows/source-ir/result.js +0 -26
  340. package/dist/workflows/source-ir/schema.js +0 -786
  341. package/dist/workflows/source-ir/triggers.js +0 -79
  342. package/dist/workflows/source-ir/uses.js +0 -40
  343. 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
- * Meta key persisting whether the sqlite-vec fast-path table (`entries_vec`) is
42
- * fully populated and trustworthy for this index. Set to "0" by the embedding
43
- * phase when one or more vec inserts FAILED (e.g. a vec0 dimension mismatch)
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
- const VEC_FAST_PATH_READY_META = "vecFastPathReady";
49
- /** Persist whether the sqlite-vec fast path is trustworthy (see the meta doc). */
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
- * True unless the embedding phase recorded a vec insert failure. Reflects the
55
- * ACTUAL insert outcomes recorded at index time — not an inference from how many
56
- * BLOB rows exist — so a degraded vec table routes search to the JS fallback
57
- * rather than silently returning partial fast-path results.
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
- export function isVecFastPathReady(db) {
60
- if (getMeta(db, VEC_FAST_PATH_READY_META) === "0")
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
- vecTablePresent.set(db, true);
33
+ modelColumnPresent.set(db, true);
235
34
  return present;
236
35
  }
237
- /** Remove both vector representations for an entry whose embedding input changed. */
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
- * Warn if sqlite-vec is unavailable and embedding count exceeds threshold.
250
- * Called from openDatabase (once at init) and from indexer (each run).
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 warnIfVecMissing(db, { once } = { once: false }) {
253
- if (isVecAvailable(db))
254
- return;
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
- * Purge stored embeddings (BLOB rows in `embeddings`, plus the `entries_vec`
269
- * virtual table) and mark the index as embedding-free. The single place that
270
- * invalidates embeddings — used on a dimension change, a model/provider change,
271
- * and a full rebuild.
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 purgeEmbeddings(db, opts) {
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
- const exists = db.prepare("SELECT 1 FROM entries WHERE id = ?").get(entryId);
294
- if (!exists)
295
- return { stored: false, vec: "unavailable" };
296
- const buf = float32Buffer(embedding);
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
- * Return the k nearest neighbours of an already-indexed entry using its
347
- * persisted embedding — no re-embedding, no network. Decodes the stored BLOB by
348
- * byte length (dim = bytes / 4) and reuses searchVec (sqlite-vec fast path or
349
- * JS-cosine fallback). Returns [] when the entry has no stored embedding or the
350
- * BLOB is corrupt. The query entry itself is typically returned with distance
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 getNeighborsByEntryId(db, id, k) {
354
- const row = db.prepare("SELECT embedding FROM embeddings WHERE id = ?").get(id);
355
- if (!row)
356
- return [];
357
- const queryEmbedding = bufferToFloat32(row.embedding, Math.floor(row.embedding.byteLength / 4));
358
- if (!queryEmbedding)
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
- return searchVec(db, queryEmbedding, k);
361
- }
362
- function float32Buffer(vec) {
363
- const f32 = new Float32Array(vec);
364
- return Buffer.from(f32.buffer);
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
- * Decode a stored embedding BLOB into a Float32 array of `expectedDim`
368
- * dimensions. Returns `null` (and emits a warning) when the byte length does
369
- * not exactly match `expectedDim * 4`, including the legacy partial-trailing
370
- * float case the previous truncating-divide silently swallowed.
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 bufferToFloat32(buf, expectedDim) {
377
- if (buf.byteLength !== expectedDim * 4) {
378
- warn("[db] bufferToFloat32: skipping embedding row — expected %d bytes (%d dim x 4), got %d", expectedDim * 4, expectedDim, buf.byteLength);
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
- const expectedDim = queryEmbedding.length;
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
- * Return all entries that do not yet have an embedding row.
411
- * Used by the embedding phase to determine which entries need vectors generated.
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
- SELECT e.id, e.search_text AS searchText, e.item_ref AS itemRef, e.file_path AS filePath FROM entries e
416
- `;
417
- const missing = "NOT EXISTS (SELECT 1 FROM embeddings b WHERE b.id = e.id)";
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
- return db.prepare(`${select} WHERE ${missing} ORDER BY e.id`).all();
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 targets = [...new Set(entryIds)].sort((left, right) => left - right);
422
- const rows = [];
423
- for (let offset = 0; offset < targets.length; offset += SQLITE_CHUNK_SIZE) {
424
- const chunk = targets.slice(offset, offset + SQLITE_CHUNK_SIZE);
425
- if (chunk.length === 0)
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
- const placeholders = chunk.map(() => "?").join(",");
428
- rows.push(...db.prepare(`${select} WHERE e.id IN (${placeholders}) AND ${missing} ORDER BY e.id`).all(...chunk));
174
+ }
175
+ entries.push({ ...row, searchText: buildSearchText(document) });
429
176
  }
430
- return rows;
177
+ return entries;
431
178
  }
432
- export function getEmbeddingCount(db) {
433
- const row = db.prepare("SELECT COUNT(*) AS cnt FROM embeddings").get();
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 proxy-adequacy tripwire computation.
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
+ }