akm-cli 0.9.17-alpha.3 → 0.9.17-alpha.5

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 (346) hide show
  1. package/CHANGELOG.md +760 -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/lint/base-linter.js +19 -5
  53. package/dist/commands/proposal/drain.js +251 -644
  54. package/dist/commands/proposal/proposal-cli.js +3 -18
  55. package/dist/commands/proposal/proposal-types.js +20 -41
  56. package/dist/commands/proposal/proposal.js +1 -2
  57. package/dist/commands/proposal/propose.js +134 -160
  58. package/dist/commands/proposal/repository.js +502 -1487
  59. package/dist/commands/proposal/validators/proposal-quality-validators.js +71 -174
  60. package/dist/commands/proposal/validators/proposal-validators.js +1 -1
  61. package/dist/commands/proposal/validators/proposals.js +13 -89
  62. package/dist/commands/read/curate.js +63 -413
  63. package/dist/commands/read/search-cli.js +16 -33
  64. package/dist/commands/read/search.js +17 -23
  65. package/dist/commands/read/show.js +2 -13
  66. package/dist/commands/sources/bundle-cli.js +25 -2
  67. package/dist/commands/sources/bundle-config-ops.js +4 -0
  68. package/dist/commands/sources/dangerous-env-audit.js +1 -2
  69. package/dist/commands/sources/info.js +2 -11
  70. package/dist/commands/sources/installed-stashes.js +197 -746
  71. package/dist/commands/sources/schema-repair.js +98 -129
  72. package/dist/commands/sources/source-add.js +62 -12
  73. package/dist/commands/sources/source-manage.js +9 -2
  74. package/dist/commands/sources/stash-cli.js +1 -1
  75. package/dist/commands/tasks/explain.js +10 -13
  76. package/dist/commands/tasks/tasks-cli.js +9 -8
  77. package/dist/commands/tasks/tasks.js +326 -930
  78. package/dist/commands/tasks/validate.js +42 -21
  79. package/dist/commands/workflow/plan.js +22 -29
  80. package/dist/commands/workflow-cli.js +4 -4
  81. package/dist/core/adapter/adapters/akm-adapter.js +0 -1
  82. package/dist/core/adapter/adapters/akm-lint.js +2 -3
  83. package/dist/core/adapter/adapters/akm-metadata.js +11 -12
  84. package/dist/core/adapter/adapters/akm-workflow-adapter.js +1 -1
  85. package/dist/core/adapter/execution-source.js +17 -29
  86. package/dist/core/asset/asset-placement.js +4 -13
  87. package/dist/core/asset/resolve-ref.js +1 -1
  88. package/dist/core/bundle-id.js +42 -5
  89. package/dist/core/bundle-rename.js +291 -0
  90. package/dist/core/config/config-io.js +1 -2
  91. package/dist/core/config/config-schema.js +1 -33
  92. package/dist/core/config/config-walker.js +1 -1
  93. package/dist/core/config/config.js +163 -68
  94. package/dist/core/config/legacy-source-shape-shim.js +38 -9
  95. package/dist/core/config/schema/embedding.js +20 -5
  96. package/dist/core/config/schema/engines.js +5 -0
  97. package/dist/core/config/schema/execution.js +1 -1
  98. package/dist/core/config/schema/experimental.js +1 -1
  99. package/dist/core/config/schema/improve-processes.js +21 -95
  100. package/dist/core/config/schema/improve.js +4 -42
  101. package/dist/core/config/schema/scheduler.js +12 -12
  102. package/dist/core/config/schema/search.js +6 -22
  103. package/dist/core/env-secret-ref.js +0 -1
  104. package/dist/core/errors.js +8 -9
  105. package/dist/core/file-lock.js +76 -173
  106. package/dist/core/logs-db.js +2 -2
  107. package/dist/core/paths.js +0 -27
  108. package/dist/core/redaction.js +109 -2
  109. package/dist/core/run-lock.js +2 -5
  110. package/dist/core/spawn-env.js +1 -1
  111. package/dist/core/state/migrations.js +108 -61
  112. package/dist/core/state-db-scope.js +2 -4
  113. package/dist/core/state-db.js +126 -692
  114. package/dist/core/type-presentation.js +1 -9
  115. package/dist/core/write-source.js +293 -1012
  116. package/dist/execution/input-contract.js +1 -1
  117. package/dist/execution/resolved-request.js +135 -689
  118. package/dist/execution/source.js +63 -257
  119. package/dist/execution/target-ref.js +1 -1
  120. package/dist/indexer/bundle-identity-guard.js +2 -2
  121. package/dist/indexer/db/graph-db.js +106 -46
  122. package/dist/indexer/ensure-index.js +44 -85
  123. package/dist/indexer/graph/graph-extraction.js +340 -562
  124. package/dist/indexer/graph/graph-related.js +130 -0
  125. package/dist/indexer/index-rebuild-lock.js +3 -11
  126. package/dist/indexer/index-writer-lock.js +8 -17
  127. package/dist/indexer/index-written-assets.js +139 -151
  128. package/dist/indexer/indexer.js +524 -846
  129. package/dist/indexer/materialize-embeddings.js +60 -397
  130. package/dist/indexer/passes/memory-inference.js +81 -90
  131. package/dist/indexer/passes/metadata.js +132 -200
  132. package/dist/indexer/read-preflight.js +0 -7
  133. package/dist/indexer/scan/doc-to-entry.js +1 -3
  134. package/dist/indexer/scan/drain-dir.js +1 -1
  135. package/dist/indexer/search/db-search.js +181 -590
  136. package/dist/indexer/search/fts-query.js +30 -41
  137. package/dist/indexer/search/ranking.js +28 -154
  138. package/dist/indexer/search/search-attribution.js +12 -32
  139. package/dist/indexer/search/search-fields.js +11 -15
  140. package/dist/indexer/search/search-hit-enrichers.js +54 -85
  141. package/dist/indexer/search/search-source.js +1 -4
  142. package/dist/indexer/usage/usage-events.js +2 -7
  143. package/dist/integrations/agent/engine-fallback.js +23 -40
  144. package/dist/integrations/agent/engine-resolution.js +93 -183
  145. package/dist/integrations/agent/execution.js +507 -0
  146. package/dist/integrations/agent/model-map.js +28 -156
  147. package/dist/integrations/agent/request-lowering.js +66 -141
  148. package/dist/integrations/agent/runner-dispatch.js +143 -321
  149. package/dist/integrations/agent/runner.js +54 -14
  150. package/dist/integrations/lockfile.js +53 -101
  151. package/dist/llm/embedders/deterministic.js +2 -3
  152. package/dist/llm/embedders/profile.js +71 -0
  153. package/dist/llm/embedders/remote.js +10 -15
  154. package/dist/llm/graph-extract.js +3 -12
  155. package/dist/llm/index-passes.js +3 -5
  156. package/dist/llm/memory-infer.js +1 -2
  157. package/dist/llm/metadata-enhance.js +1 -2
  158. package/dist/llm/structured-call.js +5 -24
  159. package/dist/output/generic-render.js +23 -11
  160. package/dist/output/html-render.js +13 -10
  161. package/dist/output/render-registry.js +3 -32
  162. package/dist/output/shapes/helpers.js +2 -34
  163. package/dist/output/shapes/passthrough.js +1 -9
  164. package/dist/{indexer/search/ranking-types.js → output/text/bundle-rename.js} +4 -1
  165. package/dist/output/text/command-format.js +60 -23
  166. package/dist/output/text/helpers.js +1 -1
  167. package/dist/output/text/migrate.js +5 -14
  168. package/dist/output/text/proposal-format.js +1 -2
  169. package/dist/output/text/workflow-format.js +0 -32
  170. package/dist/output/text.js +2 -0
  171. package/dist/registry/factory.js +4 -19
  172. package/dist/registry/network.js +66 -220
  173. package/dist/registry/providers/index.js +0 -2
  174. package/dist/registry/providers/skills-sh.js +3 -14
  175. package/dist/registry/providers/static-index.js +24 -26
  176. package/dist/registry/resolve.js +55 -131
  177. package/dist/scripts/akm-migrate-node.js +43940 -93320
  178. package/dist/scripts/akm-migrate.js +43700 -93078
  179. package/dist/setup/registry-stash-loader.js +4 -13
  180. package/dist/setup/semantic-assets.js +3 -44
  181. package/dist/setup/setup.js +1 -1
  182. package/dist/setup/steps/tasks.js +25 -15
  183. package/dist/sources/provider-factory.js +17 -18
  184. package/dist/sources/providers/filesystem.js +2 -3
  185. package/dist/sources/providers/git-install.js +7 -1
  186. package/dist/sources/providers/git-provider.js +0 -3
  187. package/dist/sources/providers/git-stash.js +0 -17
  188. package/dist/sources/providers/npm.js +2 -4
  189. package/dist/sources/providers/provider-utils.js +5 -10
  190. package/dist/sources/providers/website.js +0 -2
  191. package/dist/sources/snapshot-fetchers/website-ingest.js +1 -1
  192. package/dist/sources/website-url.js +2 -2
  193. package/dist/storage/database.js +9 -35
  194. package/dist/storage/repositories/improve-ledger-repository.js +168 -0
  195. package/dist/storage/repositories/index-connection.js +34 -70
  196. package/dist/storage/repositories/index-entries-repository.js +69 -111
  197. package/dist/storage/repositories/index-entry-mapper.js +1 -2
  198. package/dist/storage/repositories/index-entry-schema.js +83 -269
  199. package/dist/storage/repositories/index-fts-repository.js +86 -256
  200. package/dist/storage/repositories/index-llm-cache-repository.js +17 -0
  201. package/dist/storage/repositories/index-meta-repository.js +6 -4
  202. package/dist/storage/repositories/index-schema.js +192 -220
  203. package/dist/storage/repositories/index-utility-repository.js +8 -29
  204. package/dist/storage/repositories/index-vec-repository.js +133 -414
  205. package/dist/storage/repositories/outcome-repository.js +2 -1
  206. package/dist/storage/repositories/proposals-repository.js +35 -0
  207. package/dist/storage/repositories/registry-index-cache-repository.js +100 -0
  208. package/dist/storage/repositories/task-history-repository.js +26 -4
  209. package/dist/storage/repositories/workflow-runs-repository.js +53 -244
  210. package/dist/storage/sqlite-migrations.js +136 -0
  211. package/dist/storage/sqlite-pragmas.js +11 -9
  212. package/dist/storage/sqlite-transaction.js +170 -0
  213. package/dist/storage/state-db-integrity.js +34 -27
  214. package/dist/tasks/activation-config.js +134 -62
  215. package/dist/tasks/backends/cron.js +129 -277
  216. package/dist/tasks/backends/exec-utils.js +2 -5
  217. package/dist/tasks/backends/launchd.js +125 -745
  218. package/dist/tasks/backends/schtasks.js +101 -620
  219. package/dist/tasks/prepare/prepare-support.js +5 -15
  220. package/dist/tasks/prepare/prepare.js +0 -2
  221. package/dist/tasks/resolve-akm-bin.js +20 -79
  222. package/dist/tasks/run/attempt-lifecycle.js +0 -1
  223. package/dist/tasks/scheduler-binding.js +18 -238
  224. package/dist/tasks/scheduler-invocation.js +52 -52
  225. package/dist/tasks/scheduler-lock.js +53 -0
  226. package/dist/tasks/scheduler-sync.js +361 -751
  227. package/dist/tasks/source/parse-task-source.js +160 -10
  228. package/dist/tasks/source/task-source-v3-frozen.js +3 -4
  229. package/dist/tasks/source/task-to-v4.js +2 -2
  230. package/dist/workflows/authoring/authoring.js +3 -12
  231. package/dist/workflows/compile.js +211 -0
  232. package/dist/workflows/concurrency-policy.js +13 -74
  233. package/dist/workflows/exec/child-invocation.js +3 -17
  234. package/dist/workflows/exec/child-workflow.js +32 -141
  235. package/dist/workflows/exec/dispatch-redaction.js +13 -53
  236. package/dist/workflows/exec/environment.js +98 -0
  237. package/dist/workflows/exec/exec-unit.js +33 -140
  238. package/dist/workflows/exec/frozen-judge.js +7 -59
  239. package/dist/workflows/exec/native-executor.js +82 -341
  240. package/dist/workflows/exec/param-secrets.js +29 -47
  241. package/dist/workflows/exec/run-workflow.js +154 -387
  242. package/dist/workflows/exec/scheduler.js +9 -36
  243. package/dist/workflows/exec/step-work.js +127 -430
  244. package/dist/workflows/exec/unit-dispatch.js +11 -63
  245. package/dist/workflows/exec/unit-writer.js +8 -52
  246. package/dist/workflows/exec/worktree.js +39 -273
  247. package/dist/workflows/freeze/child-output-references.js +4 -15
  248. package/dist/workflows/freeze/environment.js +99 -92
  249. package/dist/workflows/freeze/freeze.js +172 -0
  250. package/dist/workflows/freeze/step-values.js +19 -21
  251. package/dist/workflows/freeze/targets/child-workflow.js +23 -92
  252. package/dist/workflows/freeze/targets/command.js +10 -33
  253. package/dist/workflows/freeze/targets/script.js +5 -12
  254. package/dist/workflows/freeze/targets/shell.js +3 -6
  255. package/dist/workflows/freeze/targets/task.js +25 -80
  256. package/dist/workflows/freeze/task-bindings.js +20 -67
  257. package/dist/workflows/{source-ir/github-yaml.js → github-yaml.js} +88 -206
  258. package/dist/workflows/ir/params.js +6 -51
  259. package/dist/workflows/ir/plan-hash.js +2 -34
  260. package/dist/workflows/parser.js +140 -43
  261. package/dist/{commands/improve/consolidate/types.js → workflows/plan.js} +2 -1
  262. package/dist/workflows/renderer.js +36 -69
  263. package/dist/workflows/resource-limits.js +12 -120
  264. package/dist/workflows/runtime/agent-identity.js +8 -40
  265. package/dist/workflows/runtime/run-outputs.js +3 -6
  266. package/dist/workflows/runtime/run-plan.js +316 -0
  267. package/dist/workflows/runtime/runs.js +48 -200
  268. package/dist/workflows/runtime/workflow-asset-loader.js +24 -57
  269. package/dist/workflows/{source-ir/semantics.js → source-semantics.js} +16 -20
  270. package/dist/workflows/validate-summary.js +2 -7
  271. package/docs/integration/bundling-akm.md +49 -42
  272. package/docs/migration/README.md +1 -0
  273. package/docs/migration/release-notes/0.9.17.md +41 -0
  274. package/docs/migration/v0.9.1-to-v0.9.2.md +19 -7
  275. package/docs/reference/cli.md +182 -125
  276. package/docs/reference/configuration.md +49 -56
  277. package/docs/reference/data-and-telemetry.md +19 -20
  278. package/docs/reference/tasks.md +86 -38
  279. package/docs/reference/workflow-schema.md +14 -18
  280. package/docs/reference/workflows.md +6 -9
  281. package/package.json +1 -1
  282. package/schemas/akm-config.json +87 -406
  283. package/dist/commands/health/advisories.js +0 -150
  284. package/dist/commands/health/metrics.js +0 -329
  285. package/dist/commands/health/surfaces.js +0 -102
  286. package/dist/commands/improve/anti-collapse.js +0 -83
  287. package/dist/commands/improve/collapse-detector.js +0 -432
  288. package/dist/commands/improve/consolidate/eligibility.js +0 -48
  289. package/dist/commands/improve/consolidate/merge.js +0 -146
  290. package/dist/commands/improve/distill/promote-memory.js +0 -329
  291. package/dist/commands/improve/distill/quality-gate.js +0 -500
  292. package/dist/commands/improve/memory/memory-contradiction-detect.js +0 -291
  293. package/dist/commands/improve/proposal-envelope.js +0 -31
  294. package/dist/commands/improve/run-context.js +0 -123
  295. package/dist/commands/improve/shared.js +0 -21
  296. package/dist/commands/improve/source-identity.js +0 -28
  297. package/dist/commands/improve/triage.js +0 -96
  298. package/dist/commands/proposal/drain-policies.js +0 -151
  299. package/dist/commands/sources/update-transaction.js +0 -220
  300. package/dist/core/action-contributors.js +0 -28
  301. package/dist/core/config/config-version-shim.js +0 -101
  302. package/dist/core/config/retired-experimental-keys-shim.js +0 -62
  303. package/dist/core/fs-txn.js +0 -405
  304. package/dist/core/lexical-score.js +0 -25
  305. package/dist/core/maintenance-barrier.js +0 -167
  306. package/dist/execution/executable-identity.js +0 -105
  307. package/dist/execution/guarded-source.js +0 -441
  308. package/dist/indexer/graph/graph-boost.js +0 -427
  309. package/dist/indexer/graph/graph-dedup.js +0 -95
  310. package/dist/indexer/search/name-match.js +0 -35
  311. package/dist/indexer/search/ranking-contributors.js +0 -515
  312. package/dist/indexer/walk/project-context.js +0 -192
  313. package/dist/integrations/agent/execution-cascade.js +0 -566
  314. package/dist/integrations/agent/execution-definitions.js +0 -202
  315. package/dist/integrations/agent/execution-lowering.js +0 -841
  316. package/dist/integrations/agent/execution-preparation.js +0 -98
  317. package/dist/integrations/agent/inline-execution.js +0 -74
  318. package/dist/registry/create-provider-registry.js +0 -29
  319. package/dist/registry/pinned-request-helper.js +0 -247
  320. package/dist/registry/pinned-transport.js +0 -717
  321. package/dist/sources/providers/index.js +0 -14
  322. package/dist/storage/engines/sqlite-migrations.js +0 -271
  323. package/dist/storage/repositories/canaries-repository.js +0 -107
  324. package/dist/storage/repositories/embedding-salvage-repository.js +0 -184
  325. package/dist/storage/repositories/registry-cache.js +0 -113
  326. package/dist/tasks/scheduler-sync-preview.js +0 -52
  327. package/dist/workflows/freeze/resolve-steps.js +0 -86
  328. package/dist/workflows/freeze/source-freeze.js +0 -64
  329. package/dist/workflows/ir/compile.js +0 -321
  330. package/dist/workflows/ir/environment-v4.js +0 -330
  331. package/dist/workflows/ir/freeze-v4.js +0 -153
  332. package/dist/workflows/ir/schema-v4.js +0 -745
  333. package/dist/workflows/ir/schema.js +0 -354
  334. package/dist/workflows/program/schema.js +0 -78
  335. package/dist/workflows/runtime/checkin.js +0 -57
  336. package/dist/workflows/runtime/plan-classifier.js +0 -196
  337. package/dist/workflows/runtime/unit-checkin.js +0 -45
  338. package/dist/workflows/runtime/unit-phases.js +0 -20
  339. package/dist/workflows/schema.js +0 -4
  340. package/dist/workflows/source-ir/compile.js +0 -200
  341. package/dist/workflows/source-ir/program.js +0 -50
  342. package/dist/workflows/source-ir/result.js +0 -26
  343. package/dist/workflows/source-ir/schema.js +0 -786
  344. package/dist/workflows/source-ir/triggers.js +0 -79
  345. package/dist/workflows/source-ir/uses.js +0 -40
  346. package/dist/workflows/validator.js +0 -60
