akm-cli 0.9.17-alpha.3 → 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 +731 -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 +361 -751
  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 -441
  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
@@ -2,61 +2,51 @@
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
4
  /**
5
- * index.db schema and version stamps, kept in the
6
- * storage layer. This isolates the one genuinely risky area (schema
7
- * evolution) from the CRUD/FTS/vector queries.
5
+ * index.db schema, kept in the storage layer so schema evolution stays apart
6
+ * from the CRUD/FTS/vector queries.
8
7
  *
9
- * The meta accessors, embedding purge, and vec-availability probe that
10
- * `ensureSchema` leans on live in the sibling `index-meta-repository` /
11
- * `index-vec-repository` modules.
8
+ * `ensureSchema` runs on every writable open and brings an older layout up
9
+ * to date in place: `CREATE ... IF NOT EXISTS`, `ALTER TABLE ... ADD COLUMN`
10
+ * for columns added after a table first shipped, drops of retired derived
11
+ * tables and columns, and one in-place rebuild of the (derived, cheap) FTS
12
+ * table when its layout is older than this release's. It never drops
13
+ * `entries`, `embeddings`, `utility_scores*`, `graph_*`, or
14
+ * `llm_enrichment_cache` to cross a version boundary; the only from-scratch
15
+ * rebuild is the SQLITE_CORRUPT path in `index-connection.ts`. A layout newer
16
+ * than this release's is refused, naming the upgrade
17
+ * ({@link newerIndexLayoutError}).
12
18
  */
19
+ import { createRequire } from "node:module";
13
20
  import { ConfigError } from "../../core/errors.js";
14
21
  import { warn } from "../../core/warn.js";
15
- import { ensureEmbeddingSalvageTable, salvageEmbeddingsBeforeDiscard } from "./embedding-salvage-repository.js";
16
- import { CANONICAL_ENTRY_SCHEMA_SQL, CANONICAL_INDEX_DB_VERSION, classifyIndexGeneration, isCanonicalIndexGeneration, } from "./index-entry-schema.js";
22
+ import { sha256Hex } from "../../runtime.js";
23
+ import { CANONICAL_ENTRY_SCHEMA_SQL, CANONICAL_INDEX_DB_VERSION, entriesFtsDdl, isContentlessFtsDdl, missingEntryColumns, readTableSql, supportsContentlessDelete, tableExists, } from "./index-entry-schema.js";
24
+ import { rebuildFts } from "./index-fts-repository.js";
17
25
  import { getMeta, setMeta } from "./index-meta-repository.js";
18
- import { isVecAvailable, purgeEmbeddings } from "./index-vec-repository.js";
19
26
  // ── Constants ───────────────────────────────────────────────────────────────
20
- // index.db is a regenerable cache. Incompatible entry-schema changes advance
21
- // this generation and discard only derived index tables; durable state remains
22
- // in state.db. Current readers and writers therefore target exactly one schema
23
- // and never carry live compatibility SQL for previous generations.
24
- //
25
- // v20→v21: remove the transitional entry_key/dir_path/stash_dir/entry_json/
26
- // entry_type columns. item_ref is the sole conflict key; document_json is the
27
- // sole stored document projection; bundle provenance and file_path provide the
28
- // current identity and materialized read path.
29
- //
30
- // v21→v22: entry mutations publish FTS synchronously and no dirty queue exists.
31
- // Discard the old derived generation so stale FTS rows and caller-managed dirty
32
- // state cannot cross the mutation-authority boundary.
33
27
  export const DB_VERSION = CANONICAL_INDEX_DB_VERSION;
34
- export const EMBEDDING_DIM = 384;
28
+ /** `index_meta` key set when the writable opener migrated the layout; cleared once `akm index` VACUUMs. */
29
+ export const VACUUM_PENDING_META = "vacuumPending";
35
30
  // #624-P1: graph_files is keyed to (stash_root, file_path, body_hash).
36
31
  export const GRAPH_SCHEMA_VERSION = 4;
