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