akm-cli 0.9.17-alpha.2 → 0.9.17-alpha.4

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (343) hide show
  1. package/CHANGELOG.md +756 -0
  2. package/dist/akm +94 -196
  3. package/dist/cli/shared.js +6 -2
  4. package/dist/cli.js +22 -9
  5. package/dist/commands/agent/agent-dispatch.js +1 -1
  6. package/dist/commands/command/command-execution.js +24 -62
  7. package/dist/commands/feedback-cli.js +0 -1
  8. package/dist/commands/health/accept-rate.js +2 -2
  9. package/dist/commands/health/checks.js +30 -75
  10. package/dist/commands/health/config-skew.js +38 -0
  11. package/dist/commands/health/egress.js +54 -0
  12. package/dist/commands/health/html-report.js +0 -38
  13. package/dist/commands/health/improve-metrics.js +123 -562
  14. package/dist/commands/health/plugin-staleness.js +53 -3
  15. package/dist/commands/health/renderers.js +12 -4
  16. package/dist/commands/health/report-view-model.js +11 -106
  17. package/dist/commands/health/types-improve.js +4 -19
  18. package/dist/commands/health/windows.js +64 -73
  19. package/dist/commands/health.js +122 -143
  20. package/dist/commands/improve/consolidate/chunking.js +25 -100
  21. package/dist/commands/improve/consolidate/sanitize.js +54 -149
  22. package/dist/commands/improve/consolidate.js +538 -1075
  23. package/dist/commands/improve/content-hash.js +16 -24
  24. package/dist/commands/improve/distill/content-repair.js +18 -100
  25. package/dist/commands/improve/distill-guards.js +20 -81
  26. package/dist/commands/improve/distill-promotion-policy.js +23 -243
  27. package/dist/commands/improve/distill.js +608 -1075
  28. package/dist/commands/improve/eligibility.js +126 -400
  29. package/dist/commands/improve/execution.js +3 -5
  30. package/dist/commands/improve/extract.js +487 -1046
  31. package/dist/commands/improve/feedback-valence.js +0 -25
  32. package/dist/commands/improve/improve-cli.js +29 -166
  33. package/dist/commands/improve/improve-result-file.js +10 -66
  34. package/dist/commands/improve/improve-strategies.js +12 -7
  35. package/dist/commands/improve/improve-usage-report.js +18 -64
  36. package/dist/commands/improve/improve.js +443 -1063
  37. package/dist/commands/improve/ledger.js +114 -0
  38. package/dist/commands/improve/locks.js +2 -8
  39. package/dist/commands/improve/loop-stages.js +459 -1172
  40. package/dist/commands/improve/memory/derived-ref.js +12 -77
  41. package/dist/commands/improve/memory/memory-belief.js +14 -118
  42. package/dist/commands/improve/memory/memory-improve.js +4 -3
  43. package/dist/commands/improve/outcome-loop.js +28 -156
  44. package/dist/commands/improve/planner.js +5 -10
  45. package/dist/commands/improve/preparation.js +851 -2339
  46. package/dist/commands/improve/proactive-maintenance.js +34 -101
  47. package/dist/commands/improve/reflect-noise.js +104 -280
  48. package/dist/commands/improve/reflect.js +621 -1367
  49. package/dist/commands/improve/salience.js +46 -232
  50. package/dist/commands/improve/session-asset.js +19 -100
  51. package/dist/commands/improve/stage.js +323 -0
  52. package/dist/commands/proposal/drain.js +251 -644
  53. package/dist/commands/proposal/proposal-cli.js +3 -18
  54. package/dist/commands/proposal/proposal-types.js +20 -41
  55. package/dist/commands/proposal/proposal.js +1 -2
  56. package/dist/commands/proposal/propose.js +134 -160
  57. package/dist/commands/proposal/repository.js +502 -1487
  58. package/dist/commands/proposal/validators/proposal-quality-validators.js +71 -174
  59. package/dist/commands/proposal/validators/proposal-validators.js +1 -1
  60. package/dist/commands/proposal/validators/proposals.js +13 -89
  61. package/dist/commands/read/curate.js +63 -413
  62. package/dist/commands/read/search-cli.js +16 -33
  63. package/dist/commands/read/search.js +17 -23
  64. package/dist/commands/read/show.js +2 -13
  65. package/dist/commands/sources/bundle-cli.js +25 -2
  66. package/dist/commands/sources/bundle-config-ops.js +7 -0
  67. package/dist/commands/sources/dangerous-env-audit.js +1 -2
  68. package/dist/commands/sources/info.js +2 -11
  69. package/dist/commands/sources/installed-stashes.js +197 -746
  70. package/dist/commands/sources/schema-repair.js +98 -129
  71. package/dist/commands/sources/source-add.js +62 -12
  72. package/dist/commands/sources/stash-cli.js +1 -1
  73. package/dist/commands/tasks/explain.js +10 -13
  74. package/dist/commands/tasks/tasks-cli.js +9 -8
  75. package/dist/commands/tasks/tasks.js +326 -930
  76. package/dist/commands/tasks/validate.js +42 -21
  77. package/dist/commands/workflow/plan.js +22 -29
  78. package/dist/commands/workflow-cli.js +4 -4
  79. package/dist/core/adapter/adapters/akm-adapter.js +0 -1
  80. package/dist/core/adapter/adapters/akm-lint.js +2 -3
  81. package/dist/core/adapter/adapters/akm-metadata.js +11 -12
  82. package/dist/core/adapter/adapters/akm-workflow-adapter.js +1 -1
  83. package/dist/core/adapter/execution-source.js +17 -29
  84. package/dist/core/asset/resolve-ref.js +1 -1
  85. package/dist/core/bundle-id.js +42 -5
  86. package/dist/core/bundle-rename.js +291 -0
  87. package/dist/core/config/config-io.js +1 -2
  88. package/dist/core/config/config-schema.js +1 -33
  89. package/dist/core/config/config-walker.js +1 -1
  90. package/dist/core/config/config.js +163 -68
  91. package/dist/core/config/legacy-source-shape-shim.js +38 -9
  92. package/dist/core/config/schema/embedding.js +20 -5
  93. package/dist/core/config/schema/engines.js +5 -0
  94. package/dist/core/config/schema/execution.js +1 -1
  95. package/dist/core/config/schema/experimental.js +1 -1
  96. package/dist/core/config/schema/improve-processes.js +21 -95
  97. package/dist/core/config/schema/improve.js +4 -42
  98. package/dist/core/config/schema/scheduler.js +12 -12
  99. package/dist/core/config/schema/search.js +6 -22
  100. package/dist/core/env-secret-ref.js +0 -1
  101. package/dist/core/errors.js +8 -9
  102. package/dist/core/file-lock.js +76 -173
  103. package/dist/core/logs-db.js +2 -2
  104. package/dist/core/paths.js +0 -27
  105. package/dist/core/redaction.js +109 -2
  106. package/dist/core/run-lock.js +2 -5
  107. package/dist/core/spawn-env.js +1 -1
  108. package/dist/core/state/migrations.js +108 -61
  109. package/dist/core/state-db-scope.js +2 -4
  110. package/dist/core/state-db.js +126 -692
  111. package/dist/core/type-presentation.js +1 -9
  112. package/dist/core/write-source.js +293 -1012
  113. package/dist/execution/input-contract.js +1 -1
  114. package/dist/execution/resolved-request.js +135 -689
  115. package/dist/execution/source.js +63 -257
  116. package/dist/execution/target-ref.js +1 -1
  117. package/dist/indexer/bundle-identity-guard.js +2 -2
  118. package/dist/indexer/db/graph-db.js +106 -46
  119. package/dist/indexer/ensure-index.js +44 -85
  120. package/dist/indexer/graph/graph-extraction.js +340 -562
  121. package/dist/indexer/graph/graph-related.js +130 -0
  122. package/dist/indexer/index-rebuild-lock.js +3 -11
  123. package/dist/indexer/index-writer-lock.js +8 -17
  124. package/dist/indexer/index-written-assets.js +139 -151
  125. package/dist/indexer/indexer.js +524 -846
  126. package/dist/indexer/materialize-embeddings.js +60 -397
  127. package/dist/indexer/passes/memory-inference.js +81 -90
  128. package/dist/indexer/passes/metadata.js +132 -200
  129. package/dist/indexer/read-preflight.js +0 -7
  130. package/dist/indexer/scan/doc-to-entry.js +1 -3
  131. package/dist/indexer/scan/drain-dir.js +1 -1
  132. package/dist/indexer/search/db-search.js +181 -590
  133. package/dist/indexer/search/fts-query.js +30 -41
  134. package/dist/indexer/search/ranking.js +28 -154
  135. package/dist/indexer/search/search-attribution.js +12 -32
  136. package/dist/indexer/search/search-fields.js +11 -15
  137. package/dist/indexer/search/search-hit-enrichers.js +54 -85
  138. package/dist/indexer/search/search-source.js +1 -4
  139. package/dist/indexer/usage/usage-events.js +2 -7
  140. package/dist/integrations/agent/engine-fallback.js +23 -40
  141. package/dist/integrations/agent/engine-resolution.js +93 -183
  142. package/dist/integrations/agent/execution.js +507 -0
  143. package/dist/integrations/agent/model-map.js +28 -156
  144. package/dist/integrations/agent/request-lowering.js +66 -141
  145. package/dist/integrations/agent/runner-dispatch.js +143 -321
  146. package/dist/integrations/agent/runner.js +54 -14
  147. package/dist/integrations/lockfile.js +53 -101
  148. package/dist/llm/embedders/deterministic.js +2 -3
  149. package/dist/llm/embedders/profile.js +71 -0
  150. package/dist/llm/embedders/remote.js +10 -15
  151. package/dist/llm/graph-extract.js +3 -12
  152. package/dist/llm/index-passes.js +3 -5
  153. package/dist/llm/memory-infer.js +1 -2
  154. package/dist/llm/metadata-enhance.js +1 -2
  155. package/dist/llm/structured-call.js +5 -24
  156. package/dist/output/generic-render.js +23 -11
  157. package/dist/output/html-render.js +13 -10
  158. package/dist/output/render-registry.js +3 -32
  159. package/dist/output/shapes/helpers.js +2 -34
  160. package/dist/output/shapes/passthrough.js +1 -9
  161. package/dist/{indexer/search/ranking-types.js → output/text/bundle-rename.js} +4 -1
  162. package/dist/output/text/command-format.js +60 -23
  163. package/dist/output/text/helpers.js +1 -1
  164. package/dist/output/text/migrate.js +5 -14
  165. package/dist/output/text/proposal-format.js +1 -2
  166. package/dist/output/text/workflow-format.js +0 -32
  167. package/dist/output/text.js +2 -0
  168. package/dist/registry/factory.js +4 -19
  169. package/dist/registry/network.js +66 -220
  170. package/dist/registry/providers/index.js +0 -2
  171. package/dist/registry/providers/skills-sh.js +3 -14
  172. package/dist/registry/providers/static-index.js +24 -26
  173. package/dist/registry/resolve.js +55 -131
  174. package/dist/scripts/akm-migrate-node.js +43937 -93313
  175. package/dist/scripts/akm-migrate.js +43697 -93071
  176. package/dist/setup/registry-stash-loader.js +4 -13
  177. package/dist/setup/semantic-assets.js +3 -44
  178. package/dist/setup/setup.js +1 -1
  179. package/dist/setup/steps/tasks.js +25 -15
  180. package/dist/sources/provider-factory.js +17 -18
  181. package/dist/sources/providers/filesystem.js +2 -3
  182. package/dist/sources/providers/git-install.js +7 -1
  183. package/dist/sources/providers/git-provider.js +0 -3
  184. package/dist/sources/providers/git-stash.js +0 -17
  185. package/dist/sources/providers/npm.js +2 -4
  186. package/dist/sources/providers/provider-utils.js +5 -10
  187. package/dist/sources/providers/website.js +0 -2
  188. package/dist/sources/snapshot-fetchers/website-ingest.js +1 -1
  189. package/dist/sources/website-url.js +2 -2
  190. package/dist/storage/database.js +9 -35
  191. package/dist/storage/repositories/improve-ledger-repository.js +168 -0
  192. package/dist/storage/repositories/index-connection.js +34 -70
  193. package/dist/storage/repositories/index-entries-repository.js +69 -111
  194. package/dist/storage/repositories/index-entry-mapper.js +1 -2
  195. package/dist/storage/repositories/index-entry-schema.js +83 -269
  196. package/dist/storage/repositories/index-fts-repository.js +86 -256
  197. package/dist/storage/repositories/index-llm-cache-repository.js +17 -0
  198. package/dist/storage/repositories/index-meta-repository.js +6 -4
  199. package/dist/storage/repositories/index-schema.js +192 -220
  200. package/dist/storage/repositories/index-utility-repository.js +8 -29
  201. package/dist/storage/repositories/index-vec-repository.js +133 -414
  202. package/dist/storage/repositories/outcome-repository.js +2 -1
  203. package/dist/storage/repositories/proposals-repository.js +35 -0
  204. package/dist/storage/repositories/registry-index-cache-repository.js +100 -0
  205. package/dist/storage/repositories/task-history-repository.js +26 -4
  206. package/dist/storage/repositories/workflow-runs-repository.js +53 -244
  207. package/dist/storage/sqlite-migrations.js +136 -0
  208. package/dist/storage/sqlite-pragmas.js +11 -9
  209. package/dist/storage/sqlite-transaction.js +170 -0
  210. package/dist/storage/state-db-integrity.js +34 -27
  211. package/dist/tasks/activation-config.js +134 -62
  212. package/dist/tasks/backends/cron.js +129 -277
  213. package/dist/tasks/backends/exec-utils.js +2 -5
  214. package/dist/tasks/backends/launchd.js +125 -745
  215. package/dist/tasks/backends/schtasks.js +101 -620
  216. package/dist/tasks/prepare/prepare-support.js +5 -15
  217. package/dist/tasks/prepare/prepare.js +0 -2
  218. package/dist/tasks/resolve-akm-bin.js +20 -79
  219. package/dist/tasks/run/attempt-lifecycle.js +0 -1
  220. package/dist/tasks/scheduler-binding.js +18 -238
  221. package/dist/tasks/scheduler-invocation.js +52 -52
  222. package/dist/tasks/scheduler-lock.js +53 -0
  223. package/dist/tasks/scheduler-sync.js +363 -679
  224. package/dist/tasks/source/parse-task-source.js +160 -10
  225. package/dist/tasks/source/task-source-v3-frozen.js +3 -4
  226. package/dist/tasks/source/task-to-v4.js +2 -2
  227. package/dist/workflows/authoring/authoring.js +3 -12
  228. package/dist/workflows/compile.js +211 -0
  229. package/dist/workflows/concurrency-policy.js +13 -74
  230. package/dist/workflows/exec/child-invocation.js +3 -17
  231. package/dist/workflows/exec/child-workflow.js +32 -141
  232. package/dist/workflows/exec/dispatch-redaction.js +13 -53
  233. package/dist/workflows/exec/environment.js +98 -0
  234. package/dist/workflows/exec/exec-unit.js +33 -140
  235. package/dist/workflows/exec/frozen-judge.js +7 -59
  236. package/dist/workflows/exec/native-executor.js +82 -341
  237. package/dist/workflows/exec/param-secrets.js +29 -47
  238. package/dist/workflows/exec/run-workflow.js +154 -387
  239. package/dist/workflows/exec/scheduler.js +9 -36
  240. package/dist/workflows/exec/step-work.js +127 -430
  241. package/dist/workflows/exec/unit-dispatch.js +11 -63
  242. package/dist/workflows/exec/unit-writer.js +8 -52
  243. package/dist/workflows/exec/worktree.js +39 -273
  244. package/dist/workflows/freeze/child-output-references.js +4 -15
  245. package/dist/workflows/freeze/environment.js +99 -92
  246. package/dist/workflows/freeze/freeze.js +172 -0
  247. package/dist/workflows/freeze/step-values.js +19 -21
  248. package/dist/workflows/freeze/targets/child-workflow.js +23 -92
  249. package/dist/workflows/freeze/targets/command.js +10 -33
  250. package/dist/workflows/freeze/targets/script.js +5 -12
  251. package/dist/workflows/freeze/targets/shell.js +3 -6
  252. package/dist/workflows/freeze/targets/task.js +25 -80
  253. package/dist/workflows/freeze/task-bindings.js +20 -67
  254. package/dist/workflows/{source-ir/github-yaml.js → github-yaml.js} +88 -206
  255. package/dist/workflows/ir/params.js +6 -51
  256. package/dist/workflows/ir/plan-hash.js +2 -34
  257. package/dist/workflows/parser.js +140 -43
  258. package/dist/{commands/improve/consolidate/types.js → workflows/plan.js} +2 -1
  259. package/dist/workflows/renderer.js +36 -69
  260. package/dist/workflows/resource-limits.js +12 -120
  261. package/dist/workflows/runtime/agent-identity.js +8 -40
  262. package/dist/workflows/runtime/run-outputs.js +3 -6
  263. package/dist/workflows/runtime/run-plan.js +316 -0
  264. package/dist/workflows/runtime/runs.js +48 -200
  265. package/dist/workflows/runtime/workflow-asset-loader.js +24 -57
  266. package/dist/workflows/{source-ir/semantics.js → source-semantics.js} +16 -20
  267. package/dist/workflows/validate-summary.js +2 -7
  268. package/docs/integration/bundling-akm.md +49 -42
  269. package/docs/migration/README.md +1 -0
  270. package/docs/migration/release-notes/0.9.17.md +41 -0
  271. package/docs/migration/v0.9.1-to-v0.9.2.md +19 -7
  272. package/docs/reference/cli.md +182 -125
  273. package/docs/reference/configuration.md +49 -56
  274. package/docs/reference/data-and-telemetry.md +19 -20
  275. package/docs/reference/tasks.md +86 -38
  276. package/docs/reference/workflow-schema.md +14 -18
  277. package/docs/reference/workflows.md +6 -9
  278. package/package.json +1 -1
  279. package/schemas/akm-config.json +87 -406
  280. package/dist/commands/health/advisories.js +0 -150
  281. package/dist/commands/health/metrics.js +0 -329
  282. package/dist/commands/health/surfaces.js +0 -102
  283. package/dist/commands/improve/anti-collapse.js +0 -83
  284. package/dist/commands/improve/collapse-detector.js +0 -432
  285. package/dist/commands/improve/consolidate/eligibility.js +0 -48
  286. package/dist/commands/improve/consolidate/merge.js +0 -146
  287. package/dist/commands/improve/distill/promote-memory.js +0 -329
  288. package/dist/commands/improve/distill/quality-gate.js +0 -500
  289. package/dist/commands/improve/memory/memory-contradiction-detect.js +0 -291
  290. package/dist/commands/improve/proposal-envelope.js +0 -31
  291. package/dist/commands/improve/run-context.js +0 -123
  292. package/dist/commands/improve/shared.js +0 -21
  293. package/dist/commands/improve/source-identity.js +0 -28
  294. package/dist/commands/improve/triage.js +0 -96
  295. package/dist/commands/proposal/drain-policies.js +0 -151
  296. package/dist/commands/sources/update-transaction.js +0 -220
  297. package/dist/core/action-contributors.js +0 -28
  298. package/dist/core/config/config-version-shim.js +0 -101
  299. package/dist/core/config/retired-experimental-keys-shim.js +0 -62
  300. package/dist/core/fs-txn.js +0 -405
  301. package/dist/core/lexical-score.js +0 -25
  302. package/dist/core/maintenance-barrier.js +0 -167
  303. package/dist/execution/executable-identity.js +0 -105
  304. package/dist/execution/guarded-source.js +0 -427
  305. package/dist/indexer/graph/graph-boost.js +0 -427
  306. package/dist/indexer/graph/graph-dedup.js +0 -95
  307. package/dist/indexer/search/name-match.js +0 -35
  308. package/dist/indexer/search/ranking-contributors.js +0 -515
  309. package/dist/indexer/walk/project-context.js +0 -192
  310. package/dist/integrations/agent/execution-cascade.js +0 -566
  311. package/dist/integrations/agent/execution-definitions.js +0 -202
  312. package/dist/integrations/agent/execution-lowering.js +0 -841
  313. package/dist/integrations/agent/execution-preparation.js +0 -98
  314. package/dist/integrations/agent/inline-execution.js +0 -74
  315. package/dist/registry/create-provider-registry.js +0 -29
  316. package/dist/registry/pinned-request-helper.js +0 -247
  317. package/dist/registry/pinned-transport.js +0 -717
  318. package/dist/sources/providers/index.js +0 -14
  319. package/dist/storage/engines/sqlite-migrations.js +0 -271
  320. package/dist/storage/repositories/canaries-repository.js +0 -107
  321. package/dist/storage/repositories/embedding-salvage-repository.js +0 -184
  322. package/dist/storage/repositories/registry-cache.js +0 -113
  323. package/dist/tasks/scheduler-sync-preview.js +0 -52
  324. package/dist/workflows/freeze/resolve-steps.js +0 -86
  325. package/dist/workflows/freeze/source-freeze.js +0 -64
  326. package/dist/workflows/ir/compile.js +0 -321
  327. package/dist/workflows/ir/environment-v4.js +0 -330
  328. package/dist/workflows/ir/freeze-v4.js +0 -153
  329. package/dist/workflows/ir/schema-v4.js +0 -745
  330. package/dist/workflows/ir/schema.js +0 -354
  331. package/dist/workflows/program/schema.js +0 -78
  332. package/dist/workflows/runtime/checkin.js +0 -57
  333. package/dist/workflows/runtime/plan-classifier.js +0 -196
  334. package/dist/workflows/runtime/unit-checkin.js +0 -45
  335. package/dist/workflows/runtime/unit-phases.js +0 -20
  336. package/dist/workflows/schema.js +0 -4
  337. package/dist/workflows/source-ir/compile.js +0 -200
  338. package/dist/workflows/source-ir/program.js +0 -50
  339. package/dist/workflows/source-ir/result.js +0 -26
  340. package/dist/workflows/source-ir/schema.js +0 -786
  341. package/dist/workflows/source-ir/triggers.js +0 -79
  342. package/dist/workflows/source-ir/uses.js +0 -40
  343. package/dist/workflows/validator.js +0 -60
@@ -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);