@@ -4,82 +4,103 @@
4
4
  /**
5
5
  * `index.db` FTS5 search + materialization repository.
6
6
  *
7
- * Owns the `entries_fts` full-text query path, per-entry projections, and the
8
- * explicit full recovery rebuild.
7
+ * Owns the `entries_fts` full-text query path, per-entry projections (the FTS
8
+ * row and the `entry_fragments` safe Markdown `akm show` resolves fragment
9
+ * selectors from), and the explicit full recovery rebuild.
10
+ *
11
+ * `entries_fts` is contentless (`content=''`, see index-entry-schema.ts): the
12
+ * text lives once in `entries`, and an FTS row is addressed only by its rowid
13
+ * (`entries.id`). The `COALESCE(f.entry_id, f.rowid)` join below also reads
14
+ * the content-bearing table older releases wrote (where the UNINDEXED column
15
+ * is populated), so a read-only open of a not-yet-migrated index still
16
+ * answers correctly.
9
17
  */
10
18
  import { splitMarkdownFragments } from "../../core/asset/markdown-fragments.js";
11
- import { stableFtsScore } from "../../core/lexical-score.js";
12
19
  import { warn } from "../../core/warn.js";
13
- import { buildLexicalQueryPlan } from "../../indexer/search/fts-query.js";
20
+ import { ftsOrMatch, ftsQueryTokens } from "../../indexer/search/fts-query.js";
14
21
  import { buildSearchFields } from "../../indexer/search/search-fields.js";