32
+ /**
33
+ * The refusal for an index a newer akm wrote. Readers and the writable opener
34
+ * both raise it: a newer layout may lack tables or columns this release reads
35
+ * (layout 25 dropped `entries.search_text`), and writing it back at this
36
+ * layout would undo the newer release's migration.
37
+ */
38
+ export function newerIndexLayoutError(storedVersion, dbPath) {
39
+ return new ConfigError(`Index database${dbPath ? ` at ${dbPath}` : ""} was written by a newer akm (layout ${storedVersion}; this akm ` +
40
+ `understands ${DB_VERSION}). Upgrade akm to use this index.`, "INDEX_SCHEMA_INCOMPATIBLE", "Upgrade akm to a version that understands this index layout.");
41
+ }
37
42
  // ── Schema ──────────────────────────────────────────────────────────────────
38
43
  /**
39
44
  * DDL for the `registry_index_cache` table. This table lives in index.db
40
45
  * (managed by this module), so its DDL belongs here next to the `ensureSchema`
41
46
  * that applies it — not in state-db.ts.
42
47
  *
43
- * Created with CREATE TABLE IF NOT EXISTS so it is safe to call inside
44
- * `ensureSchema()`. Caches the result of resolving and fetching remote registry
45
- * stash indexes so `akm search` does not hit the network on every invocation.
46
- *
47
- * Indexed (query) columns:
48
- * registry_url TEXT PK — canonical URL of the registry; cache key.
49
- * fetched_at TEXT — ISO-8601; used to detect stale entries (TTL).
50
- * etag TEXT — HTTP ETag for conditional GET (If-None-Match).
51
- * last_modified TEXT — HTTP Last-Modified for conditional GET.
52
- *
53
- * Non-indexed payload:
54
- * index_json TEXT — JSON blob of the fetched registry index document.
55
- *
56
- * ADD COLUMN extension points (future migrations):
57
- * ALTER TABLE registry_index_cache ADD COLUMN schema_version INTEGER DEFAULT 1;
58
- * ALTER TABLE registry_index_cache ADD COLUMN kit_count INTEGER DEFAULT NULL;
59
- * ALTER TABLE registry_index_cache ADD COLUMN error_message TEXT DEFAULT NULL;
48
+ * Caches the result of resolving and fetching remote registry stash indexes so
49
+ * `akm search` does not hit the network on every invocation.
60
50
  */
61
51
  const REGISTRY_INDEX_CACHE_DDL = `
62
52
  CREATE TABLE IF NOT EXISTS registry_index_cache (
@@ -73,6 +63,11 @@ const REGISTRY_INDEX_CACHE_DDL = `
73
63
  /**
74
64
  * Create the graph-extraction tables (`graph_meta`/`graph_files`/`graph_file_entities`/
75
65
  * `graph_file_relations`/`graph_extraction_queue`).
66
+ *
67
+ * graph_files is self-keyed on (stash_root, file_path, body_hash) and is not
68
+ * tied to entries.id (#624-P1): re-upserting an entries row never disturbs the
69
+ * extracted graph, and a content change yields a distinct key. A UNIQUE index
70
+ * on (stash_root, file_path) still enforces one graph_files row per path.
76
71
  */
77
72
  function ensureGraphTables(db) {
78
73
  db.exec(`
@@ -150,7 +145,6 @@ function ensureGraphTables(db) {
150
145
  -- #624-P3: lazy graph-extraction queue. Standalone table (NO FK to
151
146
  -- graph_files — a queued file by definition has no graph row yet).
152
147
  -- Idempotent on (stash_root, file_path); drained highest-priority-first.
153
- -- CREATE TABLE IF NOT EXISTS is the forward migration (no DB_VERSION bump).
154
148
  CREATE TABLE IF NOT EXISTS graph_extraction_queue (
155
149
  stash_root TEXT NOT NULL,
156
150
  file_path TEXT NOT NULL,
@@ -165,99 +159,162 @@ function ensureGraphTables(db) {
165
159
  `);
166
160
  }
167
161
  /**
168
- * Cross the incompatible entry-schema boundary by discarding the derived index
169
- * generation. No row conversion or dual-schema compatibility is attempted:
170
- * the next index run rebuilds entries, FTS, embeddings, utility aggregates,
171
- * graph extraction, and enrichment caches from current sources/state.
162
+ * An `entries` table missing a required column cannot be read or written by
163
+ * this release (the last such change was v20→v21, which removed the
164
+ * transitional `entry_key`/`dir_path`/... columns and made `item_ref` the
165
+ * key). Recreate only the tables keyed by `entries.id` — their ids are about
166
+ * to be re-minted, so the rows would dangle anyway. Graph rows (keyed by
167
+ * path) and the LLM enrichment cache (keyed by ref) are kept. The next index
168
+ * run re-walks every source.
172
169
  */
