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
package/CHANGELOG.md CHANGED
@@ -6,6 +6,766 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/).
6
6
 
7
7
  ## [Unreleased]
8
8
 
9
+ ## [0.9.17-alpha.5] - 2026-09-27
10
+
11
+ `akm show` works again for a memory that has a `.derived.md` child (835 of them
12
+ in one real bundle), and `akm bundle add --provider … --name` holds to the same
13
+ `--name` contract as every other add.
14
+
15
+ ### Fixed
16
+
17
+ - **`akm show` works for a memory that has a `.derived.md` child.** When
18
+ `memories/X.md` and `memories/X.derived.md` both existed, `akm show
19
+ memories/X`, with or without a `#fragment`, failed with
20
+ `RESOURCE_ALREADY_EXISTS` ("multiple physical owners"); `akm curate`
21
+ previewed such a memory from its description alone, and `akm curate --pack`
22
+ left it out. The index gives the derived child its own ref,
23
+ `memories/X.derived`, but the ref lookup also counted `X.derived.md` as a
24
+ file for `memories/X`. The lookup now follows the index: `memories/X` is
25
+ `X.md` and `memories/X.derived` is `X.derived.md`. A derived child whose
26
+ parent file is gone no longer answers for the parent's ref either, so it
27
+ cannot hide a real `X.md` in a lower-priority bundle. `akm lint` and
28
+ `--xref` / `--supersedes` validation still accept a ref to `memories/X`
29
+ when only `X.derived.md` remains. Broken since 0.9.7.
30
+ (`src/core/asset/asset-placement.ts`, `src/commands/lint/base-linter.ts`)
31
+ - **`akm bundle add --provider … --name` keeps the `--name` contract too.**
32
+ Since 0.9.17-alpha.4 an explicit `--name` that is not a legal bundle slug,
33
+ or is taken by another bundle, fails with exit 2, and re-adding a source
34
+ under a different name points at `akm bundle rename`. A declarative add
35
+ (`akm bundle add <target> --provider npm|git|website`) still replaced such
36
+ a name with a derived one and exited 0. It now fails the same way, before
37
+ any write. (`src/commands/sources/source-manage.ts`)
38
+
39
+ ## [0.9.17-alpha.4] - 2026-09-27
40
+
41
+ Search and curate are rebuilt on measured evidence. On a 221-query suite of real
42
+ akm queries judged for relevance, search nDCG@10 goes from 0.346 to 0.556 and
43
+ curate precision@5 from 0.350 to 0.551, with search p50 falling from 787 ms to
44
+ about 400 ms and a fresh index shrinking from 560 MB to 340 MB.
45
+
46
+ Upgrades stop breaking because the machinery that broke them is gone, not
47
+ because more was added: this release removes the scheduler-grant layer, the
48
+ filesystem transaction journals, the maintenance barrier and lock mutex, the
49
+ strict config schemas and their retired-key registry, and every per-key
50
+ config migration, and it lands with fewer lines in `src/` than 0.9.17-alpha.3.
51
+
52
+ ### Changed
53
+
54
+ - **Search ranks by reciprocal rank fusion of BM25 and document vectors.**
55
+ Two candidate lists, 100 each, are fused with equal weights (k = 60): BM25
56
+ over whole documents (`entries_fts`) matching any of the query's
57
+ non-stopword words (every word when the query has nothing else), and the
58
+ document vectors nearest to the query embedding. Equal scores are ordered by
59
+ ref, so the ranking depends only on the index and the query's embedding
60
+ (keyword-only runs of the suite reproduce exactly). Filters (`--type`,
61
+ `--from`, `--filter`, `--belief`, the default session exclusion, proposed
62
+ quality) and one-hit-per-file deduplication narrow the fused list without
63
+ reordering it. A hit's `score` is its fused score (at most 2/61 ≈ 0.033),
64
+ and `--detail full`'s `whyMatched` lists its rank in each list
65
+ (`lexical rank 3`, `vector rank 12`). On the retrieval suite (221 real
66
+ queries over a 23k-document snapshot, LLM-judged) nDCG@10 rises from 0.347
67
+ to 0.566 and P@5 from 0.347 to 0.558, level with the lab reference design;
68
+ every query class improves, questions (0.20 → 0.44) and long prompts
69
+ (0.24 → 0.52) most. End to end, process start included, search p50/p95
70
+ fell from 787/4130 ms to 341/830 ms. BM25 weighs the five columns
71
+ equally: the previous 10/5/3/2/1 weights measured 0.011 lower P@5.
72
+ (`src/indexer/search/db-search.ts`,
73
+ `src/indexer/search/ranking.ts`, `src/indexer/search/fts-query.ts`,
74
+ `src/storage/repositories/index-fts-repository.ts`.)
75
+ - **Queries are embedded the way the embedding model expects.** An embedding
76
+ profile picks query and document templates by model name: Qwen3-Embedding
77
+ gets its retrieval instruction on queries, nomic-embed `search_query: ` /
78
+ `search_document: `, the BGE English, mxbai and arctic models the
79
+ "Represent this sentence for searching relevant passages: " query prefix,
80
+ E5 `query: ` / `passage: `, and other models none; `embedding.queryTemplate`
81
+ and `embedding.documentTemplate` override the preset (`""` turns it off).
82
+ The document template is part of the embedding fingerprint, so a nomic or
83
+ E5 index re-embeds on the next `akm index`; Qwen3, BGE and the default local
84
+ model keep their vectors. Text reaches the embedder with its case: the query
85
+ is no longer lowercased, and an entry's embedded text keeps its case once
86
+ the entry is next re-indexed (`akm index --reembed` refreshes every vector
87
+ at once). The query embedding is requested before the keyword query runs,
88
+ and a search waits for it at most `embedding.queryTimeoutMs` (default 3000)
89
+ before serving keyword ranking alone with one warning — a hung endpoint
90
+ used to hold a search for up to 120 s. (`src/llm/embedders/profile.ts`,
91
+ `src/indexer/materialize-embeddings.ts`.)
92
+ - **Curate is one search.** `akm curate` takes the top `--limit` hits of the
93
+ fused search in order and enriches each with its preview, run details and
94
+ up to two graph-related support refs, so curate's items are search's top
95
+ hits (P@5 0.350 → 0.556 on the retrieval suite; p50/p95 1082/4148 ms →
96
+ 433/888 ms). The optional reranker (`search.curateRerank`, still off by
97
+ default) now reorders the top 30 fused candidates (`topN`, previously 8 but
98
+ applied only to the final `limit` items) and sends each as its name,
99
+ description and the start of its indexed content (2,000 characters in all)
100
+ instead of name and description. (`src/commands/read/curate.ts`.)
101
+ - **Vectors are stored once (index layout 25).** Each entry's vector lives
102
+ only in `embeddings`, and search scores every current-model row by cosine
103
+ similarity in JavaScript. The sqlite-vec mirror `entries_vec` is gone with
104
+ its repair pass, readiness flag and width bookkeeping: sqlite-vec cannot
105
+ load in the standalone binaries (`bun build --compile` does not bundle the
106
+ optional package), under Bun on macOS (the system SQLite refuses
107
+ extensions) or wherever the optional dependency is missing, so those
108
+ installs always searched the BLOB rows anyway, and a mirror that fell out
109
+ of step returned wrong neighbours without an error. The scan now reads
110
+ float32 views of the rows instead of copying each into an array: 85 ms per
111
+ query for 24k 1,024-dimension vectors in a fresh process, against 460 ms for
112
+ the old fallback and 41 ms for sqlite-vec. The first writable open drops
113
+ `entries_vec` (100 MB on that index) and the `embeddingDim` and
114
+ `vecFastPathReady` meta keys; dropping a vec0 table needs the extension, so
115
+ sqlite-vec stays an optional dependency for that alone, and an install
116
+ without it leaves the unread table in place. `semanticStatus` is `ready-js`
117
+ whenever every entry has a vector (`ready-vec` is gone), the `vecAvailable`
118
+ field leaves `akm index` and `akm info` output, setup no longer probes for
119
+ sqlite-vec, and `embedding.dimension` loses its 4,096 cap, which only the
120
+ vec0 column needed. (`src/storage/repositories/index-vec-repository.ts`,
121
+ `src/storage/repositories/index-schema.ts`,
122
+ `src/indexer/materialize-embeddings.ts`.)
123
+ - **The fragment full-text table is gone (index layout 25).** Nothing has
124
+ read `entry_fragments_fts` since fragments stopped competing as search
125
+ candidates, so an upsert no longer splits the body into fragment rows and
126
+ the first writable open drops the table (39 MB on a 24k-entry index).
127
+ `entry_fragments` stays: `akm show <ref>#akm-fragment-…` (#937) resolves the
128
+ selector from its stored safe Markdown.
129
+ (`src/storage/repositories/index-fts-repository.ts`,
130
+ `src/storage/repositories/index-schema.ts`.)
131
+ - **The embedding input is derived, not stored (index layout 25).**
132
+ `entries.search_text` held a third copy of every body (74 MB on a
133
+ 24k-entry index) only to feed the embedder and to notice when an entry's
134
+ vector went stale. The embedding pass now derives the text from
135
+ `document_json` when it embeds an entry, and `entries.embed_hash` keeps its
136
+ SHA-256: an upsert whose hash differs deletes the vector, exactly as a
137
+ changed `search_text` did. The first writable open hashes each stored
138
+ `search_text` before dropping the column, so every vector stays attached
139
+ until its entry's text really changes, and nothing is re-embedded by the
140
+ upgrade. (`src/storage/repositories/index-entries-repository.ts`,
141
+ `src/storage/repositories/index-vec-repository.ts`,
142
+ `src/storage/repositories/index-schema.ts`.)
143
+ - **`akm index` reclaims index.db's free pages.** Nothing ever VACUUMed
144
+ `index.db`, so every table an upgrade rebuilt or dropped stayed on disk as
145
+ free pages: a 24k-entry index measured 979 MB, 427 MB of it free, against
146
+ 560 MB for a fresh build of the same content. The run now ends with a
147
+ VACUUM when the writable open migrated the layout (it leaves
148
+ `index_meta.vacuumPending` for the next `akm index`, since the open may sit
149
+ inside a caller's transaction) and whenever more than half the pages are
150
+ free, the threshold and pass improve already apply to `state.db`
151
+ (`vacuumIfReclaimable`, formerly `vacuumStateDbIfReclaimable`). A busy
152
+ database skips the VACUUM instead of failing the run; each VACUUM prints
153
+ its page counts and appends an `index_db_vacuumed` event. With the three
154
+ layout-25 removals above, a fresh build of the 24k-entry retrieval snapshot
155
+ is 340 MB instead of 560 MB, and a copy of the 601 MB layout-24 build
156
+ migrates in 0.8 s with all 23,979 vectors kept byte for byte, then VACUUMs
157
+ to 377 MB. Retrieval is unchanged on the suite (search nDCG@10 0.5562 →
158
+ 0.5560, Δ −0.0002 [−0.0014, +0.0009]; P@5 0.5507 → 0.5517; curate P@5
159
+ identical), and search p50/p95 moved from 374/912 ms to 401/870 ms.
160
+ (`src/indexer/indexer.ts`, `src/storage/state-db-integrity.ts`.)
161
+ - **An index a newer akm wrote is refused, naming the upgrade.** Readers
162
+ used to serve a newer layout "as far as they could" and the writable open
163
+ continued at its own layout, setting the marker back so the two releases
164
+ alternated. A newer layout can lack a column an older reader selects —
165
+ layout 25 drops `entries.search_text` — so every opener now refuses it with
166
+ `INDEX_SCHEMA_INCOMPATIBLE` ("Upgrade akm to use this index.") and leaves
167
+ the file untouched; an older layout is still served as-is and migrated by
168
+ the next writable open, and `akm improve --dry-run` reports the refusal as
169
+ an incompatible snapshot. (`src/storage/repositories/index-connection.ts`,
170
+ `src/storage/repositories/index-schema.ts`.)
171
+ Downgrading to 0.9.17-alpha.3 or earlier is not supported for the index:
172
+ those releases select columns layout 25 dropped, so rebuild it with
173
+ `akm index --full` under the older release.
174
+ - **Scheduled rows no longer freeze the syncing shell's directories or PATH.**
175
+ A `--scheduler-context` descriptor now carries the resolved bundle path
176
+ (sync's ownership signal, #846) plus only the `AKM_CONFIG_DIR`,
177
+ `AKM_DATA_DIR`, `AKM_CACHE_DIR` and `AKM_STATE_DIR` values the process that
178
+ ran `task sync` had set explicitly; resolved defaults are left to resolve at
179
+ fire time, exactly as they do for an interactive command. It used to capture
180
+ every resolved directory and the whole PATH: one host's `task sync`, run from
181
+ inside a desktop app whose environment pointed `$STATE` at the app's own
182
+ config directory, froze that directory into eight cron rows on 2026-08-06,
183
+ every later sync preserved it, and the nightly improve run then held its
184
+ locks where no interactive command could see them. PATH moves into the
185
+ native artifact, where it is visible and editable: a `PATH=` line inside a
186
+ `# akm:env BEGIN`/`END` section written directly above the first akm task
187
+ block (cron applies it to the rows that follow it; akm rewrites the line on
188
+ every crontab write and removes it with the last task block), and an
189
+ `EnvironmentVariables` entry in each launchd plist. Task Scheduler runs a
190
+ task with the account's own environment and carries no PATH. Plain
191
+ `akm task sync` recomputes the descriptor on every run — an installed row
192
+ whose descriptor no longer matches is updated while its launcher is kept as
193
+ before (only `--rebind` moves that) — so one sync after upgrading rewrites
194
+ every row written under the old policy. Descriptors an older release wrote
195
+ still load, PATH included, until that sync. (`src/tasks/scheduler-invocation.ts`,
196
+ `src/tasks/backends/cron.ts`, `src/tasks/backends/launchd.ts`,
197
+ `src/tasks/scheduler-sync.ts`.)
198
+ - **The write path keeps only what its callers use** (`src/core/write-source.ts`,
199
+ 1,485 → 602 lines). The git transaction chain — publication identity capture,
200
+ path, worktree and commit snapshot validation, base-HEAD assertions,
201
+ transaction-commit discovery, a per-repo pending-mutation registry and a
202
+ plan/begin/publish API — lost its last callers when the proposal
203
+ transaction journals were removed and survived only because one test
204
+ imported it; its 15 exports and their private helpers are gone. A write to a
205
+ git-backed bundle now writes the file atomically inside the bundle root,
206
+ records the exact path, and the boundary commits exactly those paths and
207
+ pushes with `--force-with-lease`. A dirty or gitignored destination is no
208
+ longer refused: an ignored path stays local with a warning instead of the
209
+ command throwing after the file had already landed, and an upstream that
210
+ cannot be inspected during preparation warns instead of aborting. Path
211
+ containment, the symlink-escape refusal and the detached-HEAD refusal stay.
212
+ - **Readers tolerate everything older releases wrote.** No config object is
213
+ strict any more: a key this release does not know — retired, misspelled,
214
+ or written by a newer release — is kept in memory and named once
215
+ (`unknownConfigKeyPaths`, `src/core/config/config.ts`, found by walking
216
+ the schema), round-trips through ordinary writes so a newer release's
217
+ settings survive a downgrade, and is dropped only by `akm migrate apply`.
218
+ The retired-keys registry, its read shim, and the schema-compatibility
219
+ lint are removed; nothing needs registering for a key to be tolerated.
220
+ - **`akm migrate apply` has one config step.** `configFile`
221
+ (`normalizeConfigFile`) reads config.json through the same pipeline every
222
+ load runs (`configVersion` read, legacy source shape, `extraParams` lift)
223
+ and writes the current shape back under a backup, dropping unknown keys.
224
+ It replaces the per-key `configLegacySourceShape`, `configExtraParams`,
225
+ `configRetiredKeys` and `configSchedulerSourceIds` steps, the
226
+ `schedulerActivation` and `staleTxns` steps, and the `--host-local` mode. A
227
+ pending config lift is now `ready`, never a blocker for the other steps.
228
+ The `deadResidue` step also removes the transaction-journal,
229
+ maintenance-barrier, lock-mutex and version-stamp files older releases left
230
+ under `$DATA`, `$STATE` and `$CONFIG`, and runs whether or not a bundle is
231
+ configured.
232
+ - **Scheduling is one list.** `scheduler.enabled` holds the fully-qualified
233
+ refs this host schedules (`bundle//tasks/x`). It is still written in the
234
+ `{kind, ref, sourceId}` shape 0.9.16 reads, so that release keeps working
235
+ against a config this one wrote; either shape is read as the ref.
236
+ A config with no list at all (every release before 0.9.17) means "keep
237
+ what is installed": the first `akm task sync` (or `setup`, `task enable`,
238
+ `task disable`, `task add`) takes the akm-written rows already in the
239
+ native scheduler as the host's choice, writes the list, and says so;
240
+ `task sync --dry-run` reports it without writing. An explicit list, empty
241
+ or not, is never second-guessed. Grants, source identities, the
242
+ carry-forward, the scheduler-activation and source-id migrations and the
243
+ fire-time re-check are gone (`src/tasks/activation-config.ts`).
244
+ - **Proposal accept and revert write directly.** The asset file is written
245
+ (temp file + rename), committed through the ordinary write-target
246
+ boundary, then the proposal row and its event are recorded in one
247
+ state.db transaction and the file is indexed best-effort. A crash in
248
+ between leaves a re-acceptable pending proposal, nothing corrupt. The
249
+ filesystem transaction journals (`src/core/fs-txn.ts`) with their
250
+ recovery, quarantine, deferral and fencing are removed, along with the
251
+ `txn-quarantine`/`txn-awaiting-recovery` health advisories.
252
+ - **`akm bundle update` publishes, records the lock entry, then reindexes —
253
+ with no rollback transaction.** An update still fetches into a staging
254
+ directory beside the cache and audits the staged bytes for dangerous env
255
+ keys before anything goes live; a blocked or failed audit changes nothing.
256
+ It then publishes with one rename (a fast-forward for a writable Git
257
+ checkout), writes the lock entry, and reindexes. If the reindex fails, the
258
+ new content and lock entry stay for the next `akm index`, and the previous
259
+ install directory is kept. The config, staged-content, lockfile-byte and
260
+ checkout-HEAD fences and the lockfile compare-and-swap restore are gone, so
261
+ an update no longer fails with "changed concurrently" or "changed after its
262
+ staged bytes were audited": it already runs under the asset-mutation lease,
263
+ and Git refuses a fast-forward that would overwrite local work. A website
264
+ source refreshes through its mirror's own snapshot staging, so a killed
265
+ refresh still keeps the previous mirror.
266
+ - **A lock file is one `O_EXCL` create** (`src/core/file-lock.ts`). The
267
+ SQLite lock-operation mutex, the maintenance barrier (a lock guarding lock
268
+ registration) and its per-open activity registry — the source of the
269
+ lock-sidecar leak that grew `$STATE` by hundreds of megabytes — are
270
+ removed. `MAINTENANCE_BARRIER_BUSY` no longer exists; contention is
271
+ reported as `INDEX_DB_CONTENDED`, `STATE_DB_CONTENDED` or
272
+ `IMPROVE_LOCK_HELD`, as before.
273
+ - **Removed from 0.9.17-alpha:** startup version reconciliation
274
+ (`version-reconcile.json`), the akm-install enumerator and `akm upgrade
275
+ --version`/`--tag` with its other-install mover, the `version-reconcile`,
276
+ `scheduler-grants`, `scheduled-startup-failures` and `akm-installs` health
277
+ advisories, and the `akm info` `compat` manifest with its
278
+ `PLUGIN_PROTOCOL_VERSION`. `akm upgrade` is what it was in 0.9.16.
279
+ - **Documented the persisted-data compatibility contract.** Added
280
+ `docs/architecture/persisted-data-compat.md`: the four-sentence contract a
281
+ reader owes data an earlier release wrote, plus a per-format table (config,
282
+ `state.db`, `index.db`, task source, workflow IR, native scheduler rows,
283
+ proposal and task-history metadata, lock payloads, `.akm` residue) naming
284
+ where each is written, its version marker, its older/newer-data behavior,
285
+ and which gate covers it — with explicit `Gap:` notes where the code does
286
+ not meet the contract yet. Registered in `docs/architecture/README.md`.
287
+ `AGENTS.md`'s "Reading persisted data" section now points at this doc
288
+ instead of a deleted file.
289
+
290
+ - **Scheduler writes hold one lock and apply row by row.** `akm task sync`,
291
+ `add`, `enable`, `disable` and `prune --yes` hold one `O_EXCL` lock,
292
+ `$STATE/locks/scheduler.lock` (`src/tasks/scheduler-lock.ts`), for the
293
+ whole read–plan–write; a second scheduler command exits 75
294
+ (`SCHEDULER_LOCK_HELD`), and a lock left by a dead process is reclaimed.
295
+ Under it, sync reads the installed rows once and diffs by native id: a
296
+ missing row is installed, a changed row rewritten, a row whose source is
297
+ gone or no longer enabled removed. A row that fails to install or remove,
298
+ or two sources claiming one native id, is reported in `failures` (exit 1)
299
+ while every other row applies; the per-row compare-and-swap expectations
300
+ and whole-set rollback are gone. A task whose source stops parsing keeps
301
+ its installed row instead of being unscheduled by a YAML typo.
302
+ (`src/tasks/scheduler-sync.ts`, `src/commands/tasks/tasks.ts`)
303
+ - **`akm task add` is "write, enable, sync".** It validates the task and
304
+ refuses an id already scheduled from another bundle before writing
305
+ anything, then writes the source, adds the ref to `scheduler.enabled`
306
+ (unless `--disabled`) and syncs the bundle. When the row cannot be
307
+ installed, add fails naming the cause and the task stays written and
308
+ enabled for the next `akm task sync` to retry; it no longer restores the
309
+ prior source and rows byte-for-byte. `--force` with fewer schedules removes
310
+ the dropped schedules' rows through the same sync, and `--rebind` means
311
+ what it means for `task sync`.
312
+ - **Improve records what it tried in one ledger** (`improve_ledger`,
313
+ `src/storage/repositories/improve-ledger-repository.ts`). One row per
314
+ stash, ref and stage holds the last attempt, its outcome and when the ref
315
+ is next eligible, from one cadence table:
316
+
317
+ | Outcome | Next eligible | Lifted early by newer feedback? |
318
+ | --- | --- | --- |
319
+ | rejected, quality_rejected | 14 d reflect, 30 d distill, 7 d other stages | no |
320
+ | expired | 1 d | no |
321
+ | proposed, review_needed, unchanged, judged_no_action | 7 d | yes |
322
+ | accepted, failed | immediately | — |
323
+
324
+ Every stage reads it before any LLM call. It replaces proposal
325
+ fingerprints, the per-stage cooldowns, the distill reject files and the
326
+ event-timestamp cursors, which disagreed with one another (quality
327
+ rejections never reached the fingerprints; consolidate re-judged promoted
328
+ memories). Distill and consolidate now key by their input refs, so each
329
+ such input may be attempted once more after upgrading. Schema repair paces
330
+ itself with the ledger too, replacing its private 7-day cooldown and
331
+ 3-attempts-per-30-days cap. Every stage — reflect, distill, consolidate,
332
+ extract, triage, memory inference, graph extraction — runs through one
333
+ shared path (`src/commands/improve/stage.ts`): pick the runner, call the
334
+ model, judge the output, mint the proposal, record the usage.
335
+ - **`akm proposal drain` has one rule.** A proposal the quality judge passed
336
+ (a `staged` gate decision whose content hash still matches) is accepted, an
337
+ empty diff is rejected, and everything else goes to the judgment tier
338
+ (`processes.triage.judgment`) or waits for review. Extract and consolidate
339
+ proposals, which the `personal-stash` policy auto-accepted on size alone,
340
+ carry no judge stamp, so they now go to the judgment tier — or wait for
341
+ review when none is configured — instead of being accepted. The policies
342
+ and their flags are retired (see Removed). `--dry-run` now predicts what a
343
+ real drain does: a proposal whose target already holds its content (an
344
+ accept that wrote the file but was interrupted before recording it) is
345
+ reported as promoted, as the real drain finishes it, instead of as a
346
+ stale-target rejection.
347
+ - **State migration `028-improve-ledger` creates the ledger and drops six
348
+ tables.** It backfills the ledger from each ref's latest proposal and drops
349
+ `proposal_fingerprints`, `improve_gate_thresholds`, `proposal_fs_imports`,
350
+ `consolidation_judged`, `improve_cycle_metrics` and `canary_queries`.
351
+ Because it drops schema, the first open after upgrading copies the
352
+ database to `state.db.pre-028-improve-ledger.bak` before it runs.
353
+ - **Upgrading no longer rebuilds or re-embeds the search index (index layout
354
+ 24).** The first writable open applies a layout change in place — added
355
+ columns, and a one-time rebuild of the two full-text tables from the stored
356
+ entries (about 2–3 s for 24k entries); embeddings, utility scores, the
357
+ enrichment cache and the graph are never dropped, and only a corrupt file
358
+ is rebuilt from scratch (#865). Both FTS5 tables are contentless, so
359
+ indexed text is stored once (153 MB of a 980 MB index on a 23.9k-entry
360
+ stash; a SQLite older than 3.43 keeps the previous layout). Each vector
361
+ records its model (`embeddings.model`): a model change re-embeds only the
362
+ entries missing a vector for the configured model, per batch and
363
+ resumably, replacing the purge, the #955 re-embed canary and
364
+ `embedding_salvage`. `akm index --full` keeps unchanged entries' vectors,
365
+ and a one-file change in a large directory re-persists only that file.
366
+ Readers serve an older layout as-is and say so once on stderr. An akm
367
+ older than this release refuses a layout-24 index and asks to be upgraded.
368
+ - **`--verbose` embedding output lists each document's size without a
369
+ predicted batch number.** The per-batch lines already report every
370
+ provider request's document and token counts, and skipped documents are
371
+ listed at the end of the pass.
372
+ - **Workflow runs are never refused for their plan's version or hash.**
373
+ Markdown and the GitHub-shaped YAML subset compile straight to one plan
374
+ type, and new runs record plan `irVersion` 6. A stored plan that decodes
375
+ runs whatever release froze it — irVersion 4 and 5 plans are read
376
+ tolerantly, and a key this release does not know is ignored instead of
377
+ abandoning the run; one that does not decode is marked abandoned and `akm
378
+ workflow run <ref>` starts afresh; only a plan a newer akm froze is
379
+ refused, with "Upgrade akm" (`WORKFLOW_IR_VERSION_UNSUPPORTED` is gone).
380
+ One driver per run is a lock file,
381
+ `<data dir>/workflow-run-locks/<run id>.lock`: a second `akm workflow run`
382
+ exits 75 (`RUN_LEASE_HELD`) naming the holder's pid, and a dead pid's lock
383
+ is reclaimed at once — the database run lease, its heartbeat and the
384
+ check-ins are gone. Resume reuses every completed unit whatever its
385
+ recorded input hash, and warns once when the workflow file's sha256
386
+ differs from the one recorded at freeze, then continues on the frozen
387
+ plan. Executable identity (realpath, inode and hash captured at freeze,
388
+ checked at dispatch) is gone, so upgrading `claude` mid-run no longer
389
+ strands a run.
390
+ - **Every execution goes through three plain functions:** `resolveExecution`
391
+ → `buildExecution` → `runExecution` (`src/integrations/agent/execution.ts`,
392
+ `runner-dispatch.ts`), replacing a 12-hop pipeline across 14 modules — the
393
+ cascade planner, authorized-plan and provenance checks, lowerer registry
394
+ and dispatch lease. Two behaviour changes: credentials are read at each
395
+ dispatch, so a key rotated mid-run is used on the next call instead of a
396
+ snapshot taken at the start; and an explicit `engine: null` in a task,
397
+ workflow or command layer means "no preference here" and falls through to
398
+ `defaults.engine` instead of forcing the `opencode-sdk` fallback.
399
+ - **`state.db` opens on one connection.** The open creates the parent
400
+ directory, opens the file, applies the pragmas, reads the migration ledger
401
+ and runs every pending migration in one `BEGIN IMMEDIATE`; the read-only
402
+ preflight connection, the `/proc/self/fd` alias and the refusal of an empty
403
+ "unversioned" file are gone. Before a migration that drops schema runs on
404
+ an existing database, it is copied to `state.db.pre-<id>.bak`. Since any
405
+ open applies pending migrations, `akm health`'s `state-db-migrations` check
406
+ now reports what its own open applied (`evidence.applied`,
407
+ `evidence.backupPath`) and fails only when a migration could not be
408
+ applied.
409
+ - **`akm health` drops checks nothing acted on.** Removed: the
410
+ `task-log-backing` hard check, the `pool-saturation` advisory, the six
411
+ research advisories (`outcome-proxy-adequacy`, `outcome-proxy-dead`,
412
+ `salience-uniformity-collapse`, `enrichment-lane-minting`,
413
+ `improve-churn-ratio`, `collapse-churn-detector`) and the report's
414
+ coverage, degradation and minting rollups. The HTML report's embedded
415
+ `RUNS` data drops 11 per-run counters no chart or table read (scope mode,
416
+ consolidation `processed`/`failedChunks`/`totalChunks`, memory-inference
417
+ `considered`/`yieldRate`, graph-extraction `failures`, distill
418
+ `skipped`/`queued`/`llmFailed`, `orphansPurged`); `--group-by run` and
419
+ `--format md` are unchanged.
420
+ - **`configVersion` is read, never gated on.** A missing field or `"0.9.0"`
421
+ loads silently; any other value is named once and read as `0.9.0`.
422
+ `UNSUPPORTED_CONFIG_VERSION` and `src/core/config/config-version-shim.ts`
423
+ are gone.
424
+ - **`akm migrate` converts a task file in one step, whatever its version.**
425
+ One planner (`scripts/akm-migrate/migrate/task-files.ts`) takes a v2, v3 or
426
+ v4 file still carrying `schedule[].enabled` to v4 in one pass, with one
427
+ backup directory per run (`$DATA/backups/tasks/<ts>-<uuid>`); `akm migrate
428
+ status` reports one `taskFiles` section instead of
429
+ `taskV3Migration`/`taskV4Migration`. The per-generation steps, their
430
+ convergence checks and backup pruning, and the writer-relocation step are
431
+ gone.
432
+ - **Registry requests use plain `fetch()`.** DNS pinning — a Node child
433
+ process per request that resolved each registry host, rejected private
434
+ addresses and pinned the connection — is removed: a registry URL is the
435
+ built-in one or one an operator configured. `src/registry/network.ts`
436
+ retries network failures, timeouts, 429 and 5xx with backoff, caps the
437
+ body, and reports every failure as a classified error, never exit 70:
438
+ `REGISTRY_NOT_FOUND` and `REGISTRY_RESPONSE_INVALID` exit 1,
439
+ `REGISTRY_UNREACHABLE` exits 75, `REGISTRY_URL_INVALID` exits 78. A static
440
+ index whose `version` is not 2 or 3 is read with one warning instead of
441
+ refused.
442
+
443
+ ### Added
444
+
445
+ - **Upgrade rehearsal gate** (`tests/integration/upgrade-rehearsal/`,
446
+ `AKM_UPGRADE_REHEARSAL=1`): installs the previous published `akm-cli`
447
+ release as a real global npm package, drives it to build a realistic home
448
+ (a filesystem, git, website, and npm bundle; scheduled and manual tasks; a
449
+ synced fake crontab), then installs the candidate build OVER it in place —
450
+ the same prefix a real `npm i -g`/`bun add -g` upgrade replaces — and runs
451
+ the candidate against that home — `migrate status`/`apply`, `bundle list`
452
+ with every bundle confirmed enabled, `search`, `show`, plain `task sync`
453
+ (dry-run and real, no `--rebind`, as an upgrading user actually runs it),
454
+ executing the generated cron command and confirming it ran the candidate,
455
+ `health`, `improve --plan` — and finally installs a separate untouched copy
456
+ of the previous release and runs it back against the candidate-written
457
+ home. Wired into CI (`.github/workflows/ci.yml`'s new `upgrade-rehearsal`
458
+ job) and `tests/release-check.sh` (right after packing the release
459
+ candidate). `.github/workflows/ci.yml` also now runs on pushes to
460
+ `release/*` branches, which previously had no CI coverage at all.
461
+ It also proves the fix for the defect above (Fixed, below) two
462
+ ways: a new first assertion in the "previous"-origin suite runs
463
+ scheduled-a's generated cron command BEFORE any `migrate` call and
464
+ confirms `akm-migrate status --host-local` then reports `current` with no
465
+ manual step in between; and a second, dedicated origin,
466
+ `KNOWN_UPGRADE_ORIGINS`' fixed `"0.9.15"` (the last release before
467
+ source-bound scheduler grants), builds a minimal home whose crontab row
468
+ carries no host-local grant at all — the exact 2026-09-24 shape — and
469
+ confirms the candidate carries the grant forward and a plain `task sync`
470
+ afterward does not remove it.
471
+ - **`akm bundle rename <old> <new>`.** Renaming a bundle used to mean
472
+ hand-editing the `bundles` key in `config.json`, which stranded every
473
+ durable ref the tool had minted under the old id — the index and state
474
+ databases kept the old `<old>//` prefix while config named the new one
475
+ (the exact hand-rename signature `warnOnBundleRenameDrift` already
476
+ detected and warned about, with "there is no rekey command in 0.9.0").
477
+ `akm bundle rename` is that command: under the config lock it rewrites the
478
+ `bundles` key, `defaultBundle`/`defaultWriteTarget` when they name the old
479
+ id, and every `scheduler.enabled[].ref` with the old `//` prefix; then it
480
+ renames the lockfile entry, re-keys every indexed entry's
481
+ `bundle_id`/`item_ref` and the metadata-enrichment LLM cache's
482
+ `asset_ref` (in the same `index.db` write, so a rename can't land between
483
+ the two and strand the cache — the next `akm index` would otherwise treat
484
+ every renamed asset as stale and re-enrich it through the LLM from
485
+ scratch), and rewrites this tool's own state rows that name the old bundle
486
+ (`proposals.ref`, a pending proposal's `proposedTarget.source`, and
487
+ workflow `task_history.target_ref`). It then re-syncs native scheduler
488
+ rows under the new name (`akmTasksSync`, run from the command handler and
489
+ reported in the result's `taskSync` field, never thrown, since
490
+ config/index/state are already renamed by then), so a scheduled task or
491
+ workflow stops invoking `<old>//…` the moment the rename applies instead of
492
+ waiting on a manual `akm task sync`. `taskSync.ok` is `false` both when
493
+ the sync call itself fails and when it comes back with one or more
494
+ `taskSync.result.failures` — a binding that failed to prepare has already
495
+ lost its old native row and is not scheduled again until a retry, so
496
+ `akm bundle rename` never reports a partial re-sync as a clean one. Refs
497
+ inside the bundle's own CONTENT
498
+ (cross-references, a task's `uses:`, `supersededBy`) are reported, never
499
+ rewritten — the result's `contentRefs` lists the indexed files that still
500
+ spell the old prefix. `--dry-run` shows the full plan (row counts,
501
+ scheduler refs, content files, and the installed native scheduler rows a
502
+ real run's sync would replace) without writing anything.
503
+
504
+ ### Removed
505
+
506
+ - **Every ranking signal besides the two fused lists.** Search no longer
507
+ applies exact-name tiers, type, belief-state, tag, search-hint, alias,
508
+ description, metadata, graph, capture-mode, lesson-strength, pinned-fact or
509
+ project-context boosts, the utility multiplier, the relaxed-query score
510
+ ceiling, or the cosine floor on vector-only hits, and it no longer loads
511
+ the graph snapshot. On the retrieval suite plain whole-document BM25 alone
512
+ beat the boosted pipeline by 0.156 nDCG@10, and applying the belief-state
513
+ weights to the fused score lowered nDCG@10 by 0.010 [−0.020, −0.001], so
514
+ `--belief current` is the way to leave out contradicted or superseded
515
+ entries. Usage events and utility scores are still recorded (improve's
516
+ salience and graph extraction read them), and the graph still backs
517
+ `akm show`'s `related` list and curate's support refs.
518
+ - **The require-every-word keyword ladder and prefix matching.** The strict
519
+ AND query, its prefix-AND retry and the OR recovery behind them are gone
520
+ (OR matching measured 0.108 nDCG@10 better), so a word fragment such as
521
+ `dock` no longer matches `docker`.
522
+ - **Fragment hits in search.** Markdown fragments no longer compete as search
523
+ candidates (whole documents measured 0.059 nDCG@10 better), so search
524
+ returns whole-document refs and its hits drop `selectedRef`, `parentRef`,
525
+ `fragmentOrdinal`, `fragmentCount`, the fragment line and size fields and
526
+ `matchStage`; `akm show <ref>#<fragment>` still selects a section.
527
+ - **Curate's second-guessing of search:** the per-keyword fallback searches
528
+ and their max-score merge, the intent and type nudges, skill-family
529
+ collapse (and the family support refs it produced), and the close-score
530
+ comparator.
531
+ - **Retired options.** `akm search --no-project-context` now fails as an
532
+ unknown flag (exit 2). The config keys `search.minScore`,
533
+ `search.graphBoost.*` and `improve.utilityDecay.*` have no effect and are
534
+ kept as unknown keys. Search hits no longer carry the `graph` field, and
535
+ usage events no longer record `graphExtraction` attribution.
536
+ - **Guarded source reads around workflow runs.** `akm workflow run` no
537
+ longer records a read set of every source it touched or re-checks those
538
+ sources before publishing the run, so editing a command, task, script or
539
+ env file while a run is being created no longer fails creation; a source
540
+ that resolves outside its bundle is still refused. `akm workflow plan` no
541
+ longer prints a `read set:` block, and its JSON drops `sourceReadSet`. At
542
+ dispatch an env file is re-read from its recorded path (a changed key set
543
+ is still refused), so replacing or re-cloning the bundle directory no
544
+ longer fails a unit with "environment owner root physical identity
545
+ changed". The resume check that refused a run whose stored params row had
546
+ been edited is gone.
547
+ - **Drain policies.** `processes.triage.policy` and
548
+ `processes.triage.maxDiffLines` (config) and `akm proposal drain --policy`
549
+ / `--max-diff-lines` are retired with `drain-policies.ts`; the flags now
550
+ fail as unknown (exit 2) and the keys are kept as unknown config keys.
551
+ - **Improve machinery with no remaining reader:** the collapse detector with
552
+ its canary set (`scripts/refresh-canary-set.ts`) and cycle metrics, replay
553
+ selection, the outcome-proxy events, and the never-called anti-collapse
554
+ merge guards. Retired config keys (kept as unknown keys):
555
+ `processes.consolidate.antiCollapse.{maxGeneration, lexicalDiversityCheck,
556
+ mergeInformationFloor, minSpecificityRetention}`,
557
+ `processes.consolidate.contradictionDetection`,
558
+ `improve.salience.replayBudget` and `improve.collapseDetector`. Retired
559
+ events: `improve_salience_first_run`, `improve_replay_selected`,
560
+ `collapse_detector_alert`, `improve_cycle_metrics_purged`,
561
+ `outcome_proxy_dead` and `outcome_proxy_inverted`.
562
+
563
+ ### Fixed
564
+
565
+ - **Re-extracting an unchanged note now replaces its stored graph rows.**
566
+ `replaceStoredGraph` refreshed only a file's status, reason and run id when
567
+ its body hash was unchanged, so an extraction of the same body — after a
568
+ model or prompt change, or after a failed first attempt — never reached
569
+ `graph_file_entities` or `graph_file_relations`. One install had 1,389 files
570
+ marked `extracted` with no entity rows while `llm_enrichment_cache` held
571
+ their extractions. A file's rows are now rewritten whenever its entities or
572
+ relations differ from the stored ones, so the next graph pass refills such
573
+ files from the cache without a model call. (`src/indexer/db/graph-db.ts`)
574
+ - **A graph pass that stops early no longer shrinks the stored graph.** A
575
+ full scan wrote back only the files it reached, so a budget abort, a
576
+ failure-rate abort or `processes.graphExtraction.topN` deleted the stored
577
+ rows of every other file: the 2026-09-26 backfill hit its 4 h budget after
578
+ 3,358 of 15,165 eligible files, and that prefix became the whole graph. The
579
+ pass now keeps the rows of every eligible file it did not reach, and of a
580
+ file whose extraction attempt failed. It drops rows only for a file that
581
+ left the eligible set — deleted, emptied, now `inferred: true`, or of a type
582
+ no longer included — which candidate-scoped runs never did; a scan that
583
+ could not read part of the stash drops nothing. Because kept rows can come
584
+ from an older extractor, the sweep no longer reuses a stored graph node as a
585
+ cache hit: only `llm_enrichment_cache`, keyed by extractor, answers for the
586
+ current one. (`src/indexer/graph/graph-extraction.ts`)
587
+ - **`graph_meta` counts describe the stored rows.** The extraction pass
588
+ wrote counts from its in-memory graph (22,304 entities reported against
589
+ 15,833 stored on one install), and deleting entries overwrote them with raw
590
+ row counts. Each write now derives them from the stored rows, one meaning
591
+ each: stored files, files with entity rows, distinct case-folded entities and
592
+ distinct case-folded relations. The pass result, and with it the
593
+ `akm improve` summary, reports the same counts. The entries-delete recompute
594
+ and the in-memory graph deduplicator (`src/indexer/graph/graph-dedup.ts`)
595
+ are gone. (`src/indexer/db/graph-db.ts`,
596
+ `src/indexer/graph/graph-extraction.ts`,
597
+ `src/storage/repositories/index-entries-repository.ts`)
598
+ - **`akm health` counts graph-extracted files per run.** Its
599
+ `graphExtraction.extractedFiles` added the whole stored graph's file count
600
+ once per improve run in the window; it now adds the files each run
601
+ extracted, as `entities` and `relations` already did.
602
+ (`src/commands/health/improve-metrics.ts`)
603
+ - **`akm show`'s `related` refs no longer depend on index row order.** When
604
+ two entries index the same file, the ref shown for it was whichever row
605
+ SQLite returned last; the lowest concept id now wins, and shared entity
606
+ names are read in a fixed order. The ranking itself (most shared entities,
607
+ then path) was already deterministic. (`src/indexer/graph/graph-related.ts`)
608
+ - **`akm search`/`akm curate` no longer store a pasted credential verbatim in
609
+ `state.db`.** The Claude Code hook curates every user prompt, so a
610
+ credential pasted into a prompt (`PASSWORD=…`, `TOKEN=…`, `SECRET=…`, a
611
+ `Bearer` header, a JWT, a `ghp_…`/`xox…`/`AKIA…` token, a PEM private key, a
612
+ `user:pass@` URL, …) flowed straight into the query text and was persisted
613
+ as-is in both `usage_events.query` and the `events` table's
614
+ `metadata_json` — a scan of mined queries found 22 credential-like values
615
+ stored this way. `logSearchEvent`/`logCurateEvent` now redact the query
616
+ with `redactCredentialPatterns` (extended with the shapes above, plus a
617
+ `NAME=value`/`NAME: value` pass for names containing password, passwd,
618
+ secret, token, auth, credential(s), or an api/private key — the value is
619
+ replaced with `[REDACTED]`, the name is kept so queries stay useful for
620
+ evaluation) before either write, so a `show`/`select` event tracing back to
621
+ the search — which copies the search event's already-persisted `query`
622
+ metadata — inherits the same redacted text. Existing rows already written
623
+ are not rewritten. (`src/core/redaction.ts`, `src/commands/read/search.ts`,
624
+ `src/commands/read/curate.ts`)
625
+ - **`engines.<name>.supportsJsonSchema` on a `kind: "llm"` engine is a known
626
+ key again.** `LlmConnectionConfigSchema` declares it and `llm/client.ts`
627
+ reads it, but the named-engine object (`LlmEngineSchema`) never listed it,
628
+ so this release's unknown-key walk named it on every load and `akm migrate
629
+ apply` would have deleted a live setting from config.json.
630
+ (`src/core/config/schema/engines.ts`)
631
+ - **`akm migrate` finds the leaked activity registry where earlier releases
632
+ actually wrote it.** The `deadResidue` step looked for
633
+ `maintenance-activities/` under `$STATE`; the maintenance barrier created it
634
+ next to its own lock under `$DATA`, so the directory that had grown to
635
+ 229,943 four-kilobyte sidecars (927 MB) on one host was never reported or
636
+ removed. Both roots are checked, the registry is reported as one entry rather
637
+ than once per sidecar, and a directory already listed whole is not descended
638
+ into by the sidecar scan. (`scripts/akm-migrate/migrate/dead-residue.ts`)
639
+ - **`akm bundle add`'s `--name` is now a contract on every add path (local,
640
+ website, registry), not a hint.** An explicit `--name` that is not a legal
641
+ bundle slug, or that is already taken by a different bundle, used to fall
642
+ back silently — `deriveBundleId` minted a derived name, or a `-<hash>`
643
+ suffix — so `akm bundle add ... --name my.bundle` installed under a name
644
+ the caller never asked for, without saying so. It now fails with a
645
+ `UsageError` (exit 2) naming the rule, before any write (config, lock, or
646
+ network sync). Re-adding an already-installed ref under a *different*
647
+ `--name` than it already carries used to keep the existing key and say
648
+ nothing; it now fails the same way, naming the existing key and
649
+ `akm bundle rename <old> <new>`. A DERIVED name (no `--name` given) is
650
+ unaffected and keeps `deriveBundleId`'s forgiving `-<hash>` uniqueness
651
+ fallback. Every `akm bundle add` result (local, website, and registry) now
652
+ also carries `bundleId` (the resolved bundle key), and a registry add's
653
+ result always carries `registryId` (the registry install id) rather than
654
+ only when it happens to differ from `bundleId`, so a caller no longer has
655
+ to reconstruct the key from `sourceAdded`/`installed`.
656
+ - **`akm bundle add <registry ref> --name <name>` now keys the bundle by
657
+ `<name>`.** For npm, `github:` and Git refs, `--name` was accepted and then
658
+ dropped before the bundle key was derived, so the bundle was keyed by the
659
+ basename of its materialized cache directory instead — `extracted`, or
660
+ `extracted-<hash>` once that was taken — and its assets were only
661
+ addressable as `extracted//…`. The name now goes through the same
662
+ slug-legality and uniqueness rules as a local or website add. The install's
663
+ registry id is still recorded as `registryId`, so `akm bundle update` and
664
+ `akm bundle remove` keep resolving the original ref. Re-adding a ref that is
665
+ already installed keeps its existing key, as local and website re-adds do.
666
+ - **A registry bundle added without `--name` is keyed by its package or repo
667
+ name instead of `extracted`.** `akm bundle add npm:<pkg>` now creates bundle
668
+ `<pkg>` (`npm:@scope/pkg` → `pkg`), and `github:owner/repo` or a Git URL
669
+ ending in `/repo` creates `repo` — the mapping the bundle schema already
670
+ documented for `registryId`. The key used to come from the basename of the
671
+ cache directory the package was unpacked into, which is always `extracted`,
672
+ so every registry bundle after the first was `extracted-<hash>`. A dotted
673
+ or mixed-case name is slugged like a directory name (`Foo.js` → `foo-js`).
674
+ Bundles that are already installed keep their current key, including
675
+ `extracted`, because every recorded `extracted//…` ref depends on it.
676
+ - **A one-file change in a large directory no longer costs `akm index` half
677
+ an hour.** Both full-text tables keyed their per-entry deletes on
678
+ `entry_id`, an unindexed FTS5 column, so every upsert scanned the whole
679
+ full-text index, twice per entry per run. On a 23.9k-entry index, one
680
+ touched file in a flat `knowledge/` directory of 13.7k entries took 26
681
+ minutes (task run `2026-09-24T20-30-01-663Z`); on backup copies of that
682
+ index the same rescan took 31 minutes before this change and takes 38 s
683
+ after it. FTS rows are keyed by rowid, and the
684
+ first writable open after upgrading realigns an existing index in place,
685
+ about 10 s and ~1.1 GB peak memory at that size, with no index-generation
686
+ bump, so an older binary keeps reading it. A writable open realigns again
687
+ if an older binary sharing the generation has written rows since.
688
+ - **One bad scheduler-sync item, or one bad migration step, no longer fails
689
+ the whole operation.** `akm task sync` used to throw and abort the entire
690
+ reconciliation over one binding it could not reconcile or one bundle whose
691
+ sources failed to read; that binding or bundle is now reported in the sync
692
+ result's `failures: [{path, ref?, reason}]` (documented in
693
+ `docs/reference/cli.md`) while every other one still syncs (see Changed).
694
+ `akm-migrate`'s
695
+ `runMigration` (`scripts/akm-migrate/run-migrate.ts`) now runs every step
696
+ under its own catch too: a step's own throw (or, under `apply`, its
697
+ read-only fallback failing as well) is recorded in the plan's new
698
+ `failedSteps: [{step, error}]` and forces `status: "blocked"` instead of
699
+ ending the run with no plan at all — the remaining steps still run in
700
+ order. `akm migrate status|apply` (`scripts/akm-migrate/main.ts`) already
701
+ exits 1 for any blocked plan, so a poisoned step no longer exits the
702
+ internal-error code 70 with nothing printed.
703
+ - **The legacy `stashDir`/`sources[]`/`installed` config shape is persisted
704
+ by `akm migrate apply`, and an empty one no longer fails every command**
705
+ (#863). `migrateLegacySourceShape`
706
+ (`src/core/config/legacy-source-shape-shim.ts`) has always converted a
707
+ usable `stashDir`/`sources[]`/`installed` in memory on every load and told
708
+ the user to run `akm migrate apply` to make that stick, but nothing on disk
709
+ ever did; the migrator's `configFile` step now writes that current shape
710
+ back once, under a backup. Separately, through 0.9.16 and 0.9.17-alpha.3 a
711
+ config whose `sources` was `[]` (what 0.8.9's `akm source remove` writes
712
+ after the last source is removed) or whose `stashDir` was empty or
713
+ unusable failed every command with exit 78; it now loads, with the shim's
714
+ one-time warning.
715
+ - **A `version: 2` or `version: 3` task source reads and runs again instead
716
+ of failing closed on upgrade.** `e413af024` deleted the in-memory
717
+ v2/v3 -> v4 read shim on the argument that "untrusted source cannot carry
718
+ obsolete activation semantics" — but activation had already moved to
719
+ host-local `scheduler.enabled` in that same commit, so the shim never
720
+ carried activation in the first place, and deleting it just reintroduced
721
+ the exact upgrade break 0.9.4 originally shipped the shim to fix ("would
722
+ have broken every pre-0.9.4 scheduled task headlessly on upgrade").
723
+ `parseTaskSource` (`src/tasks/source/parse-task-source.ts`) once again
724
+ routes `version: 2`/`version: 3` through the SAME pure planners
725
+ `akm migrate apply` uses, entirely in memory, with a one-line stderr
726
+ deprecation warning (once per file per process) and no disk write; the
727
+ parsed document never carries a source-owned `enabled` field, since the
728
+ v3->v4 planner already never hoists `akm.enabled` or a schedule entry's
729
+ `enabled` key. Only a v2/v3 document the deterministic conversion itself
730
+ cannot resolve still fails with `TASK_SCHEMA_VERSION_UNSUPPORTED`, naming
731
+ the specific blocked reason. The same in-memory shim now also tolerates a
732
+ declared `version: 4` document whose `schedule[]` still carries a
733
+ per-entry `enabled` key — 0.9.15's v4 grammar accepted it (`akm task add
734
+ --disabled` wrote it), this release's does not, and without this the
735
+ upgrade break above recurs for every 0.9.15-authored scheduled task. The
736
+ key is stripped without ever being read — `enabled: false` cannot
737
+ suppress a granted task and `enabled: true` cannot schedule an ungranted
738
+ one, since activation stays host-local `scheduler.enabled`. `akm task
739
+ validate` reports such a file `converts` (`sourceVersion` still `4`)
740
+ instead of `valid`, since it read through the shim rather than the direct
741
+ v4 path.
742
+ - **Lock contention exits 75, like `state.db` contention.** Another process
743
+ holding `akm.lock`'s write sentinel (`LOCKFILE_CONTENDED`, was a config
744
+ error, exit 78) or the asset-mutation writer lease past its wait
745
+ (`ASSET_MUTATION_LEASE_HELD`, was an unclassified error, exit 70) is now a
746
+ retry-shortly `TransientError`, exit 75.
747
+ - **`akm health`'s `state.db` repair steps no longer corrupt the rebuilt
748
+ file.** `state-db-integrity` used to suggest `.dump` into a new file with
749
+ no writer stop; it now says to back up `state.db`, `.recover` it into
750
+ `state.new.db`, confirm that passes `quick_check`, stop every akm process,
751
+ delete `state.db-wal` and `state.db-shm`, then swap the new file in — a
752
+ leftover WAL replays onto the new database and corrupts it.
753
+ - **The package launcher (`dist/akm`) passes `--scheduler-context` through to
754
+ the CLI** instead of re-validating the descriptor with a stale copy of its
755
+ schema, which rejected every descriptor 0.9.17 writes.
756
+ (`scripts/node-runtime/akm`)
757
+ - **A `task_history` row with a malformed `engine` value decodes.** The
758
+ decoder used to reject the whole row when `engine` was present but not a
759
+ string or `null`; it now drops the bad value and decodes the rest, the
760
+ tolerance it already applied to every other unrecognized field.
761
+ (`src/storage/repositories/task-history-repository.ts`)
762
+ - **The LLM enrichment budget warning prints for every index run.** When the
763
+ metadata-enrichment pass ran out of its wall-clock budget during an index
764
+ another command started (`akm bundle update`, `akm setup`, `akm bundle
765
+ add`, improve's preflight, a read command's auto-index), it stopped
766
+ silently; it now prints the same "LLM enrichment budget exceeded" warning
767
+ `akm index` does.
768
+
9
769
  ## [0.9.17-alpha.3] - 2026-09-24
10
770
 
11
771
  ### Fixed