15
22
  import { SQLITE_CHUNK_SIZE } from "./index-sql.js";
16
- const INSERT_FTS_SQL = "INSERT INTO entries_fts (entry_id, name, description, tags, hints, content) VALUES (?, ?, ?, ?, ?, ?)";
17
- const INSERT_FRAGMENT_SQL = "INSERT INTO entry_fragments_fts (entry_id, fragment_id, fragment_ordinal, content) VALUES (?, ?, ?, ?)";
23
+ // `entries_fts.rowid = entries.id`, so a per-entry delete is a rowid lookup.
24
+ const INSERT_FTS_SQL = "INSERT INTO entries_fts (rowid, entry_id, name, description, tags, hints, content) VALUES (?, ?, ?, ?, ?, ?, ?)";
18
25
  const ftsMutationStatementsByDb = new WeakMap();
19
26
  function getFtsMutationStatements(db) {
20
27
  const existing = ftsMutationStatementsByDb.get(db);
21
28
  if (existing)
22
29
  return existing;
23
30
  const statements = {
24
- deleteOne: db.prepare("DELETE FROM entries_fts WHERE entry_id = ?"),
31
+ deleteOne: db.prepare("DELETE FROM entries_fts WHERE rowid = ?"),
25
32
  insert: db.prepare(INSERT_FTS_SQL),
26
- deleteFragments: db.prepare("DELETE FROM entry_fragments_fts WHERE entry_id = ?"),
27
33
  upsertFragmentSource: db.prepare("INSERT INTO entry_fragments (entry_id, safe_markdown) VALUES (?, ?) ON CONFLICT(entry_id) DO UPDATE SET safe_markdown = excluded.safe_markdown"),
28
34
  deleteFragmentSource: db.prepare("DELETE FROM entry_fragments WHERE entry_id = ?"),
29
- insertFragment: db.prepare(INSERT_FRAGMENT_SQL),
30
35
  };
31
36
  ftsMutationStatementsByDb.set(db, statements);
32
37
  return statements;
33
38
  }
