akm-cli 0.9.1 → 0.9.2-alpha.2

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 (350) hide show
  1. package/CHANGELOG.md +103 -28
  2. package/README.md +3 -1
  3. package/SECURITY.md +1 -1
  4. package/STABILITY.md +1 -1
  5. package/dist/akm +2 -2
  6. package/dist/akm-migrate +2 -2
  7. package/dist/assets/hints/cli-hints-full.md +14 -9
  8. package/dist/assets/improve-strategies/proactive-maintenance.json +1 -1
  9. package/dist/assets/improve-strategies/reflect-distill.json +1 -1
  10. package/dist/assets/models.json +35 -0
  11. package/dist/assets/stash-skeleton/facts/conventions/backlinks.md +3 -4
  12. package/dist/assets/stash-skeleton/facts/conventions/organization.md +1 -3
  13. package/dist/assets/tasks/core/extract.yml +6 -5
  14. package/dist/assets/tasks/core/improve.yml +6 -5
  15. package/dist/assets/tasks/core/index-refresh.yml +6 -5
  16. package/dist/assets/tasks/core/sync.yml +6 -5
  17. package/dist/assets/tasks/core/version-check.yml +6 -5
  18. package/dist/assets/tasks/improve/akm-graph-refresh-weekly.yml +6 -5
  19. package/dist/assets/tasks/improve/akm-improve-catchup.yml +6 -5
  20. package/dist/assets/tasks/improve/akm-improve-consolidate.yml +6 -5
  21. package/dist/assets/tasks/improve/akm-improve-frequent.yml +6 -5
  22. package/dist/assets/tasks/improve/akm-improve-nightly.yml +6 -5
  23. package/dist/cli/confirm.js +2 -2
  24. package/dist/cli/parse-args.js +3 -24
  25. package/dist/cli/retired-commands.js +1 -1
  26. package/dist/cli/shared.js +2 -2
  27. package/dist/cli.js +11 -9
  28. package/dist/commands/agent/agent-dispatch.js +55 -89
  29. package/dist/commands/agent/contribute-cli.js +12 -45
  30. package/dist/commands/command/builtin-action.js +32 -0
  31. package/dist/commands/command/command-cli.js +99 -0
  32. package/dist/commands/command/command-execution.js +308 -0
  33. package/dist/commands/command/execution-source-loader.js +176 -0
  34. package/dist/commands/command/portable-template.js +60 -0
  35. package/dist/commands/config-cli.js +10 -4
  36. package/dist/commands/env/env.js +4 -2
  37. package/dist/commands/feedback-cli.js +1 -1
  38. package/dist/commands/health/checks.js +241 -29
  39. package/dist/commands/health/html-report.js +0 -14
  40. package/dist/commands/health/report-view-model.js +0 -1
  41. package/dist/commands/health/surfaces.js +6 -7
  42. package/dist/commands/health/types.js +0 -2
  43. package/dist/commands/health.js +63 -18
  44. package/dist/commands/improve/collapse-detector.js +5 -6
  45. package/dist/commands/improve/consolidate.js +251 -214
  46. package/dist/commands/improve/distill/promote-memory.js +71 -34
  47. package/dist/commands/improve/distill/quality-gate.js +17 -5
  48. package/dist/commands/improve/distill.js +232 -155
  49. package/dist/commands/improve/eligibility.js +112 -79
  50. package/dist/commands/improve/execution.js +57 -0
  51. package/dist/commands/improve/extract-cli.js +5 -5
  52. package/dist/commands/improve/extract-prompt.js +64 -22
  53. package/dist/commands/improve/extract.js +608 -360
  54. package/dist/commands/improve/improve-strategies.js +43 -14
  55. package/dist/commands/improve/improve.js +249 -29
  56. package/dist/commands/improve/loop-stages.js +11 -17
  57. package/dist/commands/improve/memory/memory-contradiction-detect.js +90 -66
  58. package/dist/commands/improve/outcome-loop.js +22 -38
  59. package/dist/commands/improve/planner.js +134 -0
  60. package/dist/commands/improve/preparation.js +730 -409
  61. package/dist/commands/improve/reflect.js +386 -223
  62. package/dist/commands/improve/run-context.js +3 -4
  63. package/dist/commands/improve/salience.js +6 -58
  64. package/dist/commands/improve/session-asset.js +12 -12
  65. package/dist/commands/lint/index.js +101 -29
  66. package/dist/commands/migrate-cli.js +11 -69
  67. package/dist/commands/migration-tool.js +6 -9
  68. package/dist/commands/models-cli.js +27 -0
  69. package/dist/commands/proposal/drain.js +258 -186
  70. package/dist/commands/proposal/proposal-cli.js +32 -10
  71. package/dist/commands/proposal/proposal.js +2 -5
  72. package/dist/commands/proposal/propose.js +192 -172
  73. package/dist/commands/proposal/repository.js +54 -91
  74. package/dist/commands/proposal/validators/proposal-validators.js +9 -7
  75. package/dist/commands/read/curate.js +53 -22
  76. package/dist/commands/read/registry-search.js +25 -9
  77. package/dist/commands/read/remember-cli.js +14 -2
  78. package/dist/commands/read/search.js +10 -4
  79. package/dist/commands/read/show.js +139 -153
  80. package/dist/commands/registry-cli.js +16 -7
  81. package/dist/commands/remember.js +33 -18
  82. package/dist/commands/sources/add-cli.js +19 -178
  83. package/dist/commands/sources/bundle-cli.js +15 -3
  84. package/dist/commands/sources/dangerous-env-audit.js +135 -0
  85. package/dist/commands/sources/info.js +2 -1
  86. package/dist/commands/sources/installed-stashes.js +901 -177
  87. package/dist/commands/sources/schema-repair.js +174 -95
  88. package/dist/commands/sources/self-update.js +30 -74
  89. package/dist/commands/sources/source-add.js +3 -5
  90. package/dist/commands/sources/sources-cli.js +2 -15
  91. package/dist/commands/sources/update-transaction.js +220 -0
  92. package/dist/commands/tasks/tasks-cli.js +3 -3
  93. package/dist/commands/tasks/tasks.js +736 -317
  94. package/dist/commands/workflow-cli.js +2 -2
  95. package/dist/core/adapter/adapters/agent-skills-adapter.js +3 -0
  96. package/dist/core/adapter/adapters/akm-adapter.js +85 -35
  97. package/dist/core/adapter/adapters/akm-lint.js +54 -39
  98. package/dist/core/adapter/adapters/akm-metadata.js +45 -45
  99. package/dist/core/adapter/adapters/akm-task-adapter.js +32 -49
  100. package/dist/core/adapter/adapters/akm-workflow-adapter.js +38 -23
  101. package/dist/core/adapter/adapters/dotenv-adapter.js +30 -1
  102. package/dist/core/adapter/adapters/generic-files-adapter.js +11 -0
  103. package/dist/core/adapter/adapters/index.js +0 -9
  104. package/dist/core/adapter/adapters/llm-wiki-adapter.js +4 -0
  105. package/dist/core/adapter/adapters/okf-adapter.js +4 -0
  106. package/dist/core/adapter/adapters/opencode-adapter.js +5 -8
  107. package/dist/core/adapter/adapters/tool-dir-shared.js +63 -6
  108. package/dist/core/adapter/adapters/website-snapshot-adapter.js +4 -0
  109. package/dist/core/adapter/execution-source.js +308 -0
  110. package/dist/core/adapter/recognize-match.js +36 -13
  111. package/dist/core/adapter/registry.js +0 -9
  112. package/dist/core/asset/stash-meta.js +94 -4
  113. package/dist/core/common.js +6 -11
  114. package/dist/core/config/config-io.js +3 -3
  115. package/dist/core/config/config-schema.js +18 -40
  116. package/dist/core/config/config-sources.js +11 -21
  117. package/dist/core/config/config-walker.js +31 -13
  118. package/dist/core/config/config.js +23 -26
  119. package/dist/core/config/schema/engines.js +8 -7
  120. package/dist/core/config/schema/improve-processes.js +29 -5
  121. package/dist/core/config/schema/index-config.js +0 -27
  122. package/dist/core/config/schema/primitives.js +1 -23
  123. package/dist/core/config/schema/sources-bundles.js +13 -16
  124. package/dist/core/errors.js +2 -0
  125. package/dist/core/events.js +68 -32
  126. package/dist/core/extra-params.js +1 -0
  127. package/dist/core/improve-result.js +315 -0
  128. package/dist/core/lesson-lint.js +0 -6
  129. package/dist/core/maintenance-barrier.js +4 -4
  130. package/dist/core/network-policy.js +152 -0
  131. package/dist/core/paths.js +1 -1
  132. package/dist/core/recognition-util.js +4 -4
  133. package/dist/core/registry-url.js +456 -0
  134. package/dist/core/state/migrations.js +161 -47
  135. package/dist/core/state-db.js +453 -80
  136. package/dist/core/system-error.js +32 -0
  137. package/dist/core/time.js +2 -12
  138. package/dist/core/write-source.js +0 -18
  139. package/dist/execution/directory-identity.js +52 -0
  140. package/dist/execution/executable-identity.js +107 -0
  141. package/dist/execution/guarded-source.js +398 -0
  142. package/dist/execution/json.js +95 -0
  143. package/dist/{commands/health/types-session-log.js → execution/limits.js} +2 -1
  144. package/dist/execution/record.js +55 -0
  145. package/dist/execution/resolved-request.js +730 -0
  146. package/dist/execution/source.js +320 -0
  147. package/dist/indexer/bundle-identity-guard.js +5 -4
  148. package/dist/indexer/db/graph-db.js +33 -0
  149. package/dist/indexer/graph/graph-boost.js +3 -4
  150. package/dist/indexer/graph/graph-extraction.js +562 -373
  151. package/dist/indexer/index-written-assets.js +78 -39
  152. package/dist/indexer/indexer.js +471 -432
  153. package/dist/indexer/installations.js +6 -0
  154. package/dist/indexer/lookup/adapter-concept-owner.js +283 -0
  155. package/dist/indexer/materialize-embeddings.js +155 -0
  156. package/dist/indexer/passes/memory-inference.js +227 -174
  157. package/dist/indexer/passes/metadata.js +263 -118
  158. package/dist/indexer/scan/doc-to-entry.js +7 -10
  159. package/dist/indexer/scan/drain-dir.js +51 -23
  160. package/dist/indexer/search/db-search.js +156 -50
  161. package/dist/indexer/search/fts-query.js +40 -40
  162. package/dist/indexer/search/ranking.js +36 -1
  163. package/dist/indexer/search/search-attribution.js +3 -1
  164. package/dist/indexer/search/search-fields.js +23 -14
  165. package/dist/indexer/search/search-hit-enrichers.js +1 -1
  166. package/dist/indexer/search/search-source.js +7 -16
  167. package/dist/indexer/search/semantic-status.js +10 -1
  168. package/dist/indexer/usage/show-usage.js +105 -0
  169. package/dist/indexer/usage/usage-events.js +7 -2
  170. package/dist/indexer/walk/matchers.js +40 -10
  171. package/dist/indexer/walk/path-resolver.js +5 -2
  172. package/dist/indexer/walk/walker.js +20 -2
  173. package/dist/integrations/agent/builder-shared.js +3 -6
  174. package/dist/integrations/agent/conversation-fallback.js +16 -0
  175. package/dist/integrations/agent/engine-resolution.js +87 -87
  176. package/dist/integrations/agent/execution-cascade.js +566 -0
  177. package/dist/integrations/agent/execution-definitions.js +211 -0
  178. package/dist/integrations/agent/execution-lowering.js +811 -0
  179. package/dist/integrations/agent/execution-preparation.js +67 -0
  180. package/dist/integrations/agent/index.js +0 -2
  181. package/dist/integrations/agent/inline-execution.js +74 -0
  182. package/dist/integrations/agent/model-map.js +515 -0
  183. package/dist/integrations/agent/persona-fallback.js +30 -0
  184. package/dist/integrations/agent/request-lowering.js +186 -0
  185. package/dist/integrations/agent/runner-dispatch.js +230 -37
  186. package/dist/integrations/agent/runner.js +12 -83
  187. package/dist/integrations/harnesses/aider/agent-builder.js +8 -0
  188. package/dist/integrations/harnesses/aider/index.js +0 -1
  189. package/dist/integrations/harnesses/amazonq/agent-builder.js +8 -0
  190. package/dist/integrations/harnesses/amazonq/index.js +0 -1
  191. package/dist/integrations/harnesses/claude/agent-builder.js +14 -1
  192. package/dist/integrations/harnesses/claude/index.js +1 -5
  193. package/dist/integrations/harnesses/claude/session-log.js +3 -33
  194. package/dist/integrations/harnesses/codex/agent-builder.js +8 -0
  195. package/dist/integrations/harnesses/codex/index.js +0 -1
  196. package/dist/integrations/harnesses/copilot/agent-builder.js +8 -0
  197. package/dist/integrations/harnesses/copilot/index.js +0 -1
  198. package/dist/integrations/harnesses/gemini/agent-builder.js +8 -0
  199. package/dist/integrations/harnesses/gemini/index.js +0 -1
  200. package/dist/integrations/harnesses/index.js +4 -44
  201. package/dist/integrations/harnesses/opencode/agent-builder.js +16 -9
  202. package/dist/integrations/harnesses/opencode/index.js +0 -2
  203. package/dist/integrations/harnesses/opencode/session-log.js +14 -204
  204. package/dist/integrations/harnesses/opencode-sdk/harness.js +12 -1
  205. package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +40 -42
  206. package/dist/integrations/harnesses/openhands/agent-builder.js +8 -0
  207. package/dist/integrations/harnesses/openhands/index.js +0 -1
  208. package/dist/integrations/harnesses/pi/agent-builder.js +8 -0
  209. package/dist/integrations/harnesses/pi/index.js +0 -1
  210. package/dist/integrations/harnesses/shared.js +0 -1
  211. package/dist/integrations/harnesses/types.js +1 -3
  212. package/dist/integrations/lockfile.js +82 -79
  213. package/dist/integrations/session-logs/index.js +6 -17
  214. package/dist/integrations/session-logs/provider-base.js +1 -29
  215. package/dist/llm/client.js +10 -5
  216. package/dist/llm/embedder.js +6 -7
  217. package/dist/llm/embedders/local.js +37 -88
  218. package/dist/llm/embedders/types.js +1 -1
  219. package/dist/llm/graph-extract.js +75 -50
  220. package/dist/llm/index-passes.js +43 -5
  221. package/dist/llm/memory-infer.js +8 -6
  222. package/dist/llm/metadata-enhance.js +5 -3
  223. package/dist/llm/structured-call.js +122 -25
  224. package/dist/output/format-exempt.js +1 -1
  225. package/dist/output/render-registry.js +0 -16
  226. package/dist/output/renderers.js +12 -7
  227. package/dist/output/shapes/curate.js +1 -0
  228. package/dist/output/shapes/helpers.js +10 -2
  229. package/dist/output/shapes/passthrough.js +2 -0
  230. package/dist/output/text/command-format.js +31 -33
  231. package/dist/output/text/health-format.js +1 -29
  232. package/dist/output/text/migrate.js +6 -56
  233. package/dist/output/text/proposal-format.js +16 -1
  234. package/dist/output/text/workflow-format.js +16 -0
  235. package/dist/registry/network.js +279 -0
  236. package/dist/registry/pinned-request-helper.js +247 -0
  237. package/dist/registry/pinned-transport.js +717 -0
  238. package/dist/registry/providers/skills-sh.js +18 -6
  239. package/dist/registry/providers/static-index.js +20 -7
  240. package/dist/registry/resolve.js +53 -28
  241. package/dist/scripts/akm-migrate-node.js +19334 -52269
  242. package/dist/scripts/akm-migrate.js +19270 -51612
  243. package/dist/setup/registry-stash-loader.js +64 -20
  244. package/dist/setup/semantic-assets.js +9 -34
  245. package/dist/setup/setup.js +12 -30
  246. package/dist/setup/source-identity.js +17 -0
  247. package/dist/setup/steps/sources.js +36 -15
  248. package/dist/setup/steps/tasks.js +39 -11
  249. package/dist/sources/providers/git-provider.js +3 -3
  250. package/dist/sources/providers/npm.js +2 -2
  251. package/dist/sources/providers/provider-utils.js +4 -3
  252. package/dist/sources/providers/website.js +11 -7
  253. package/dist/sources/snapshot-fetchers/host-guard.js +9 -136
  254. package/dist/sources/snapshot-fetchers/website-ingest.js +25 -109
  255. package/dist/sources/website-url.js +73 -0
  256. package/dist/storage/engines/sqlite-migrations.js +81 -26
  257. package/dist/storage/managed-db.js +27 -24
  258. package/dist/storage/repositories/events-repository.js +3 -0
  259. package/dist/storage/repositories/index-connection.js +42 -10
  260. package/dist/storage/repositories/index-entries-repository.js +203 -229
  261. package/dist/storage/repositories/index-entry-mapper.js +8 -12
  262. package/dist/storage/repositories/index-entry-schema.js +255 -0
  263. package/dist/storage/repositories/index-fts-repository.js +64 -71
  264. package/dist/storage/repositories/index-llm-cache-repository.js +8 -13
  265. package/dist/storage/repositories/index-meta-repository.js +0 -11
  266. package/dist/storage/repositories/index-schema.js +74 -350
  267. package/dist/storage/repositories/index-utility-repository.js +12 -17
  268. package/dist/storage/repositories/index-vec-repository.js +56 -7
  269. package/dist/storage/repositories/proposals-repository.js +4 -127
  270. package/dist/storage/repositories/registry-cache.js +2 -1
  271. package/dist/storage/repositories/task-history-repository.js +20 -40
  272. package/dist/storage/repositories/workflow-runs-repository.js +228 -129
  273. package/dist/storage/sqlite-read-snapshot.js +148 -0
  274. package/dist/tasks/backends/cron.js +170 -42
  275. package/dist/tasks/backends/index.js +1 -1
  276. package/dist/tasks/backends/launchd.js +787 -202
  277. package/dist/tasks/backends/schtasks.js +282 -83
  278. package/dist/tasks/embedded.js +7 -7
  279. package/dist/tasks/frozen-script.js +50 -0
  280. package/dist/tasks/resolve-akm-bin.js +5 -1
  281. package/dist/tasks/runner.js +239 -251
  282. package/dist/tasks/runtime-v3.js +281 -0
  283. package/dist/tasks/scheduler-binding.js +272 -0
  284. package/dist/tasks/scheduler-invocation.js +57 -43
  285. package/dist/tasks/scheduler-sync.js +654 -0
  286. package/dist/tasks/source-v3.js +752 -0
  287. package/dist/tasks/standalone-script-entry.js +5 -0
  288. package/dist/tasks/task-id.js +29 -0
  289. package/dist/workflows/authoring/authoring.js +15 -32
  290. package/dist/workflows/exec/dispatch-redaction.js +14 -8
  291. package/dist/workflows/exec/exec-unit.js +7 -28
  292. package/dist/workflows/exec/frozen-judge.js +57 -89
  293. package/dist/workflows/exec/lowering-notices.js +23 -0
  294. package/dist/workflows/exec/native-executor.js +301 -458
  295. package/dist/workflows/exec/param-secrets.js +4 -3
  296. package/dist/workflows/exec/run-workflow.js +26 -32
  297. package/dist/workflows/exec/step-work.js +105 -109
  298. package/dist/workflows/exec/unit-dispatch.js +103 -27
  299. package/dist/workflows/exec/unit-writer.js +3 -3
  300. package/dist/workflows/exec/worktree.js +2 -2
  301. package/dist/workflows/ir/compile.js +86 -72
  302. package/dist/workflows/ir/environment-v4.js +328 -0
  303. package/dist/workflows/ir/freeze-v4.js +122 -0
  304. package/dist/workflows/ir/plan-hash.js +13 -7
  305. package/dist/workflows/ir/schema-v4.js +525 -0
  306. package/dist/workflows/ir/schema.js +25 -284
  307. package/dist/workflows/ir/source-freeze-v4.js +506 -0
  308. package/dist/workflows/parser.js +27 -24
  309. package/dist/workflows/program/schema.js +1 -2
  310. package/dist/workflows/renderer.js +42 -29
  311. package/dist/workflows/resource-limits.js +4 -5
  312. package/dist/workflows/runtime/agent-identity.js +11 -13
  313. package/dist/workflows/runtime/plan-classifier.js +8 -8
  314. package/dist/workflows/runtime/runs.js +27 -43
  315. package/dist/workflows/runtime/workflow-asset-loader.js +45 -205
  316. package/dist/workflows/source-files.js +373 -0
  317. package/dist/workflows/source-ir/compile.js +196 -0
  318. package/dist/workflows/source-ir/github-yaml.js +577 -0
  319. package/dist/workflows/source-ir/ordering.js +38 -0
  320. package/dist/workflows/source-ir/program.js +50 -0
  321. package/dist/workflows/source-ir/result.js +26 -0
  322. package/dist/workflows/source-ir/schema.js +772 -0
  323. package/dist/workflows/source-ir/semantics.js +242 -0
  324. package/dist/workflows/source-ir/uses.js +14 -0
  325. package/docs/README.md +2 -0
  326. package/docs/migration/README.md +3 -1
  327. package/docs/migration/release-notes/0.9.2.md +55 -0
  328. package/docs/migration/release-notes/README.md +5 -0
  329. package/docs/migration/v0.8-to-v0.9.md +76 -1077
  330. package/docs/migration/v0.9.0-troubleshooting.md +104 -516
  331. package/docs/migration/v0.9.1-to-v0.9.2.md +150 -0
  332. package/docs/reference/README.md +1 -0
  333. package/docs/reference/cli.md +230 -98
  334. package/docs/reference/configuration.md +159 -36
  335. package/docs/reference/data-and-telemetry.md +19 -1
  336. package/docs/reference/supported-formats.md +23 -3
  337. package/docs/reference/tasks.md +182 -0
  338. package/docs/reference/workflow-schema.md +91 -40
  339. package/docs/reference/workflows.md +33 -6
  340. package/package.json +10 -6
  341. package/schemas/akm-config.json +372 -224
  342. package/schemas/akm-task.json +324 -80
  343. package/schemas/akm-workflow.json +6 -9
  344. package/dist/core/migration-operation.js +0 -75
  345. package/dist/integrations/agent/model-aliases.js +0 -74
  346. package/dist/tasks/parser.js +0 -380
  347. package/dist/tasks/schema.js +0 -123
  348. package/dist/tasks/validator.js +0 -80
  349. package/dist/workflows/ir/freeze.js +0 -320
  350. package/dist/workflows/runtime/document-cache.js +0 -13