173
- function rebuildIncompatibleIndexGeneration(db) {
174
- const version = getMeta(db, "version");
175
- const hasEntries = tableExists(db, "entries");
176
- if (!hasEntries && version === undefined)
170
+ function ensureEntriesLayout(db) {
171
+ if (!tableExists(db, "entries"))
177
172
  return;
178
- if (isCanonicalIndexGeneration(db))
173
+ const missing = missingEntryColumns(db);
174
+ if (missing.length === 0)
175
+ return;
176
+ warn(`Index database entries table predates the ${missing.join(", ")} column${missing.length === 1 ? "" : "s"} — ` +
177
+ "recreating the entries-keyed tables (entries, full-text, embeddings, utility scores); graph data and the " +
178
+ "LLM enrichment cache are kept. The next index run re-walks every source.");
179
+ db.transaction(() => {
180
+ for (const table of [
181
+ "entries_fts",
182
+ "entry_fragments",
183
+ "embeddings",
184
+ "utility_scores_scoped",
185
+ "utility_scores",
186
+ "index_dir_state",
187
+ "entries",
188
+ ]) {
189
+ db.exec(`DROP TABLE IF EXISTS ${table}`);
190
+ }
191
+ db.exec("DELETE FROM index_meta WHERE key IN ('builtAt', 'hasEmbeddings')");
192
+ })();
193
+ }
194
+ /**
195
+ * Drop the sqlite-vec mirror of `embeddings` (`entries_vec`, layout 24 and
196
+ * earlier); vectors are searched from `embeddings` alone. Dropping a vec0
197
+ * table needs its module, so an install without sqlite-vec leaves the table
198
+ * in place, unread, and the next writable open that can load it drops it.
199
+ */
200
+ function dropVecMirror(db) {
201
+ if (!tableExists(db, "entries_vec"))
179
202
  return;
180
- const classification = classifyIndexGeneration(db);
181
- if (classification.status === "newer") {
182
- throw new ConfigError(`Index database was built by a newer akm (stored generation ${classification.storedVersion ?? "unknown"}; ` +
183
- `this binary understands generation ${CANONICAL_INDEX_DB_VERSION}). Refusing to modify it — upgrade akm ` +
184
- "to use this index.", "INDEX_SCHEMA_INCOMPATIBLE", "Upgrade akm to a version that understands this index generation.");
185
- }
186
- warn(`Index database generation ${classification.storedVersion ?? "unknown"} is older than this akm's generation ` +
187
- `${CANONICAL_INDEX_DB_VERSION} — rebuilding the derived index (entries, FTS, embeddings, graph tables, ` +
188
- "utility scores, and the LLM enrichment cache). This re-walks and re-indexes every source on the next run.");
189
- let vecResetPending = false;
190
203
  try {
191
- db.exec("DROP TABLE IF EXISTS entries_vec");
204
+ createRequire(import.meta.url)("sqlite-vec").load(db);
205
+ db.exec("DROP TABLE entries_vec");
192
206
  }
193
207
  catch {
194
- // A vec0 table cannot be dropped while sqlite-vec is unavailable. It does
195
- // not reference entries, so leave a marker and drop it on the first later
196
- // open where the extension is available.
197
- vecResetPending = true;
208
+ // sqlite-vec is not loadable here.
209
+ }
210
+ }
211
+ /**
212
+ * Layout 25 keeps a hash of each entry's embedding input (`embed_hash`)
213
+ * instead of the text (`search_text`, layout 24 and earlier); the text is
214
+ * derived from `document_json` when the entry is embedded. The hash is taken
215
+ * from the stored text, so every vector stays valid until its entry's text
216
+ * changes. One transaction: a crash leaves `search_text` for the next
217
+ * writable open.
218
+ */
219
+ function replaceSearchTextWithHash(db) {
220
+ if (!tableHasColumn(db, "entries", "search_text"))
221
+ return;
222
+ db.transaction(() => {
223
+ ensureColumn(db, "entries", "embed_hash", "TEXT");
224
+ const page = db.prepare("SELECT id, search_text FROM entries WHERE id > ? ORDER BY id LIMIT 500");
225
+ const update = db.prepare("UPDATE entries SET embed_hash = ? WHERE id = ?");
226
+ let afterId = -1;
227
+ for (;;) {
228
+ const rows = page.all(afterId);
229
+ if (rows.length === 0)
230
+ break;
231
+ afterId = rows[rows.length - 1].id;
232
+ for (const row of rows)
233
+ update.run(sha256Hex(row.search_text), row.id);
234
+ }
235
+ db.exec("ALTER TABLE entries DROP COLUMN search_text");
236
+ })();
237
+ }
238
+ /**
239
+ * Bring `entries_fts` to the contentless layout, rebuilding it from `entries`
240
+ * when it is missing or still carries the content-bearing layout older
241
+ * releases wrote (the one-time v23→v24 migration). One transaction: a crash
242
+ * mid-rebuild leaves the old table in place and the next writable open
243
+ * retries. A SQLite without `contentless_delete` keeps (or gets) the
244
+ * content-bearing layout instead.
245
+ */
246
+ function ensureFtsLayout(db) {
247
+ const contentless = supportsContentlessDelete(db);
248
+ const sql = readTableSql(db, "entries_fts");
249
+ if (sql !== null && isContentlessFtsDdl(sql) === contentless)
250
+ return;
251
+ const entryCount = Number(db.prepare("SELECT COUNT(*) AS n FROM entries").get().n);
252
+ if (entryCount > 0) {
253
+ warn(`Rebuilding the full-text index for ${entryCount} entr${entryCount === 1 ? "y" : "ies"} ` +
254
+ "(embeddings, utility scores, graph data and the LLM enrichment cache are kept).");
198
255
  }
199
256
  db.transaction(() => {
200
- // #955: copy embeddings about to be discarded wholesale into
201
- // `embedding_salvage` (keyed by content hash + the fingerprint they were
202
- // generated under) BEFORE dropping `embeddings`, in the same transaction
203
- // as the drop, so the copy and the discard commit or roll back together.
204
- // The next embedding pass hands salvaged vectors back to unchanged
205
- // content instead of re-embedding the whole corpus after this bump.
206
- salvageEmbeddingsBeforeDiscard(db);
207
- db.exec("DROP TABLE IF EXISTS graph_file_relations");
208
- db.exec("DROP TABLE IF EXISTS graph_file_entities");
209
- db.exec("DROP TABLE IF EXISTS graph_files");
210
- db.exec("DROP TABLE IF EXISTS graph_extraction_queue");
211
- db.exec("DROP TABLE IF EXISTS graph_meta");
212
- db.exec("DROP TABLE IF EXISTS entries_fts_dirty");
213
- db.exec("DROP TABLE IF EXISTS entry_fragments_fts");
214
- db.exec("DROP TABLE IF EXISTS entry_fragments");
215
257
  db.exec("DROP TABLE IF EXISTS entries_fts");
216
- db.exec("DROP TABLE IF EXISTS embeddings");
217
- db.exec("DROP TABLE IF EXISTS utility_scores_scoped");
218
- db.exec("DROP TABLE IF EXISTS utility_scores");
219
- db.exec("DROP TABLE IF EXISTS llm_enrichment_cache");
220
- db.exec("DROP TABLE IF EXISTS index_dir_state");
221
- db.exec("DROP TABLE IF EXISTS entries");
222
- db.exec("DELETE FROM index_meta");
223
- // embedding_salvage is deliberately absent from the drop list above —
224
- // it is the ONE piece of derived state a generation rebuild must not
225
- // discard.
258
+ db.exec(entriesFtsDdl(contentless));
259
+ rebuildFts(db);
226
260
  })();
227
- if (vecResetPending)
228
- setMeta(db, "vecResetPending", "1");
229
261
  }
230
- export function ensureSchema(db, embeddingDim) {
231
- // Create meta table first so we can check version
262
+ function tableHasColumn(db, table, column) {
263
+ const columns = db.prepare(`PRAGMA table_info(${table})`).all();
264
+ return columns.some((existing) => existing.name === column);
265
+ }
266
+ /** `ALTER TABLE ... ADD COLUMN` for a column added after the table first shipped. Idempotent. */
267
+ function ensureColumn(db, table, column, type) {
268
+ if (tableHasColumn(db, table, column))
269
+ return false;
270
+ db.exec(`ALTER TABLE ${table} ADD COLUMN ${column} ${type}`);
271
+ return true;
272
+ }
273
+ export function ensureSchema(db) {
232
274
  db.exec(`
233
275
  CREATE TABLE IF NOT EXISTS index_meta (
234
276
  key TEXT PRIMARY KEY,
235
277
  value TEXT NOT NULL
236
278
  );
237
279
  `);
238
- // #955: created before the generation-rebuild check below so a discard
239
- // has somewhere to copy vectors to. Additive-only — it carries no bearing
240
- // on the `entries` generation fingerprint (`hasCanonicalEntrySchema`), so
241
- // adding it does not require a `CANONICAL_INDEX_DB_VERSION` bump.
242
- ensureEmbeddingSalvageTable(db);
243
- rebuildIncompatibleIndexGeneration(db);
280
+ const storedVersion = Number(getMeta(db, "version") ?? 0);
281
+ if (storedVersion > DB_VERSION)
282
+ throw newerIndexLayoutError(storedVersion);
283
+ ensureEntriesLayout(db);
284
+ const hadFragmentSource = tableExists(db, "entry_fragments");
244
285
  db.exec(CANONICAL_ENTRY_SCHEMA_SQL);
245
- // Workflow source is compiled directly into source IR at each command
246
- // boundary. The former workflow_documents cache duplicated that IR in a
247
- // second persisted representation and was never used by current execution.
248
- // index.db is derived state, so remove the obsolete table on every open.
286
+ replaceSearchTextWithHash(db);
287
+ // Retired derived tables: the workflow IR cache, the pre-v22 FTS dirty
288
+ // queue, the #955 embedding salvage staging table (embeddings now carry
289
+ // their model per row, so nothing is copied aside and reused), and the
290
+ // fragment FTS table search stopped reading (layout 24 and earlier).
249
291
  db.exec("DROP TABLE IF EXISTS workflow_documents");
250
- // BLOB-based embedding storage (always available, no sqlite-vec needed)
292
+ db.exec("DROP TABLE IF EXISTS entries_fts_dirty");
293
+ db.exec("DROP TABLE IF EXISTS embedding_salvage");
294
+ db.exec("DROP TABLE IF EXISTS entry_fragments_fts");
295
+ // One float32 BLOB per entry, searched by an exact scan
296
+ // (index-vec-repository.ts). `model` is the provider fingerprint the vector was generated under
297
+ // (`deriveSemanticProviderFingerprint`); the embedding pass re-embeds only
298
+ // rows whose model differs from the configured one. NULL means the row
299
+ // predates model tracking and is trusted as the current model.
251
300
  db.exec(`
252
301
  CREATE TABLE IF NOT EXISTS embeddings (
253
302
  id INTEGER PRIMARY KEY,
254
303
  embedding BLOB NOT NULL,
304
+ model TEXT,
255
305
  FOREIGN KEY (id) REFERENCES entries(id)
256
306
  );
257
307
  `);
258
- // usage_events lives in state.db. utility_scores remains a regenerable
259
- // index.db cache.
260
- // Utility scores table (aggregated per-entry utility metrics)
308
+ if (ensureColumn(db, "embeddings", "model", "TEXT")) {
309
+ // Rows written before model tracking were generated under the fingerprint
310
+ // the last pass recorded; label them so a later model change re-embeds
311
+ // them instead of trusting them forever.
312
+ const fingerprint = getMeta(db, "embeddingFingerprint");
313
+ if (fingerprint)
314
+ db.prepare("UPDATE embeddings SET model = ? WHERE model IS NULL").run(fingerprint);
315
+ }
316
+ // Utility scores (aggregated per-entry utility metrics) — a regenerable
317
+ // cache recomputed from state.db's usage_events on every index run.
261
318
  db.exec(`
262
319
  CREATE TABLE IF NOT EXISTS utility_scores (
263
320
  entry_id INTEGER PRIMARY KEY,
@@ -272,7 +329,6 @@ export function ensureSchema(db, embeddingDim) {
272
329
  `);
273
330
  // Per-project scoped utility scores — tracks usage per (entry, cwd-anchor)
274
331
  // so assets useful in project A don't pollute rankings in project B.
275
- // The global utility_scores table is preserved as a fallback / cold-start aid.
276
332
  db.exec(`
277
333
  CREATE TABLE IF NOT EXISTS utility_scores_scoped (
278
334
  entry_id INTEGER NOT NULL,
@@ -291,16 +347,18 @@ export function ensureSchema(db, embeddingDim) {
291
347
  file_mtime_max_ms REAL NOT NULL,
292
348
  reason TEXT NOT NULL,
293
349
  updated_at TEXT NOT NULL,
294
- row_count INTEGER
350
+ row_count INTEGER,
351
+ index_variant TEXT
295
352
  );
296
353
  `);
297
- ensureIndexDirStateRowCountColumn(db);
298
- // LLM enrichment result cache. Stores a SHA-256 body hash and the JSON
299
- // result for each asset so that subsequent `akm index --enrich` runs can
300
- // skip the LLM call when the body hasn't changed. The cache is keyed by
301
- // a stable asset_ref string (e.g. the absolute file path for graph/memory
302
- // passes, or `itemRef:passId` for the metadata-enhance pass).
303
- // Entries are cleaned up when assets are removed or --re-enrich is used.
354
+ // #900 (`row_count`) and the adapter variant were added after the table's
355
+ // first release. Pre-existing rows keep NULL until their directory is next
356
+ // drained.
357
+ ensureColumn(db, "index_dir_state", "row_count", "INTEGER");
358
+ ensureColumn(db, "index_dir_state", "index_variant", "TEXT");
359
+ // LLM enrichment result cache, keyed by a stable asset_ref string (the
360
+ // absolute file path for graph/memory passes, `item_ref` for the
361
+ // metadata-enhance pass) plus the body hash the result was produced for.
304
362
  db.exec(`
305
363
  CREATE TABLE IF NOT EXISTS llm_enrichment_cache (
306
364
  asset_ref TEXT NOT NULL,
@@ -314,110 +372,24 @@ export function ensureSchema(db, embeddingDim) {
314
372
  CREATE INDEX IF NOT EXISTS idx_llm_cache_updated
315
373
  ON llm_enrichment_cache(updated_at);
316
374
  `);
317
- // Graph extraction tables — schema v4 ((stash_root, file_path, body_hash) PK).
318
- //
319
- // graph_files is self-keyed on (stash_root, file_path, body_hash) and is NO
320
- // LONGER tied to entries.id. This is the #624-P1 win: deleting and
321
- // re-inserting an entries row during a reindex no longer cascade-wipes the
322
- // extracted graph — as long as the file's body_hash is unchanged, the graph
323
- // data survives. body_hash is part of the PK so a content change yields a
324
- // distinct key; a UNIQUE index on (stash_root, file_path) still enforces
325
- // exactly one graph_files row per path (delete-then-insert on a hash change).
326
- //
327
- // graph_file_entities and graph_file_relations carry (stash_root, file_path,
328
- // body_hash) and declare a composite FK -> graph_files ON DELETE CASCADE so
329
- // child rows are removed when a graph_files row is replaced.
330
- //
331
375
  ensureGraphTables(db);
332
- // If a generation rebuild could not drop a vec0 table while the extension
333
- // was unavailable, finish that reset as soon as vec0 can be loaded again.
334
- if (isVecAvailable(db) && getMeta(db, "vecResetPending") === "1") {
335
- db.exec("DROP TABLE IF EXISTS entries_vec");
336
- setMeta(db, "vecResetPending", "0");
337
- }
338
- // sqlite-vec table
339
- //
340
- // Dimension contract:
341
- // - When `embeddingDim` is `undefined`, the caller did NOT request a
342
- // specific dim. Do not touch `index_meta.embeddingDim` and do not run
343
- // the dim-change wipe — fall back to the stored dim (or the static
344
- // default) only when we have to materialise the vec table for the
345
- // first time. Without this guard, registry-side and other dim-unaware
346
- // `openDatabase()` callers would silently overwrite the dim-aware
347
- // improve/index value and oscillate the stored dim.
348
- // - When `embeddingDim` is a number, the caller explicitly asked for
349
- // that dim and owns the dim-change/backup/wipe semantics.
350
- const dimExplicit = embeddingDim !== undefined;
351
- const requestedDim = embeddingDim ?? (Number(getMeta(db, "embeddingDim")) || EMBEDDING_DIM);
352
- const effectiveDim = Number.isInteger(requestedDim) && requestedDim > 0 ? requestedDim : EMBEDDING_DIM;
353
- if (effectiveDim !== requestedDim) {
354
- warn(`Invalid embedding dimension ${requestedDim} — falling back to the default (${EMBEDDING_DIM}).`);
355
- }
356
- if (isVecAvailable(db)) {
357
- // Check if stored embedding dimension differs from configured one
358
- if (dimExplicit) {
359
- const storedDim = getMeta(db, "embeddingDim");
360
- if (storedDim && storedDim !== String(effectiveDim)) {
361
- // Stored vectors are incompatible with the new dimension. Drop the vec
362
- // table so the block below recreates it at the new width; the BLOB rows
363
- // go too. Regenerable from markdown — re-embedded by the next index.
364
- purgeEmbeddings(db, { dropVecTable: true });
365
- }
366
- }
367
- const vecExists = db.prepare("SELECT name FROM sqlite_master WHERE type='table' AND name='entries_vec'").get();
368
- if (!vecExists) {
369
- db.exec(`
370
- CREATE VIRTUAL TABLE entries_vec USING vec0(
371
- id INTEGER PRIMARY KEY,
372
- embedding FLOAT[${effectiveDim}]
373
- );
374
- `);
375
- }
376
- if (dimExplicit) {
377
- setMeta(db, "embeddingDim", String(effectiveDim));
378
- }
379
- }
380
- else {
381
- // Also purge BLOB embeddings on dimension change (JS fallback path).
382
- // When sqlite-vec is unavailable, entries_vec doesn't exist but the BLOB
383
- // embeddings table still stores vectors. If the configured dimension
384
- // changes, those stored BLOBs become silently incompatible.
385
- if (dimExplicit) {
386
- const storedDim = getMeta(db, "embeddingDim");
387
- if (storedDim && storedDim !== String(effectiveDim)) {
388
- // JS-fallback path: no vec table, just clear the stale BLOB vectors.
389
- purgeEmbeddings(db);
390
- }
391
- setMeta(db, "embeddingDim", String(effectiveDim));
392
- }
393
- }
394
- // Usage telemetry (usage_events) lives in state.db since Chunk-8 WI-8.3 —
395
- // no longer created here.
396
- // Registry index cache table — caches remote registry index documents so
397
- // `akm search` does not hit the network on every invocation.
376
+ dropVecMirror(db);
377
+ // Meta keys only the sqlite-vec mirror read.
378
+ db.exec("DELETE FROM index_meta WHERE key IN ('embeddingDim', 'vecFastPathReady')");
398
379
  db.exec(REGISTRY_INDEX_CACHE_DDL);
399
- // Write the generation stamp only after every required DDL surface exists.
400
- // A crash before this point leaves an unversioned generation that the next
401
- // writable open safely rebuilds instead of admitting a partial v23 index.
402
- setMeta(db, "version", String(DB_VERSION));
403
- }
404
- /**
405
- * Returns true when a table exists in the current database.
406
- */
407
- function tableExists(db, name) {
408
- const row = db.prepare("SELECT 1 FROM sqlite_master WHERE type='table' AND name=? LIMIT 1").get(name);
409
- return row !== undefined && row !== null;
410
- }
411
- /**
412
- * #900: `row_count` was added after the table's first release, so a database
413
- * created before it needs an `ALTER TABLE` (`CREATE TABLE IF NOT EXISTS` only
414
- * shapes a fresh table). Idempotent. Pre-existing rows keep NULL until their
415
- * directory is next drained; index.db is a regenerable cache, so nothing is
416
- * backfilled.
417
- */
418
- function ensureIndexDirStateRowCountColumn(db) {
419
- const columns = db.prepare("PRAGMA table_info(index_dir_state)").all();
420
- if (!columns.some((column) => column.name === "row_count")) {
421
- db.exec("ALTER TABLE index_dir_state ADD COLUMN row_count INTEGER");
380
+ ensureFtsLayout(db);
381
+ // An index that had no fragment source table (v22 and earlier) has no safe
382
+ // Markdown for `akm show` to resolve fragment selectors from until each
383
+ // directory is drained again. Clearing the per-directory cursor makes the
384
+ // next run re-read every source; entry ids, embeddings and utility rows
385
+ // stay put.
386
+ if (!hadFragmentSource && tableExists(db, "entries")) {
387
+ db.exec("DELETE FROM index_dir_state");
422
388
  }
389
+ // Migrating an existing layout drops tables and columns; the next `akm index`
390
+ // VACUUMs the pages they leave free (`vacuumIndexDb`, indexer.ts), since a
391
+ // writable open may run inside a caller's transaction, where VACUUM cannot.
392
+ if (storedVersion > 0 && storedVersion < DB_VERSION)
393
+ setMeta(db, VACUUM_PENDING_META, "1");
394
+ setMeta(db, "version", String(DB_VERSION));
423
395
  }
@@ -4,9 +4,9 @@
4
4
  /**
5
5
  * `index.db` utility-score + retrieval-count repository (MemRL signals).
6
6
  *
7
- * Owns the raw SQL for `utility_scores` / `utility_scores_scoped` and the
8
- * retrieval-frequency counting over `usage_events`. The bounded-step EMA policy
9
- * itself lives in `indexer/feedback/utility-policy`; this repo only reads/writes.
7
+ * Owns the raw SQL for `utility_scores` and the retrieval-frequency counting
8
+ * over `usage_events`. The bounded-step EMA policy itself lives in
9
+ * `indexer/feedback/utility-policy`; this repo only reads/writes.
10
10
  */
11
11
  import { conceptIdFromTypeName, parseRefInput } from "../../core/asset/resolve-ref.js";
12
12
  import { computeNextUtility } from "../../indexer/feedback/utility-policy.js";
@@ -31,18 +31,10 @@ export function getUtilityScore(db, entryId) {
31
31
  };
32
32
  }
33
33
  /**
34
- * Batch-load utility scores for multiple entry IDs in a single query.
35
- * Returns a `{ global, scoped }` pair, both Maps keyed by entry_id.
36
- *
37
- * When `scopeKey` is provided a second query runs against
38
- * `utility_scores_scoped` and the result is returned as `scoped`.
39
- * Both maps are always present; `scoped` is empty when `scopeKey` is absent.
34
+ * Batch-load utility scores for multiple entry IDs, keyed by entry_id.
40
35
  */
41
- export function getUtilityScoresByIds(db, ids, scopeKey) {
42
- const global = new Map();
43
- const scoped = new Map();
44
- if (ids.length === 0)
45
- return { global, scoped };
36
+ export function getUtilityScoresByIds(db, ids) {
37
+ const scores = new Map();
46
38
  // Process in chunks to stay within SQLITE_MAX_VARIABLE_NUMBER
47
39
  for (let i = 0; i < ids.length; i += SQLITE_CHUNK_SIZE) {
48
40
  const chunk = ids.slice(i, i + SQLITE_CHUNK_SIZE);
@@ -51,7 +43,7 @@ export function getUtilityScoresByIds(db, ids, scopeKey) {
51
43
  .prepare(`SELECT entry_id, utility, show_count, search_count, select_rate, last_used_at, updated_at FROM utility_scores WHERE entry_id IN (${placeholders})`)
52
44
  .all(...chunk);
53
45
  for (const row of rows) {
54
- global.set(row.entry_id, {
46
+ scores.set(row.entry_id, {
55
47
  entryId: row.entry_id,
56
48
  utility: row.utility,
57
49
  showCount: row.show_count,
@@ -61,21 +53,8 @@ export function getUtilityScoresByIds(db, ids, scopeKey) {
61
53
  updatedAt: row.updated_at,
62
54
  });
63
55
  }
64
- if (scopeKey) {
65
- const scopedRows = db
66
- .prepare(`SELECT entry_id, scope_key, utility, last_used_at FROM utility_scores_scoped WHERE scope_key = ? AND entry_id IN (${placeholders})`)
67
- .all(scopeKey, ...chunk);
68
- for (const row of scopedRows) {
69
- scoped.set(row.entry_id, {
70
- entryId: row.entry_id,
71
- scopeKey: row.scope_key,
72
- utility: row.utility,
73
- lastUsedAt: row.last_used_at,
74
- });
75
- }
76
- }
77
56
  }
78
- return { global, scoped };
57
+ return scores;
79
58
  }
80
59
  /**
81
60
  * Insert or update a utility score for an entry.