34
- /** Replace one entry's derived FTS projection inside the caller's transaction. */
39
+ /**
40
+ * Replace one entry's derived FTS row, and its fragment source when the scan
41
+ * read Markdown, inside the caller's transaction.
42
+ */
35
43
  export function replaceFtsEntry(db, entryId, entry, fragmentContent) {
36
44
  const fields = buildSearchFields(entry);
37
45
  const statements = getFtsMutationStatements(db);
38
46
  statements.deleteOne.run(entryId);
39
- statements.insert.run(entryId, fields.name, fields.description, fields.tags, fields.hints, fields.content);
47
+ statements.insert.run(entryId, entryId, fields.name, fields.description, fields.tags, fields.hints, fields.content);
40
48
  if (fragmentContent === undefined) {
41
49
  // Metadata-only re-upserts and re-keys deserialize the public document
42
50
  // without the internal substrate. Leave the persisted source untouched.
43
51
  // A scan that did read Markdown always supplies a value below.
44
52
  return;
45
53
  }
46
- statements.deleteFragments.run(entryId);
47
- statements.deleteFragmentSource.run(entryId);
48
- if (!fragmentContent)
49
- return;
50
- statements.upsertFragmentSource.run(entryId, fragmentContent);
51
- for (const fragment of splitMarkdownFragments(fragmentContent)) {
52
- statements.insertFragment.run(entryId, fragment.fragmentId, fragment.ordinal, fragment.text.toLowerCase());
53
- }
54
+ if (fragmentContent)
55
+ statements.upsertFragmentSource.run(entryId, fragmentContent);
56
+ else
57
+ statements.deleteFragmentSource.run(entryId);
54
58
  }