@@ -9,8 +9,7 @@
9
9
  * call per asset. Results are recorded as `schema_repair_invoked` events.
10
10
  *
11
11
  * This module is extracted from `improve.ts` to make the repair logic
12
- * independently testable and to use the `tryLlmFeature` seam rather than raw
13
- * `chatCompletion`.
12
+ * independently testable and to use the shared structured execution seam.
14
13
  */
15
14
  import fs from "node:fs";
16
15
  import path from "node:path";
@@ -18,12 +17,14 @@ import { assembleAsset } from "../../core/asset/asset-serialize.js";
18
17
  import { parseFrontmatter } from "../../core/asset/frontmatter.js";
19
18
  import { parseRefInput } from "../../core/asset/resolve-ref.js";
20
19
  import { authoringRulesForType } from "../../core/authoring-rules.js";
20
+ import { ConfigError } from "../../core/errors.js";
21
21
  import { appendEvent, readEvents } from "../../core/events.js";
22
22
  import { parseEmbeddedJsonResponse } from "../../core/parse.js";
23
23
  import { resolveStandardsContext } from "../../core/standards/resolve-standards-context.js";
24
24
  import { info } from "../../core/warn.js";
25
25
  import { resolveAssetPath } from "../../indexer/walk/path-resolver.js";