55
- /** Delete derived FTS projections for canonical entries that are being removed. */
59
+ /** Delete the FTS rows and fragment sources of canonical entries that are being removed. */
56
60
  export function deleteFtsEntries(db, entryIds) {
57
61
  for (let i = 0; i < entryIds.length; i += SQLITE_CHUNK_SIZE) {
58
62
  const chunk = entryIds.slice(i, i + SQLITE_CHUNK_SIZE);
59
63
  const placeholders = chunk.map(() => "?").join(",");
60
- db.prepare(`DELETE FROM entries_fts WHERE entry_id IN (${placeholders})`).run(...chunk);
61
- db.prepare(`DELETE FROM entry_fragments_fts WHERE entry_id IN (${placeholders})`).run(...chunk);
64
+ db.prepare(`DELETE FROM entries_fts WHERE rowid IN (${placeholders})`).run(...chunk);
62
65
  db.prepare(`DELETE FROM entry_fragments WHERE entry_id IN (${placeholders})`).run(...chunk);
63
66
  }
64
67
  }
65
- export function searchFts(db, query, limit, entryType, excludeTypes) {
66
- const plan = buildLexicalQueryPlan(query);
67
- if (!plan.exact)
68
+ /**
69
+ * BM25 per-column weights for `entries_fts(entry_id, name, description, tags,
70
+ * hints, content)`. `entry_id` is UNINDEXED and carries no weight.
71
+ */
72
+ const BM25_COLUMN_WEIGHTS = "0, 1.0, 1.0, 1.0, 1.0, 1.0";
73
+ /**
74
+ * The top `limit` entries matching ANY of the query's tokens
75
+ * (`ftsQueryTokens`), best whole-document BM25 first; equal scores are
76
+ * ordered by `item_ref` so the ranking is deterministic. Fragments play no
77
+ * part: whole-document BM25 beat the fragment population by 0.059 nDCG@10 on
78
+ * the retrieval suite.
79
+ *
80
+ * `entryType` narrows to one type; otherwise `excludeTypes` (#627, the
81
+ * default-hidden types such as `session`) are left out.
82
+ */
83
+ export function searchFts(db, query, limit, entryType, excludeTypes = []) {
84
+ const tokens = ftsQueryTokens(query);
85
+ if (limit <= 0 || tokens.length === 0)
68
86
  return [];
69
- // Try the exact AND query first
70
- const exactResults = runFtsQuery(db, plan.exact, "exact", limit, entryType, excludeTypes);
71
- if (exactResults.length > 0)
72
- return exactResults;
73
- if (plan.exactPrefix) {
74
- const prefixResults = runFtsQuery(db, plan.exactPrefix, "prefix", limit, entryType, excludeTypes);
75
- if (prefixResults.length > 0)
76
- return prefixResults;
77
- }
78
- // One measured relaxation only after both conjunctive forms miss. This is
79
- // still the same FTS table, BM25 weights, candidate collection, and
80
- // downstream ranker — merely an OR candidate query for sentence-shaped
81
- // input whose filler terms prevented a strict hit.
82
- return plan.relaxed ? runFtsQuery(db, plan.relaxed, "relaxed", limit, entryType, excludeTypes) : [];
87
+ const typed = entryType !== undefined && entryType !== "any";
88
+ const filter = typed
89
+ ? "AND e.type = ?"
90
+ : excludeTypes.length > 0
91
+ ? `AND e.type NOT IN (${excludeTypes.map(() => "?").join(", ")})`
92
+ : "";
93
+ const params = [ftsOrMatch(tokens), ...(typed ? [entryType] : excludeTypes), limit];
94
+ // Contentless FTS rows address their entry by rowid; the COALESCE also reads
95
+ // the content-bearing layout older releases wrote.
96
+ return db
97
+ .prepare(`SELECT e.id AS id, e.item_ref AS itemRef
98
+ FROM entries_fts f
99
+ JOIN entries e ON e.id = COALESCE(f.entry_id, f.rowid)
100
+ WHERE entries_fts MATCH ? ${filter}
101
+ ORDER BY bm25(entries_fts, ${BM25_COLUMN_WEIGHTS}), e.item_ref
102
+ LIMIT ?`)
103
+ .all(...params);
83
104
  }
84
105
  /**
85
106
  * Resolve an opaque fragment selector from the indexed safe projection, not
@@ -87,40 +108,14 @@ export function searchFts(db, query, limit, entryType, excludeTypes) {
87
108
  * file edit; the next index refresh atomically publishes the new revision.
88
109
  */
89
110
  export function getIndexedMarkdownFragment(db, itemRef, fragmentId) {
90
- return getIndexedMarkdownFragments(db, [{ itemRef, fragmentId }])[0];
91
- }
92
- /**
93
- * Batch the selected-hit projection read. Search commonly enriches several
94
- * fragment hits at once; reading all indexed-safe parents in chunks avoids an
95
- * N-query loop, while grouping selectors by parent ensures each safe revision
96
- * is split at most once.
97
- */
98
- export function getIndexedMarkdownFragments(db, selections) {
99
- if (selections.length === 0)
100
- return [];
101
- const itemRefs = [...new Set(selections.map((selection) => selection.itemRef))];
102
- const sourceByRef = new Map();
103
- for (let offset = 0; offset < itemRefs.length; offset += SQLITE_CHUNK_SIZE) {
104
- const chunk = itemRefs.slice(offset, offset + SQLITE_CHUNK_SIZE);
105
- const placeholders = chunk.map(() => "?").join(",");
106
- const rows = db
107
- .prepare(`SELECT e.item_ref, s.safe_markdown FROM entry_fragments s JOIN entries e ON e.id = s.entry_id WHERE e.item_ref IN (${placeholders})`)
108
- .all(...chunk);
109
- for (const row of rows)
110
- sourceByRef.set(row.item_ref, row.safe_markdown);
111
- }
112
- const fragmentsByRef = new Map();
113
- for (const [itemRef, safeMarkdown] of sourceByRef) {
114
- fragmentsByRef.set(itemRef, splitMarkdownFragments(safeMarkdown));
115
- }
116
- return selections.map((selection) => {
117
- const safeMarkdown = sourceByRef.get(selection.itemRef);
118
- const fragments = fragmentsByRef.get(selection.itemRef);
119
- if (safeMarkdown === undefined || !fragments)
120
- return undefined;
121
- const fragment = fragments.find((candidate) => candidate.fragmentId === selection.fragmentId || candidate.headingSlug === selection.fragmentId);
122
- return fragment ? materializeIndexedMarkdownFragment(fragment, fragments, safeMarkdown.length) : undefined;
123
- });
111
+ const row = db
112
+ .prepare("SELECT s.safe_markdown FROM entry_fragments s JOIN entries e ON e.id = s.entry_id WHERE e.item_ref = ?")
113
+ .get(itemRef);
114
+ if (row === undefined)
115
+ return undefined;
116
+ const fragments = splitMarkdownFragments(row.safe_markdown);
117
+ const fragment = fragments.find((candidate) => candidate.fragmentId === fragmentId || candidate.headingSlug === fragmentId);
118
+ return fragment ? materializeIndexedMarkdownFragment(fragment, fragments, row.safe_markdown.length) : undefined;
124
119
  }
125
120
  function materializeIndexedMarkdownFragment(fragment, fragments, parentChars) {
126
121
  return {
@@ -136,170 +131,8 @@ function materializeIndexedMarkdownFragment(fragment, fragments, parentChars) {
136
131
  fragments,
137
132
  };
138
133
  }
139
- function runFtsQuery(db, ftsQuery, lexicalMatch, limit, entryType, excludeTypes) {
140
- // Preserve the repository's ordinary limit contract for direct callers.
141
- // The boundary-expansion rule applies only to a positive candidate pool.
142
- if (limit <= 0)
143
- return [];
144
- // #627 — exclude-type clause. Only applies on the untyped ('any') path; an
145
- // explicit include filter (entryType) already narrows to a single type, so
146
- // exclusion is redundant there. An empty list skips the clause entirely
147
- // (never emit `NOT IN ()`, which is a SQL error / always-false).
148
- const excludes = excludeTypes && excludeTypes.length > 0 ? excludeTypes : [];
149
- const candidateBoundaryOffset = Math.max(0, limit - 1);
150
- // The typed and untyped paths differ only by one `type` WHERE clause
151
- // equality vs. an optional NOT IN exclusion) and their parameter order.
152
- // Join on integer entry_id directly (no CAST; we store integer). bm25()
153
- // per-column weights:
154
- // entry_id(0), name(10), description(5), tags(3), hints(2), content(1).
155
- let filterClause;
156
- let params;
157
- if (entryType && entryType !== "any") {
158
- filterClause = "AND e.type = ?";
159
- params = [ftsQuery, entryType, candidateBoundaryOffset];
160
- }
161
- else {
162
- filterClause = excludes.length > 0 ? `AND e.type NOT IN (${excludes.map(() => "?").join(", ")})` : "";
163
- // Param order: MATCH, then the NOT IN values, then the zero-based
164
- // candidate-boundary offset.
165
- params = [ftsQuery, ...excludes, candidateBoundaryOffset];
166
- }
167
- const sql = `
168
- -- Do not make a SQL-only relevance decision inside a tied BM25 boundary:
169
- -- the TypeScript ranker adds exact-name, type, and other contributors
170
- -- afterwards. Materialize BM25 once, locate the Nth score, and admit
171
- -- every row tied with it. This deliberately makes the result set
172
- -- data-bound for a pathological all-tied query; that is the only way to
173
- -- avoid silently dropping a legitimate later ranking winner.
174
- WITH scored AS MATERIALIZED (
175
- -- Keep this materialized set deliberately narrow. document_json can be
176
- -- large, and only rows admitted through the BM25 boundary need it.
177
- SELECT e.id, bm25(entries_fts, 0, 10.0, 5.0, 3.0, 2.0, 1.0) AS bm25Score
178
- FROM entries_fts f
179
- JOIN entries e ON e.id = f.entry_id
180
- WHERE entries_fts MATCH ?
181
- ${filterClause}
182
- ), boundary AS (
183
- SELECT bm25Score
184
- FROM scored
185
- ORDER BY bm25Score
186
- LIMIT 1 OFFSET ?
187
- )
188
- SELECT e.id, e.file_path AS filePath, e.document_json AS documentJson, e.search_text AS searchText,
189
- e.item_ref AS itemRef, e.bundle_id AS bundleId, e.concept_id AS conceptId, e.adapter_id AS adapterId,
190
- scored.bm25Score
191
- FROM scored
192
- JOIN entries e ON e.id = scored.id
193
- WHERE NOT EXISTS (SELECT 1 FROM boundary)
194
- OR scored.bm25Score <= (SELECT bm25Score FROM boundary)
195
- ORDER BY scored.bm25Score, e.id ASC
196
- `;
197
- const rows = db.prepare(sql).all(...params);
198
- const results = materializeRows(rows, lexicalMatch);
199
- // Fragments are a separate, intentionally calibrated evidence population:
200
- // parent FTS remains the sole implementation of metadata/body conjunction.
201
- // A selector is emitted only for one fragment that independently satisfies
202
- // this query. Raw BM25 values are never claimed comparable across tables;
203
- // each is passed through #933's stable mapping before merge.
204
- const fragmentResults = hasFragmentFts(db)
205
- ? runFragmentQuery(db, ftsQuery, lexicalMatch, limit, entryType, excludes)
206
- : [];
207
- return mergeParentAndFragmentResults(results, fragmentResults);
208
- }
209
- function hasFragmentFts(db) {
210
- return Boolean(db.prepare("SELECT 1 FROM sqlite_master WHERE type = 'table' AND name = 'entry_fragments_fts'").get());
211
- }
212
- function materializeRows(rows, lexicalMatch) {
213
- const results = [];
214
- for (const row of rows) {
215
- let entry;
216
- try {
217
- entry = JSON.parse(row.documentJson);
218
- }
219
- catch {
220
- warn(`[db] searchFts: skipping entry id=${row.id} — corrupt document_json`);
221
- continue;
222
- }
223
- results.push({
224
- id: row.id,
225
- filePath: row.filePath,
226
- entry,
227
- searchText: row.searchText,
228
- bm25Score: row.bm25Score,
229
- itemRef: row.itemRef,
230
- bundleId: row.bundleId,
231
- conceptId: row.conceptId,
232
- adapterId: row.adapterId,
233
- lexicalMatch,
234
- });
235
- }
236
- return results;
237
- }
238
- function runFragmentQuery(db, ftsQuery, lexicalMatch, limit, entryType, excludes) {
239
- const filter = entryType && entryType !== "any"
240
- ? "AND e.type = ?"
241
- : excludes.length
242
- ? `AND e.type NOT IN (${excludes.map(() => "?").join(",")})`
243
- : "";
244
- const filterParams = entryType && entryType !== "any" ? [entryType] : excludes;
245
- const candidateBoundaryOffset = Math.max(0, limit - 1);
246
- // Select the winning child per parent inside SQLite before finding the
247
- // candidate boundary. A document with many matching fragments therefore
248
- // occupies one parent slot, while a boundary tie retains every parent for
249
- // the TypeScript ranker to decide with its non-BM25 contributors. This has
250
- // one FTS query and no OFFSET walk; the returned boundary is intentionally
251
- // data-bound for a pathological all-tied query, just like parent FTS.
252
- const sql = `
253
- WITH matches AS MATERIALIZED (
254
- -- Keep repeated child rows as narrow as parent FTS's scored CTE. The
255
- -- document projection can be large; hydrate it only after the one-child
256
- -- per-parent collapse and BM25 boundary filtering below.
257
- SELECT e.id, f.fragment_id AS fragmentId, f.fragment_ordinal AS fragmentOrdinal,
258
- bm25(entry_fragments_fts) AS bm25Score
259
- FROM entry_fragments_fts f JOIN entries e ON e.id = f.entry_id
260
- WHERE entry_fragments_fts MATCH ? ${filter}
261
- ), ranked AS MATERIALIZED (
262
- SELECT *, ROW_NUMBER() OVER (PARTITION BY id ORDER BY bm25Score ASC, fragmentOrdinal ASC, fragmentId ASC) AS parentRank
263
- FROM matches
264
- ), parents AS MATERIALIZED (
265
- SELECT * FROM ranked WHERE parentRank = 1
266
- ), boundary AS (
267
- SELECT bm25Score FROM parents ORDER BY bm25Score ASC LIMIT 1 OFFSET ?
268
- )
269
- SELECT e.id, e.file_path AS filePath, e.document_json AS documentJson, e.search_text AS searchText,
270
- e.item_ref AS itemRef, e.bundle_id AS bundleId, e.concept_id AS conceptId, e.adapter_id AS adapterId,
271
- parents.fragmentId, parents.bm25Score
272
- FROM parents JOIN entries e ON e.id = parents.id
273
- WHERE NOT EXISTS (SELECT 1 FROM boundary)
274
- OR parents.bm25Score <= (SELECT bm25Score FROM boundary)
275
- ORDER BY parents.bm25Score ASC, parents.id ASC`;
276
- const rows = db.prepare(sql).all(ftsQuery, ...filterParams, candidateBoundaryOffset);
277
- const results = [];
278
- for (const row of rows) {
279
- const [result] = materializeRows([row], lexicalMatch);
280
- if (result) {
281
- results.push({
282
- ...result,
283
- fragmentId: row.fragmentId,
284
- lexicalScore: stableFtsScore(result.bm25Score, "fragment"),
285
- });
286
- }
287
- }
288
- return results;
289
- }
290
- function mergeParentAndFragmentResults(parents, fragments) {
291
- const winners = new Map();
292
- for (const parent of parents)
293
- winners.set(parent.id, { ...parent, lexicalScore: stableFtsScore(parent.bm25Score) });
294
- for (const fragment of fragments) {
295
- const existing = winners.get(fragment.id);
296
- if (!existing || (fragment.lexicalScore ?? 0) > (existing.lexicalScore ?? 0))
297
- winners.set(fragment.id, fragment);
298
- }
299
- return [...winners.values()].sort((left, right) => (right.lexicalScore ?? 0) - (left.lexicalScore ?? 0) || left.id - right.id);
300
- }
301
134
  /**
302
- * Explicitly rebuild the complete FTS5 projection from canonical entries.
135
+ * Explicitly rebuild `entries_fts` from canonical entries.
303
136
  * Ordinary entry mutations do not call this: `upsertEntry` and the delete
304
137
  * operations publish their FTS state in the same transaction as `entries`.
305
138
  * This remains a recovery/schema-verification primitive for regenerable
@@ -311,29 +144,26 @@ function mergeParentAndFragmentResults(parents, fragments) {
311
144
  export function rebuildFts(db) {
312
145
  db.transaction(() => {
313
146
  db.exec("DELETE FROM entries_fts");
314
- db.exec("DELETE FROM entry_fragments_fts");
315
- const rows = db
316
- .prepare("SELECT e.id, e.document_json, f.safe_markdown FROM entries e LEFT JOIN entry_fragments f ON f.entry_id = e.id")
317
- .all();
147
+ // Keyset pages, so a large index is never held in memory at once.
148
+ const page = db.prepare("SELECT id, document_json FROM entries WHERE id > ? ORDER BY id LIMIT 500");
318
149
  const insertStmt = db.prepare(INSERT_FTS_SQL);
319
- const fragmentStmt = db.prepare(INSERT_FRAGMENT_SQL);
320
150
  let skipped = 0;
321
- for (const row of rows) {
322
- let entry;
323
- let fields;
324
- try {
325
- entry = JSON.parse(row.document_json);
326
- fields = buildSearchFields(entry);
327
- }
328
- catch {
329
- skipped++;
330
- continue;
331
- }
332
- insertStmt.run(row.id, fields.name, fields.description, fields.tags, fields.hints, fields.content);
333
- if (row.safe_markdown) {
334
- for (const fragment of splitMarkdownFragments(row.safe_markdown)) {
335
- fragmentStmt.run(row.id, fragment.fragmentId, fragment.ordinal, fragment.text.toLowerCase());
151
+ let afterId = -1;
152
+ for (;;) {
153
+ const rows = page.all(afterId);
154
+ if (rows.length === 0)
155
+ break;
156
+ afterId = rows[rows.length - 1].id;
157
+ for (const row of rows) {
158
+ let fields;
159
+ try {
160
+ fields = buildSearchFields(JSON.parse(row.document_json));
161
+ }
162
+ catch {
163
+ skipped++;
164
+ continue;
336
165
  }
166
+ insertStmt.run(row.id, row.id, fields.name, fields.description, fields.tags, fields.hints, fields.content);
337
167
  }
338
168
  }
339
169
  if (skipped > 0) {
@@ -8,6 +8,7 @@
8
8
  * lets `akm index --enrich` skip the LLM call when a file's body is unchanged.
9
9
  */
10
10
  import { sha256Hex } from "../../runtime.js";
11
+ import { escapeLikePattern } from "../like-pattern.js";
11
12
  import { SQLITE_CHUNK_SIZE } from "./index-sql.js";
12
13
  /**
13
14
  * Look up a cached LLM result for the given asset_ref.
@@ -93,6 +94,22 @@ export function clearStaleCacheEntries(db) {
93
94
  AND asset_ref NOT IN (SELECT item_ref FROM entries)
94
95
  `);
95
96
  }
97
+ /**
98
+ * Rewrite every `llm_enrichment_cache.asset_ref` naming `oldBundleId` (a
99
+ * metadata-enrichment cache key, the canonical `<bundle>//conceptId` form of
100
+ * `entries.item_ref`) to `newBundleId` (`akm bundle rename`, D6). Must run in
101
+ * the same `index.db` write as `renameEntriesBundleId` — otherwise the next
102
+ * `akm index`'s {@link clearStaleCacheEntries} deletes every row still keyed
103
+ * to the old bundle, forcing a full LLM re-enrichment. A graph/memory cache
104
+ * row (keyed by absolute file path, not `item_ref`) never matches the `//`
105
+ * prefix and is left alone. Returns the number of rows rewritten.
106
+ */
107
+ export function renameLlmCacheAssetRefs(db, oldBundleId, newBundleId) {
108
+ const prefix = `${escapeLikePattern(oldBundleId)}//`;
109
+ return Number(db
110
+ .prepare(`UPDATE llm_enrichment_cache SET asset_ref = ? || substr(asset_ref, ?) WHERE asset_ref LIKE ? ESCAPE '\\'`)
111
+ .run(newBundleId, oldBundleId.length + 1, `${prefix}%`).changes);
112
+ }
96
113
  /**
97
114
  * Compute a stable SHA-256 hex digest of a UTF-8 string. Used as the body_hash
98
115
  * key in `llm_enrichment_cache`. Routed through the runtime boundary so the
@@ -23,7 +23,7 @@ export function deleteMeta(db, key) {
23
23
  // ── Per-directory index state ───────────────────────────────────────────────
24
24
  export function getIndexDirState(db, dirPath) {
25
25
  const row = db
26
- .prepare("SELECT dir_path, file_set_hash, file_mtime_max_ms, reason, updated_at, row_count FROM index_dir_state WHERE dir_path = ?")
26
+ .prepare("SELECT dir_path, file_set_hash, file_mtime_max_ms, reason, updated_at, row_count, index_variant FROM index_dir_state WHERE dir_path = ?")
27
27
  .get(dirPath);
28
28
  if (!row)
29
29
  return undefined;
@@ -34,17 +34,19 @@ export function getIndexDirState(db, dirPath) {
34
34
  reason: row.reason,
35
35
  updatedAt: row.updated_at,
36
36
  rowCount: row.row_count ?? undefined,
37
+ indexVariant: row.index_variant ?? undefined,
37
38
  };
38
39
  }
39
40
  export function upsertIndexDirState(db, state) {
40
- db.prepare(`INSERT INTO index_dir_state (dir_path, file_set_hash, file_mtime_max_ms, reason, updated_at, row_count)
41
- VALUES (?, ?, ?, ?, ?, ?)
41
+ db.prepare(`INSERT INTO index_dir_state (dir_path, file_set_hash, file_mtime_max_ms, reason, updated_at, row_count, index_variant)
42
+ VALUES (?, ?, ?, ?, ?, ?, ?)
42
43
  ON CONFLICT(dir_path) DO UPDATE SET
43
44
  file_set_hash = excluded.file_set_hash,
44
45
  file_mtime_max_ms = excluded.file_mtime_max_ms,
45
46
  reason = excluded.reason,
46
47
  updated_at = excluded.updated_at,
47
- row_count = excluded.row_count`).run(state.dirPath, state.fileSetHash, state.fileMtimeMaxMs, state.reason, new Date().toISOString(), state.rowCount ?? null);
48
+ row_count = excluded.row_count,
49
+ index_variant = excluded.index_variant`).run(state.dirPath, state.fileSetHash, state.fileMtimeMaxMs, state.reason, new Date().toISOString(), state.rowCount ?? null, state.indexVariant ?? null);
48
50
  }
49
51
  export function deleteIndexDirState(db, dirPath) {
50
52
  db.prepare("DELETE FROM index_dir_state WHERE dir_path = ?").run(dirPath);