26
- import { chatCompletion } from "../../llm/client.js";
26
+ import { disposeLoweredExecutionDispatchLease } from "../../integrations/agent/execution-lowering.js";
27
+ import { callStructured, preflightStructuredLlmRunner } from "../../llm/structured-call.js";
27
28
  import { createProposal, isProposalSkipped } from "../proposal/repository.js";
28
29
  // ── Constants ────────────────────────────────────────────────────────────────
29
30
  /** Minimum gap between schema-repair attempts on the same asset. */
@@ -37,36 +38,23 @@ const SCHEMA_REPAIR_COOLDOWN_MS = 7 * 24 * 60 * 60 * 1000; // 7 days
37
38
  */
38
39
  const SCHEMA_REPAIR_MAX_ATTEMPTS = 3;
39
40
  const SCHEMA_REPAIR_WINDOW_MS = 30 * 24 * 60 * 60 * 1000; // 30 days
40
- // ── Main ─────────────────────────────────────────────────────────────────────
41
- /**
42
- * Run the schema-repair loop for a batch of validation failures.
43
- * Returns a list of per-asset outcome records and the set of refs whose live
44
- * files were repaired. Queued proposals never count as live repairs.
45
- */
46
- export async function runSchemaRepairPass(failures, options) {
47
- const repairs = [];
48
- const repairedRefs = new Set();
49
- const { startMs, budgetMs, llmConfig, stashDir, findFilePath = defaultFindFilePath, isLessonCandidateFn = defaultIsLessonCandidate, chatFn = chatCompletion, } = options;
50
- if (!stashDir) {
51
- throw new Error("runSchemaRepairPass requires stashDir so repairs route through the proposal queue");
52
- }
41
+ async function classifySchemaRepairs(args) {
42
+ const { failures, startMs, budgetMs, stashDir, findFilePath, isLessonCandidateFn, repairs } = args;
43
+ const eligibleRepairs = [];
44
+ const pendingErrors = [];
53
45
  for (const failure of failures) {
54
46
  if (Date.now() - startMs >= budgetMs)
55
47
  break;
56
- // Cooldown: skip repair if we ran it successfully recently.
57
48
  const recentRepairs = readEvents({ type: "schema_repair_invoked", ref: failure.ref });
58
49
  const lastRepair = recentRepairs.events
59
- .filter((e) => e.metadata?.outcome === "queued")
50
+ .filter((event) => event.metadata?.outcome === "queued")
60
51
  .sort((a, b) => new Date(b.ts ?? 0).getTime() - new Date(a.ts ?? 0).getTime())[0];
61
52
  if (lastRepair?.ts && Date.now() - new Date(lastRepair.ts).getTime() < SCHEMA_REPAIR_COOLDOWN_MS) {
62
53
  repairs.push({ ref: failure.ref, reason: failure.reason, outcome: "skipped" });
63
54
  continue;
64
55
  }
65
- // O-6 / #379: Cap total attempts at SCHEMA_REPAIR_MAX_ATTEMPTS per SCHEMA_REPAIR_WINDOW_MS.
66
- // Prevents indefinite nightly re-repair of assets whose source is genuinely ambiguous.
67
- // After the cap is reached, the asset is skipped until the window rolls over.
68
56
  const windowStart = Date.now() - SCHEMA_REPAIR_WINDOW_MS;
69
- const attemptsInWindow = recentRepairs.events.filter((e) => e.ts !== undefined && new Date(e.ts).getTime() >= windowStart).length;
57
+ const attemptsInWindow = recentRepairs.events.filter((event) => event.ts !== undefined && new Date(event.ts).getTime() >= windowStart).length;
70
58
  if (attemptsInWindow >= SCHEMA_REPAIR_MAX_ATTEMPTS) {
71
59
  repairs.push({
72
60
  ref: failure.ref,
@@ -77,32 +65,23 @@ export async function runSchemaRepairPass(failures, options) {
77
65
  continue;
78
66
  }
79
67
  const filePath = await findFilePath(failure.ref, stashDir);
80
- if (!filePath) {
81
- repairs.push({ ref: failure.ref, reason: failure.reason, outcome: "skipped" });
82
- continue;
83
- }
84
- if (path.extname(filePath).toLowerCase() !== ".md") {
68
+ if (!filePath || path.extname(filePath).toLowerCase() !== ".md") {
85
69
  repairs.push({ ref: failure.ref, reason: failure.reason, outcome: "skipped" });
86
70
  continue;
87
71
  }
88
72
  try {
89
73
  const raw = fs.readFileSync(filePath, "utf8");
90
- const fm = parseFrontmatter(raw);
74
+ const frontmatter = parseFrontmatter(raw);
91
75
  const missingFields = [];
92
- if (!fm.data.description)
76
+ if (!frontmatter.data.description)
93
77
  missingFields.push("description");
94
- if (isLessonCandidateFn(failure.ref) && !fm.data.when_to_use)
78
+ if (isLessonCandidateFn(failure.ref) && !frontmatter.data.when_to_use)
95
79
  missingFields.push("when_to_use");
96
80
  if (missingFields.length === 0) {
97
81
  repairs.push({ ref: failure.ref, reason: failure.reason, outcome: "skipped" });
98
82
  continue;
99
83
  }
100
84
  const fieldList = missingFields.join(" and ");
101
- info(`[improve] schema-repair ${failure.ref} (${fieldList})`);
102
- const bodyPreview = (fm.content ?? raw).slice(0, 2000);
103
- // Standards "rulebook" for this target — wiki schema (wiki page) or stash
104
- // convention/meta facts (non-wiki asset). `resolveStandardsContext`
105
- // dispatches on the ref.
106
85
  const standardsContext = resolveStandardsContext(failure.ref, stashDir);
107
86
  const standardsSection = standardsContext.trim()
108
87
  ? `\n\nStandards to follow (the rulebook for this target):\n${standardsContext.trim()}`
@@ -110,75 +89,175 @@ export async function runSchemaRepairPass(failures, options) {
110
89
  const assetType = parseRefInput(failure.ref).type;
111
90
  const authoringRules = authoringRulesForType(assetType);
112
91
  const authoringRulesSection = authoringRules ? `\n\n${authoringRules}` : "";
113
- const llmResponse = await chatFn(llmConfig, [
114
- {
115
- role: "system",
116
- content: `You generate concise asset frontmatter fields. Respond with a JSON object containing only the missing fields. No prose, no markdown fences.`,
117
- },
118
- {
119
- role: "user",
120
- content: `Generate the missing frontmatter fields (${fieldList}) for this ${assetType} asset. Return ONLY valid JSON like {"description": "...", "when_to_use": "..."}${standardsSection}${authoringRulesSection}\n\n${bodyPreview}`,
121
- },
122
- ]);
123
- const parsed = parseEmbeddedJsonResponse(llmResponse.trim());
124
- if (!parsed) {
125
- repairs.push({
126
- ref: failure.ref,
127
- reason: failure.reason,
128
- outcome: "error",
129
- error: "LLM returned unparseable JSON for schema repair",
130
- });
131
- continue;
132
- }
133
- const newFm = { ...fm.data };
134
- if (parsed.description)
135
- newFm.description = parsed.description;
136
- if (parsed.when_to_use)
137
- newFm.when_to_use = parsed.when_to_use;
138
- const newContent = assembleAsset(newFm, fm.content);
139
- // M-3 / #387: Route through proposal queue instead of writing directly to
140
- // disk. This restores akm's safety invariant — the proposal queue is the
141
- // only path to a committed asset write. LLM-generated `description` /
142
- // `when_to_use` fields can be incorrect; routing through the queue makes
143
- // them human-reviewable before they affect search ranking and curate hints.
144
- // mem0 open gaps (arXiv:2504.19413) — any LLM write to a memory field
145
- // should be human-reviewable.
146
- const proposalResult = createProposal(stashDir, {
147
- ref: failure.ref,
148
- source: "schema-repair",
149
- // §23.6 fingerprint model-id term (WI-6.4).
150
- modelId: llmConfig.model,
151
- payload: {
152
- content: newContent,
153
- ...(Object.keys(newFm).length > 0 ? { frontmatter: newFm } : {}),
154
- },
92
+ eligibleRepairs.push({
93
+ failure,
94
+ frontmatter,
95
+ fieldList,
96
+ messages: [
97
+ {
98
+ role: "system",
99
+ content: "You generate concise asset frontmatter fields. Respond with a JSON object containing only the missing fields. No prose, no markdown fences.",
100
+ },
101
+ {
102
+ role: "user",
103
+ content: `Generate the missing frontmatter fields (${fieldList}) for this ${assetType} asset. Return ONLY valid JSON like {"description": "...", "when_to_use": "..."}${standardsSection}${authoringRulesSection}\n\n${(frontmatter.content ?? raw).slice(0, 2000)}`,
104
+ },
105
+ ],
155
106
  });
156
- if (isProposalSkipped(proposalResult)) {
157
- info(`[improve] schema-repair proposal skipped for ${failure.ref}: ${proposalResult.message}`);
158
- repairs.push({ ref: failure.ref, reason: failure.reason, outcome: "skipped" });
159
- continue;
160
- }
161
- info(`[improve] schema-repair queued: ${failure.ref} (proposal id: ${proposalResult.id})`);
107
+ }
108
+ catch (error) {
109
+ pendingErrors.push({ ref: failure.ref, reason: failure.reason, outcome: "error", error: String(error) });
110
+ }
111
+ }
112
+ return { eligibleRepairs, pendingErrors };
113
+ }
114
+ // ── Main ─────────────────────────────────────────────────────────────────────
115
+ /**
116
+ * Run the schema-repair loop for a batch of validation failures.
117
+ * Returns a list of per-asset outcome records and the set of refs whose live
118
+ * files were repaired. Queued proposals never count as live repairs. Invalid
119
+ * symbolic credentials reject with {@link ConfigError} before any repair
120
+ * proposal or per-ref event is written.
121
+ */
122
+ export async function runSchemaRepairPass(failures, options) {
123
+ const repairs = [];
124
+ const repairedRefs = new Set();
125
+ const { startMs, budgetMs, stashDir, findFilePath = defaultFindFilePath, isLessonCandidateFn = defaultIsLessonCandidate, chatFn, } = options;
126
+ const llmRunner = options.llmRunner ?? null;
127
+ if (!llmRunner)
128
+ throw new Error("runSchemaRepairPass requires a resolved LLM runner");
129
+ if (!stashDir) {
130
+ throw new Error("runSchemaRepairPass requires stashDir so repairs route through the proposal queue");
131
+ }
132
+ // Classify the entire batch using read-only work before credential lookup or
133
+ // proposal/event persistence. A missing required credential therefore aborts
134
+ // the eligible batch atomically instead of leaving an earlier proposal behind.
135
+ const { eligibleRepairs, pendingErrors } = await classifySchemaRepairs({
136
+ failures,
137
+ startMs,
138
+ budgetMs,
139
+ stashDir,
140
+ findFilePath,
141
+ isLessonCandidateFn,
142
+ repairs,
143
+ });
144
+ if (eligibleRepairs.length === 0) {
145
+ for (const repair of pendingErrors) {
162
146
  appendEvent({
163
147
  eventType: "schema_repair_invoked",
164
- ref: failure.ref,
165
- metadata: { outcome: "queued", reason: failure.reason, proposalId: proposalResult.id },
166
- });
167
- repairs.push({
168
- ref: failure.ref,
169
- reason: failure.reason,
170
- outcome: "queued",
171
- proposalId: proposalResult.id,
148
+ ref: repair.ref,
149
+ metadata: { outcome: "error", reason: repair.reason, error: repair.error },
172
150
  });
151
+ repairs.push(repair);
173
152
  }
174
- catch (e) {
153
+ return { repairs, repairedRefs };
154
+ }
155
+ const dispatchLease = await preflightStructuredLlmRunner(llmRunner);
156
+ try {
157
+ for (const repair of pendingErrors) {
175
158
  appendEvent({
176
159
  eventType: "schema_repair_invoked",
177
- ref: failure.ref,
178
- metadata: { outcome: "error", reason: failure.reason, error: String(e) },
160
+ ref: repair.ref,
161
+ metadata: { outcome: "error", reason: repair.reason, error: repair.error },
179
162
  });
180
- repairs.push({ ref: failure.ref, reason: failure.reason, outcome: "error", error: String(e) });
163
+ repairs.push(repair);
181
164
  }
165
+ for (const { failure, frontmatter: fm, fieldList, messages } of eligibleRepairs) {
166
+ let loweringNotices = [];
167
+ const noticeFields = () => loweringNotices.length > 0 ? { notices: loweringNotices } : {};
168
+ try {
169
+ info(`[improve] schema-repair ${failure.ref} (${fieldList})`);
170
+ const llmResponse = await callStructured({
171
+ feature: "schema_repair",
172
+ runner: llmRunner,
173
+ lease: dispatchLease,
174
+ messages,
175
+ ...(chatFn ? { request: { chat: chatFn } } : {}),
176
+ onNotices: (value) => {
177
+ loweringNotices = value;
178
+ },
179
+ parse: (rawResponse) => rawResponse ?? "",
180
+ onError: () => "",
181
+ fallback: "",
182
+ });
183
+ const parsed = parseEmbeddedJsonResponse(llmResponse.trim());
184
+ if (!parsed) {
185
+ repairs.push({
186
+ ref: failure.ref,
187
+ reason: failure.reason,
188
+ outcome: "error",
189
+ error: "LLM returned unparseable JSON for schema repair",
190
+ ...noticeFields(),
191
+ });
192
+ continue;
193
+ }
194
+ const newFm = { ...fm.data };
195
+ if (parsed.description)
196
+ newFm.description = parsed.description;
197
+ if (parsed.when_to_use)
198
+ newFm.when_to_use = parsed.when_to_use;
199
+ const newContent = assembleAsset(newFm, fm.content);
200
+ // M-3 / #387: Route through proposal queue instead of writing directly to
201
+ // disk. This restores akm's safety invariant — the proposal queue is the
202
+ // only path to a committed asset write. LLM-generated `description` /
203
+ // `when_to_use` fields can be incorrect; routing through the queue makes
204
+ // them human-reviewable before they affect search ranking and curate hints.
205
+ // mem0 open gaps (arXiv:2504.19413) — any LLM write to a memory field
206
+ // should be human-reviewable.
207
+ const proposalResult = createProposal(stashDir, {
208
+ ref: failure.ref,
209
+ source: "schema-repair",
210
+ // §23.6 fingerprint model-id term (WI-6.4).
211
+ modelId: llmRunner.connection.model,
212
+ payload: {
213
+ content: newContent,
214
+ ...(Object.keys(newFm).length > 0 ? { frontmatter: newFm } : {}),
215
+ },
216
+ });
217
+ if (isProposalSkipped(proposalResult)) {
218
+ info(`[improve] schema-repair proposal skipped for ${failure.ref}: ${proposalResult.message}`);
219
+ repairs.push({ ref: failure.ref, reason: failure.reason, outcome: "skipped", ...noticeFields() });
220
+ continue;
221
+ }
222
+ info(`[improve] schema-repair queued: ${failure.ref} (proposal id: ${proposalResult.id})`);
223
+ appendEvent({
224
+ eventType: "schema_repair_invoked",
225
+ ref: failure.ref,
226
+ metadata: {
227
+ outcome: "queued",
228
+ reason: failure.reason,
229
+ proposalId: proposalResult.id,
230
+ ...noticeFields(),
231
+ },
232
+ });
233
+ repairs.push({
234
+ ref: failure.ref,
235
+ reason: failure.reason,
236
+ outcome: "queued",
237
+ proposalId: proposalResult.id,
238
+ ...noticeFields(),
239
+ });
240
+ }
241
+ catch (e) {
242
+ if (e instanceof ConfigError)
243
+ throw e;
244
+ appendEvent({
245
+ eventType: "schema_repair_invoked",
246
+ ref: failure.ref,
247
+ metadata: { outcome: "error", reason: failure.reason, error: String(e), ...noticeFields() },
248
+ });
249
+ repairs.push({
250
+ ref: failure.ref,
251
+ reason: failure.reason,
252
+ outcome: "error",
253
+ error: String(e),
254
+ ...noticeFields(),
255
+ });
256
+ }
257
+ }
258
+ }
259
+ finally {
260
+ disposeLoweredExecutionDispatchLease(dispatchLease);
182
261
  }
183
262
  return { repairs, repairedRefs };
184
263
  }
@@ -7,29 +7,17 @@ import fs from "node:fs";
7
7
  import path from "node:path";
8
8
  import { fetchWithRetry, IS_WINDOWS, ResponseTooLargeError, readBodyWithByteCap, readChunkWithDeadline, } from "../../core/common.js";
9
9
  import { ConfigError } from "../../core/errors.js";
10
+ import { upgradeHistoricalStateDatabase } from "../../core/state-db.js";
10
11
  import { warn } from "../../core/warn.js";
11
12
  import { githubHeaders } from "../../integrations/github.js";
12
13
  import { getDirname, mainPath, semverOrder } from "../../runtime.js";
13
- import { resolveAkmInvocation } from "../../tasks/resolve-akm-bin.js";
14
14
  const REPO = "itlackey/akm";
15
15
  const DEFAULT_PACKAGE_NAME = "akm-cli";
16
16
  const NODE_MODULES_SEGMENT = "/node_modules/";
17
17
  const BUN_GLOBAL_INSTALL_PATTERN = /(^|\/)\.bun\/(?:[^/]+\/)+node_modules\//;
18
18
  const PNPM_GLOBAL_INSTALL_PATTERN = /(^|\/)(?:pnpm\/global|\.pnpm-global)(?:\/\d+)?\/node_modules\//;
19
- const MIGRATION_CONTRACT_VERSION = "0.9.0-rc.0";
20
- const MIGRATION_CONTRACT_BLOCKED_MESSAGE = "AKM 0.8 does not implement the migrate command or the --migration-config upgrade contract, so self-update cannot safely cross into 0.9. " +
21
- "Prepare the 0.9 config and an independent backup, install or stage the 0.9 binary manually, then use the new binary to run: " +
22
- "akm migrate apply --config <prepared-0.9.json>. See docs/migration/v0.8-to-v0.9.md.";
23
19
  const MAX_BINARY_DOWNLOAD_BYTES = 256 * 1024 * 1024;
24
20
  const MAX_CHECKSUM_METADATA_BYTES = 1024 * 1024;
25
- export function defaultMigrationCommand(preparedConfigPath) {
26
- const configArgs = preparedConfigPath ? ["--config", preparedConfigPath] : [];
27
- return {
28
- preflight: (akmBin) => runRequiredCommand(akmBin, ["migrate", "status"], "Migration preflight"),
29
- stagedPreflight: (akmBin) => runRequiredCommand(akmBin, ["migrate", "status", ...configArgs], "Staged migration preflight"),
30
- apply: (akmBin) => runRequiredCommand(akmBin, ["migrate", "apply", ...configArgs], "Migration apply"),
31
- };
32
- }
33
21
  /**
34
22
  * Bounds on the binary body read. `fetchWithTimeout`'s timer only covers
35
23
  * time-to-HEADERS — it is cleared the moment `fetch` resolves — so without
@@ -184,7 +172,6 @@ export async function performUpgrade(check, opts, dependencies) {
184
172
  const { currentVersion, latestVersion, installMethod } = check;
185
173
  const force = opts?.force === true;
186
174
  const skipPostUpgrade = opts?.skipPostUpgrade === true;
187
- const migration = dependencies?.migration ?? defaultMigrationCommand(opts?.migrationConfig);
188
175
  // All install methods can short-circuit here unless the user explicitly forces an upgrade.
189
176
  if (!check.updateAvailable && !force) {
190
177
  return {
@@ -195,11 +182,6 @@ export async function performUpgrade(check, opts, dependencies) {
195
182
  message: `akm v${currentVersion} is already the latest version`,
196
183
  };
197
184
  }
198
- if (latestVersion &&
199
- semverOrder(currentVersion, MIGRATION_CONTRACT_VERSION) < 0 &&
200
- semverOrder(latestVersion, MIGRATION_CONTRACT_VERSION) >= 0) {
201
- throw new ConfigError(MIGRATION_CONTRACT_BLOCKED_MESSAGE, "UPGRADE_BLOCKED");
202
- }
203
185
  const packageManagerCommand = getPackageManagerUpgradeCommand(installMethod);
204
186
  if (packageManagerCommand) {
205
187
  return runPackageManagerUpgrade({
@@ -207,8 +189,8 @@ export async function performUpgrade(check, opts, dependencies) {
207
189
  currentVersion,
208
190
  latestVersion,
209
191
  installMethod,
210
- migration,
211
192
  skipPostUpgrade,
193
+ upgradeState: dependencies?.upgradeHistoricalStateDatabase ?? upgradeHistoricalStateDatabase,
212
194
  });
213
195
  }
214
196
  if (installMethod === "unknown") {
@@ -307,14 +289,6 @@ export async function performUpgrade(check, opts, dependencies) {
307
289
  `Set AKM_UPGRADE_SKIP_CHECKSUM=1 to bypass (not recommended).`);
308
290
  }
309
291
  }
310
- try {
311
- migration.preflight(execPath);
312
- migration.stagedPreflight(stagedPath);
313
- }
314
- catch (err) {
315
- removeFileBestEffort(stagedPath);
316
- throw err;
317
- }
318
292
  if (fs.existsSync(backupPath)) {
319
293
  removeFileBestEffort(stagedPath);
320
294
  throw new ConfigError(`Refusing to overwrite retained previous binary at ${backupPath}.`, "UPGRADE_BLOCKED");
@@ -333,14 +307,7 @@ export async function performUpgrade(check, opts, dependencies) {
333
307
  }
334
308
  throw err;
335
309
  }
336
- try {
337
- migration.apply(execPath);
338
- }
339
- catch (err) {
340
- const detail = err instanceof Error ? err.message : String(err);
341
- throw new Error(`Migration apply failed; the new binary remains installed and the previous binary retained at ${backupPath}: ${detail}`);
342
- }
343
- // Keep the previous binary available until migration apply has completed.
310
+ // The replacement completed; the temporary rollback copy is no longer needed.
344
311
  removeFileBestEffort(backupPath);
345
312
  return {
346
313
  currentVersion,
@@ -349,19 +316,32 @@ export async function performUpgrade(check, opts, dependencies) {
349
316
  installMethod,
350
317
  binaryPath: execPath,
351
318
  checksumVerified,
352
- postUpgrade: runPostUpgradeTasks(execPath, { skip: skipPostUpgrade }),
319
+ postUpgrade: runPostUpgradeTasks(execPath, { skip: skipPostUpgrade }, dependencies?.upgradeHistoricalStateDatabase ?? upgradeHistoricalStateDatabase),
353
320
  };
354
321
  }
355
322
  /**
356
- * Rebuild the derived index after the explicit migration command succeeds.
357
- * Migration and indexing are intentionally separate lifecycle steps.
323
+ * Rebuild the derived index after a successful upgrade.
358
324
  */
359
- function runPostUpgradeTasks(akmBin, opts) {
325
+ function runPostUpgradeTasks(akmBin, opts, upgradeState) {
326
+ let stateUpgrade;
327
+ try {
328
+ stateUpgrade = upgradeState();
329
+ }
330
+ catch (error) {
331
+ const detail = error instanceof Error ? error.message : String(error);
332
+ return {
333
+ ok: false,
334
+ skipped: opts.skip,
335
+ message: `Upgrade completed, but the state schema was not prepared (${detail}). ` +
336
+ "Preserve state.db and run `akm upgrade --force` before other AKM commands.",
337
+ };
338
+ }
339
+ const stateNote = stateUpgrade.safetyCopyPath ? ` Historical state safety copy: ${stateUpgrade.safetyCopyPath}.` : "";
360
340
  if (opts.skip) {
361
341
  return {
362
342
  ok: true,
363
343
  skipped: true,
364
- message: "Migration completed; skipped the index rebuild. Run `akm index` manually to rebuild the index.",
344
+ message: `Upgrade completed.${stateNote} Skipped the index rebuild. Run \`akm index\` manually to rebuild the index.`,
365
345
  };
366
346
  }
367
347
  try {
@@ -374,7 +354,7 @@ function runPostUpgradeTasks(akmBin, opts) {
374
354
  return {
375
355
  ok: false,
376
356
  skipped: false,
377
- message: `Migration completed, but the index rebuild could not start: ${result.error.message}. Run \`akm index\` manually.`,
357
+ message: `Upgrade completed.${stateNote} The index rebuild could not start: ${result.error.message}. Run \`akm index\` manually.`,
378
358
  };
379
359
  }
380
360
  if (result.status !== 0) {
@@ -383,14 +363,14 @@ function runPostUpgradeTasks(akmBin, opts) {
383
363
  ok: false,
384
364
  skipped: false,
385
365
  exitCode: result.status,
386
- message: `Migration completed, but post-upgrade \`akm index\` failed (${detail}). Run \`akm index\` manually.`,
366
+ message: `Upgrade completed.${stateNote} Post-upgrade \`akm index\` failed (${detail}). Run \`akm index\` manually.`,
387
367
  };
388
368
  }
389
369
  return {
390
370
  ok: true,
391
371
  skipped: false,
392
372
  exitCode: 0,
393
- message: "Migration completed and the index was rebuilt against the new binary.",
373
+ message: `Upgrade completed and the index was rebuilt against the new binary.${stateNote}`,
394
374
  };
395
375
  }
396
376
  catch (err) {
@@ -398,21 +378,20 @@ function runPostUpgradeTasks(akmBin, opts) {
398
378
  return {
399
379
  ok: false,
400
380
  skipped: false,
401
- message: `Migration completed, but the index rebuild failed: ${detail}. Run \`akm index\` manually.`,
381
+ message: `Upgrade completed.${stateNote} The index rebuild failed: ${detail}. Run \`akm index\` manually.`,
402
382
  };
403
383
  }
404
384
  }
405
385
  /**
406
- * The package-manager arm of {@link performUpgrade}: preflight → install
407
- * post-install version verification → migrate apply → post-upgrade tasks.
386
+ * The package-manager arm of {@link performUpgrade}: installpost-install
387
+ * version verification → post-upgrade tasks.
408
388
  * Extracted whole so performUpgrade stays under its fn-size baseline.
409
389
  */
410
390
  function runPackageManagerUpgrade(input) {
411
- const { packageManagerCommand, currentVersion, latestVersion, installMethod, migration, skipPostUpgrade } = input;
391
+ const { packageManagerCommand, currentVersion, latestVersion, installMethod, skipPostUpgrade, upgradeState } = input;
412
392
  if (!latestVersion) {
413
393
  throw new Error("Unable to determine latest version from GitHub releases. Check https://github.com/itlackey/akm/releases");
414
394
  }
415
- migration.preflight("akm");
416
395
  const result = childProcess.spawnSync(packageManagerCommand.command, packageManagerCommand.args, {
417
396
  encoding: "utf8",
418
397
  env: process.env,
@@ -429,8 +408,7 @@ function runPackageManagerUpgrade(input) {
429
408
  // `latestVersion`: a lagging `@latest` dist-tag (partial publish,
430
409
  // registry mirror lag) "succeeds" while leaving the old version on PATH.
431
410
  // Re-read the version the shim actually reports before claiming an
432
- // upgrade a confirmed mismatch also means `migrate apply` would run
433
- // against the OLD binary, so stop before it.
411
+ // upgrade, so stop before claiming success.
434
412
  const installedVersion = readInstalledCliVersion("akm");
435
413
  if (installedVersion !== undefined && installedVersion !== latestVersion) {
436
414
  return {
@@ -444,7 +422,6 @@ function runPackageManagerUpgrade(input) {
444
422
  `${packageManagerCommand.displayCommand.replace(/@latest\b/, `@${latestVersion}`)}`,
445
423
  };
446
424
  }
447
- migration.apply("akm");
448
425
  return {
449
426
  currentVersion,
450
427
  newVersion: latestVersion,
@@ -453,7 +430,7 @@ function runPackageManagerUpgrade(input) {
453
430
  message: installedVersion === latestVersion
454
431
  ? `akm upgraded via ${installMethod} (verified: akm --version reports v${installedVersion})`
455
432
  : `akm upgraded via ${installMethod} (installed version could not be verified)`,
456
- postUpgrade: runPostUpgradeTasks("akm", { skip: skipPostUpgrade }),
433
+ postUpgrade: runPostUpgradeTasks("akm", { skip: skipPostUpgrade }, upgradeState),
457
434
  };
458
435
  }
459
436
  /**
@@ -474,27 +451,6 @@ function readInstalledCliVersion(akmBin) {
474
451
  const match = (result.stdout ?? "").match(/\d+\.\d+\.\d+(?:-[0-9A-Za-z.-]+)?/);
475
452
  return match?.[0];
476
453
  }
477
- function runRequiredCommand(akmBin, args, label) {
478
- // A bare "akm" is not spawnable on Windows: npm/pnpm/yarn install a global CLI
479
- // as akm.cmd / akm.ps1 shims, and spawnSync without a shell does not apply
480
- // PATHEXT — so the package-manager upgrade arm died with ENOENT before it ever
481
- // ran. resolveAkmInvocation returns a concrete argv (launcher, runtime + main
482
- // script, or a standalone binary) for however this install actually runs.
483
- // An explicit path (the standalone arm passes one) is used as given.
484
- const [command, ...prefixArgs] = path.isAbsolute(akmBin) ? [akmBin] : resolveAkmInvocation().argv;
485
- const result = childProcess.spawnSync(command ?? akmBin, [...prefixArgs, ...args], {
486
- encoding: "utf8",
487
- env: process.env,
488
- stdio: "pipe",
489
- });
490
- if (result.error) {
491
- throw new Error(`${label} could not start: ${result.error.message}`);
492
- }
493
- if (result.status !== 0) {
494
- const detail = (result.stderr ?? "").trim() || (result.stdout ?? "").trim() || `exit code ${result.status}`;
495
- throw new Error(`${label} failed (${detail}).`);
496
- }
497
- }
498
454
  function removeFileBestEffort(filePath) {
499
455
  try {
500
456
  fs.unlinkSync(filePath);
@@ -26,7 +26,8 @@ export async function akmAdd(input) {
26
26
  if (shouldAddAsWebsiteUrl(ref)) {
27
27
  return addWebsiteSource(ref, stashDir, input.name, input.options);
28
28
  }
29
- // Detect local directory refs and route them to stashes[] instead of installed[]
29
+ // Local directories become filesystem bundles; registry refs use the
30
+ // registry-backed bundle installer below.
30
31
  try {
31
32
  const parsed = parseRegistryRef(ref);
32
33
  if (parsed.source === "local") {
@@ -38,10 +39,7 @@ export async function akmAdd(input) {
38
39
  }
39
40
  return addRegistryStash(ref, stashDir, input.writable);
40
41
  }
41
- /**
42
- * Add a local directory as a filesystem bundle (spec §10.1) — replaces the
43
- * retired `sources[]` filesystem entry.
44
- */
42
+ /** Add a local directory as a filesystem bundle. */
45
43
  async function addLocalSource(ref, sourcePath, stashDir, explicitName) {
46
44
  const stashRoot = detectStashRoot(sourcePath);
47
45
  const resolvedPath = path.resolve(stashRoot);
@@ -42,13 +42,9 @@ export const upgradeCommand = defineJsonCommand({
42
42
  force: { type: "boolean", description: "Force upgrade even if on latest", default: false },
43
43
  "skip-post-upgrade": {
44
44
  type: "boolean",
45
- description: "Skip the post-upgrade index rebuild (migration preflight and apply still run)",
45
+ description: "Skip the post-upgrade index rebuild",
46
46
  default: false,
47
47
  },
48
- "migration-config": {
49
- type: "string",
50
- description: "For 0.9+ upgrades, pass an operator-prepared config only to the new binary's migration apply",
51
- },
52
48
  },
53
49
  async run({ args }) {
54
50
  const check = await checkForUpdate(pkgVersion);
@@ -57,8 +53,7 @@ export const upgradeCommand = defineJsonCommand({
57
53
  return;
58
54
  }
59
55
  const skipPostUpgrade = args["skip-post-upgrade"];
60
- const migrationConfig = args["migration-config"];
61
- const result = await performUpgrade(check, { force: args.force, skipPostUpgrade, migrationConfig });
56
+ const result = await performUpgrade(check, { force: args.force, skipPostUpgrade });
62
57
  output("upgrade", result);
63
58
  },
64
59
  });
@@ -75,14 +70,6 @@ async function runSyncBody(args) {
75
70
  writable = resolveWritableOverride(loadConfig());
76
71
  }
77
72
  const result = saveGitStash(effectiveName, args.message, writable, { push: args.push !== false });
78
- // 0.9.0 breaking change: both "save" holdovers from the command's
79
- // pre-rename name are now "sync" — the persisted eventType below and the
80
- // envelope shape emitted at the end of this function. Historical state.db
81
- // rows still carry "save" — `readEvents`/`tailEvents` (src/core/events.ts)
82
- // treat "save" and "sync" as synonyms on READ so `akm log --type save`
83
- // keeps returning both old and new rows. Only the WRITE side changes here.
84
- // The envelope shape needs no such synonym: it is per-invocation, never
85
- // persisted, so nothing can be holding an old value.
86
73
  appendEvent({
87
74
  eventType: "sync",
88
75
  metadata: {