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
@@ -0,0 +1,308 @@
1
+ // This Source Code Form is subject to the terms of the Mozilla Public
2
+ // License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
+ import { createHash } from "node:crypto";
5
+ import { isMap, isScalar, parseDocument, visit } from "yaml";
6
+ import { cloneExecutionJson, cloneExecutionJsonObject, } from "../../execution/json.js";
7
+ import { assertSnapshotKeys, snapshotStrictRecord } from "../../execution/record.js";
8
+ import { cloneToolSelection, createAdapterRenderedExecutionSource, } from "../../execution/source.js";
9
+ function nextLine(text, start) {
10
+ const lf = text.indexOf("\n", start);
11
+ if (lf < 0)
12
+ return { line: text.slice(start), next: text.length };
13
+ const end = lf > start && text[lf - 1] === "\r" ? lf - 1 : lf;
14
+ return { line: text.slice(start, end), next: lf + 1 };
15
+ }
16
+ /** Strict execution-only frontmatter parser; indexing's tolerant parser is deliberately not reused. */
17
+ export function parseExecutionMarkdown(raw) {
18
+ if (typeof raw !== "string")
19
+ throw new TypeError("execution source raw content must be a string");
20
+ const withoutBom = raw.startsWith("\uFEFF") ? raw.slice(1) : raw;
21
+ const opening = nextLine(withoutBom, 0);
22
+ if (opening.line !== "---")
23
+ return { content: withoutBom, data: Object.freeze({}) };
24
+ if (opening.next === withoutBom.length) {
25
+ throw new TypeError("execution source has unterminated frontmatter");
26
+ }
27
+ let cursor = opening.next;
28
+ let frontmatterEnd = -1;
29
+ let bodyStart = -1;
30
+ while (cursor <= withoutBom.length) {
31
+ const lineStart = cursor;
32
+ const current = nextLine(withoutBom, cursor);
33
+ if (current.line === "---") {
34
+ frontmatterEnd = lineStart;
35
+ bodyStart = current.next;
36
+ break;
37
+ }
38
+ if (current.next === cursor || current.next === withoutBom.length)
39
+ break;
40
+ cursor = current.next;
41
+ }
42
+ if (frontmatterEnd < 0 || bodyStart < 0) {
43
+ throw new TypeError("execution source has unterminated frontmatter");
44
+ }
45
+ const yaml = withoutBom.slice(opening.next, frontmatterEnd);
46
+ const document = parseDocument(yaml, { uniqueKeys: true });
47
+ if (document.errors.length > 0) {
48
+ throw new TypeError(`execution source has invalid YAML frontmatter: ${document.errors[0]?.message ?? "parse error"}`);
49
+ }
50
+ if (document.warnings.length > 0) {
51
+ throw new TypeError(`execution source YAML frontmatter uses an unsupported tag or construct: ${document.warnings[0]?.message}`);
52
+ }
53
+ if (!isMap(document.contents)) {
54
+ throw new TypeError("execution source YAML frontmatter must be a mapping");
55
+ }
56
+ let unsupported;
57
+ visit(document, {
58
+ Alias: () => {
59
+ unsupported ??= "aliases";
60
+ },
61
+ Node: (_key, node) => {
62
+ if (node.tag)
63
+ unsupported ??= "custom or explicit tags";
64
+ if (node.anchor !== undefined)
65
+ unsupported ??= "anchors";
66
+ },
67
+ Pair: (_key, pair) => {
68
+ if (!isScalar(pair.key) || typeof pair.key.value !== "string")
69
+ unsupported ??= "non-string mapping keys";
70
+ },
71
+ });
72
+ if (unsupported)
73
+ throw new TypeError(`execution source YAML frontmatter does not support ${unsupported}`);
74
+ let root;
75
+ try {
76
+ root = document.toJS({ maxAliasCount: 0 });
77
+ }
78
+ catch (cause) {
79
+ throw new TypeError("execution source YAML frontmatter could not be converted safely", { cause });
80
+ }
81
+ const data = cloneExecutionJsonObject(root, "execution source YAML frontmatter");
82
+ return { content: withoutBom.slice(bodyStart), data: Object.freeze(data) };
83
+ }
84
+ function own(data, key) {
85
+ return Object.hasOwn(data, key);
86
+ }
87
+ function requireMetadataMapping(value, path) {
88
+ try {
89
+ return snapshotStrictRecord(value, path);
90
+ }
91
+ catch (cause) {
92
+ throw new TypeError(`${path} must be a mapping with enumerable data fields`, { cause });
93
+ }
94
+ }
95
+ function nullableString(value, path) {
96
+ if (value !== null && typeof value !== "string")
97
+ throw new TypeError(`${path} must be a string or null`);
98
+ return value;
99
+ }
100
+ function nullableObject(value, path) {
101
+ return value === null ? null : cloneExecutionJsonObject(value, path);
102
+ }
103
+ function nullableEnvironment(value, path) {
104
+ if (value === null)
105
+ return null;
106
+ const environment = cloneExecutionJsonObject(value, path);
107
+ if (Object.values(environment).some((entry) => typeof entry !== "string")) {
108
+ throw new TypeError(`${path} values must be strings`);
109
+ }
110
+ return environment;
111
+ }
112
+ function nullableTimeout(value, path) {
113
+ const timeout = cloneExecutionJson(value, path);
114
+ if (timeout !== null && typeof timeout !== "string" && typeof timeout !== "number") {
115
+ throw new TypeError(`${path} must be a string, number, or null`);
116
+ }
117
+ return timeout;
118
+ }
119
+ /** Adapter helper for the common frontmatter fields fixed by the 0.9.2 design. */
120
+ export function executionDefaultsFromFrontmatter(data, options) {
121
+ const frontmatter = snapshotStrictRecord(data, "frontmatter");
122
+ const optionRecord = snapshotStrictRecord(options, "execution frontmatter projection options");
123
+ assertSnapshotKeys(optionRecord, ["kind", "allowTopLevelEngine", "toolsKeys"], "execution frontmatter projection options");
124
+ const kind = optionRecord.kind;
125
+ if (kind !== "command" && kind !== "persona") {
126
+ throw new TypeError("execution frontmatter projection options.kind is invalid");
127
+ }
128
+ const allowTopLevelEngine = Object.hasOwn(optionRecord, "allowTopLevelEngine")
129
+ ? optionRecord.allowTopLevelEngine
130
+ : false;
131
+ if (typeof allowTopLevelEngine !== "boolean") {
132
+ throw new TypeError("execution frontmatter projection options.allowTopLevelEngine must be a boolean");
133
+ }
134
+ const configuredToolsKeys = Object.hasOwn(optionRecord, "toolsKeys")
135
+ ? cloneExecutionJson(optionRecord.toolsKeys, "execution frontmatter projection options.toolsKeys")
136
+ : ["tools"];
137
+ if (!Array.isArray(configuredToolsKeys) || configuredToolsKeys.some((key) => typeof key !== "string" || !key)) {
138
+ throw new TypeError("execution frontmatter projection options.toolsKeys must be an array of non-empty strings");
139
+ }
140
+ const namespace = own(frontmatter, "akm")
141
+ ? requireMetadataMapping(frontmatter.akm, "frontmatter.akm")
142
+ : snapshotStrictRecord({}, "frontmatter.akm");
143
+ const out = Object.create(null);
144
+ if (kind === "command" && own(frontmatter, "agent")) {
145
+ out.agent = nullableString(frontmatter.agent, "frontmatter.agent");
146
+ }
147
+ const namespacedEngine = own(namespace, "engine")
148
+ ? nullableString(namespace.engine, "frontmatter.akm.engine")
149
+ : undefined;
150
+ const topLevelEngine = allowTopLevelEngine && own(frontmatter, "engine")
151
+ ? nullableString(frontmatter.engine, "frontmatter.engine")
152
+ : undefined;
153
+ if (topLevelEngine !== undefined)
154
+ out.engine = topLevelEngine;
155
+ else if (namespacedEngine !== undefined)
156
+ out.engine = namespacedEngine;
157
+ if (own(frontmatter, "model")) {
158
+ out.model = nullableString(frontmatter.model, "frontmatter.model");
159
+ }
160
+ let toolsSelected = false;
161
+ for (const key of configuredToolsKeys) {
162
+ if (!own(frontmatter, key))
163
+ continue;
164
+ const selection = cloneToolSelection(frontmatter[key], `frontmatter.${key}`);
165
+ if (!toolsSelected) {
166
+ out.tools = selection;
167
+ toolsSelected = true;
168
+ }
169
+ }
170
+ let inferenceEntries;
171
+ if (own(namespace, "inference")) {
172
+ if (namespace.inference === null)
173
+ inferenceEntries = null;
174
+ else {
175
+ inferenceEntries = new Map();
176
+ for (const [key, value] of Object.entries(cloneExecutionJsonObject(namespace.inference, "frontmatter.akm.inference"))) {
177
+ inferenceEntries.set(key, value);
178
+ }
179
+ }
180
+ }
181
+ for (const key of ["temperature", "effort"]) {
182
+ if (!own(frontmatter, key))
183
+ continue;
184
+ if (inferenceEntries === null) {
185
+ throw new TypeError(`frontmatter.${key} conflicts with explicit null frontmatter.akm.inference`);
186
+ }
187
+ inferenceEntries ??= new Map();
188
+ const value = frontmatter[key];
189
+ if (key === "temperature") {
190
+ if (value !== null && typeof value !== "number") {
191
+ throw new TypeError("frontmatter.temperature must be a number or null");
192
+ }
193
+ }
194
+ else if (value !== null && typeof value !== "string") {
195
+ throw new TypeError("frontmatter.effort must be a string or null");
196
+ }
197
+ inferenceEntries.set(key, cloneExecutionJson(value, `frontmatter.${key}`));
198
+ }
199
+ if (inferenceEntries === null)
200
+ out.inference = null;
201
+ else if (inferenceEntries !== undefined)
202
+ out.inference = Object.fromEntries(inferenceEntries);
203
+ const namespacedSchema = own(namespace, "schema")
204
+ ? nullableObject(namespace.schema, "frontmatter.akm.schema")
205
+ : undefined;
206
+ const topLevelSchema = own(frontmatter, "schema")
207
+ ? nullableObject(frontmatter.schema, "frontmatter.schema")
208
+ : undefined;
209
+ if (topLevelSchema !== undefined)
210
+ out.outputSchema = topLevelSchema;
211
+ else if (namespacedSchema !== undefined)
212
+ out.outputSchema = namespacedSchema;
213
+ const namespacedTimeouts = new Map();
214
+ for (const key of ["timeoutMs", "timeout"]) {
215
+ if (own(namespace, key)) {
216
+ namespacedTimeouts.set(key, nullableTimeout(namespace[key], `frontmatter.akm.${key}`));
217
+ }
218
+ }
219
+ const topLevelTimeouts = new Map();
220
+ if (allowTopLevelEngine) {
221
+ for (const key of ["timeoutMs", "timeout"]) {
222
+ if (own(frontmatter, key)) {
223
+ topLevelTimeouts.set(key, nullableTimeout(frontmatter[key], `frontmatter.${key}`));
224
+ }
225
+ }
226
+ }
227
+ const timeoutKey = namespacedTimeouts.has("timeoutMs")
228
+ ? "timeoutMs"
229
+ : namespacedTimeouts.has("timeout")
230
+ ? "timeout"
231
+ : undefined;
232
+ const topLevelTimeoutKey = allowTopLevelEngine
233
+ ? topLevelTimeouts.has("timeoutMs")
234
+ ? "timeoutMs"
235
+ : topLevelTimeouts.has("timeout")
236
+ ? "timeout"
237
+ : undefined
238
+ : undefined;
239
+ const selectedTimeoutKey = topLevelTimeoutKey ?? timeoutKey;
240
+ if (selectedTimeoutKey) {
241
+ out.timeout = topLevelTimeoutKey
242
+ ? topLevelTimeouts.get(selectedTimeoutKey)
243
+ : namespacedTimeouts.get(selectedTimeoutKey);
244
+ }
245
+ for (const key of ["workspace", "environment", "runtime"]) {
246
+ const namespaced = own(namespace, key)
247
+ ? key === "workspace"
248
+ ? nullableString(namespace[key], `frontmatter.akm.${key}`)
249
+ : key === "environment"
250
+ ? nullableEnvironment(namespace[key], `frontmatter.akm.${key}`)
251
+ : nullableObject(namespace[key], `frontmatter.akm.${key}`)
252
+ : undefined;
253
+ const topLevel = allowTopLevelEngine && own(frontmatter, key)
254
+ ? key === "workspace"
255
+ ? nullableString(frontmatter[key], `frontmatter.${key}`)
256
+ : key === "environment"
257
+ ? nullableEnvironment(frontmatter[key], `frontmatter.${key}`)
258
+ : nullableObject(frontmatter[key], `frontmatter.${key}`)
259
+ : undefined;
260
+ if (topLevel !== undefined)
261
+ out[key] = topLevel;
262
+ else if (namespaced !== undefined)
263
+ out[key] = namespaced;
264
+ }
265
+ return out;
266
+ }
267
+ function snapshotRendererInput(input) {
268
+ const inputSnapshot = snapshotStrictRecord(input, "adapter execution source");
269
+ assertSnapshotKeys(inputSnapshot, ["kind", "raw", "identity", "defaults", "extensions"], "adapter execution source");
270
+ const identity = snapshotStrictRecord(inputSnapshot.identity, "adapter execution source identity");
271
+ assertSnapshotKeys(identity, ["ref", "bundle", "adapter", "file"], "adapter execution source identity");
272
+ return { input: inputSnapshot, identity };
273
+ }
274
+ export function renderMarkdownExecutionSource(input) {
275
+ const snapshots = snapshotRendererInput(input);
276
+ const raw = snapshots.input.raw;
277
+ if (typeof raw !== "string")
278
+ throw new TypeError("adapter execution source.raw must be a string");
279
+ const kind = snapshots.input.kind;
280
+ if (kind !== "command" && kind !== "persona")
281
+ throw new TypeError("adapter execution source.kind is invalid");
282
+ const parsed = parseExecutionMarkdown(raw);
283
+ const hasDefaults = Object.hasOwn(snapshots.input, "defaults");
284
+ const defaultsProjection = snapshots.input.defaults;
285
+ const defaults = typeof defaultsProjection === "function" ? defaultsProjection(parsed.data) : defaultsProjection;
286
+ if (hasDefaults && defaults === undefined) {
287
+ throw new TypeError("adapter execution source defaults must be omitted or resolve to an object");
288
+ }
289
+ const extensionsProjection = snapshots.input.extensions;
290
+ const extensionsAreProjected = typeof extensionsProjection === "function";
291
+ const extensions = extensionsAreProjected ? extensionsProjection(parsed.data) : extensionsProjection;
292
+ if (Object.hasOwn(snapshots.input, "extensions") && !extensionsAreProjected && extensions === undefined) {
293
+ throw new TypeError("adapter execution source extensions must be omitted or be an adapter-owned extension object");
294
+ }
295
+ return createAdapterRenderedExecutionSource({
296
+ kind,
297
+ content: parsed.content,
298
+ identity: {
299
+ ref: snapshots.identity.ref,
300
+ bundle: snapshots.identity.bundle,
301
+ adapter: snapshots.identity.adapter,
302
+ file: snapshots.identity.file,
303
+ hash: createHash("sha256").update(raw, "utf8").digest("hex"),
304
+ },
305
+ ...(hasDefaults ? { defaults } : {}),
306
+ ...(extensions === undefined ? {} : { extensions }),
307
+ });
308
+ }
@@ -1,15 +1,16 @@
1
1
  // This Source Code Form is subject to the terms of the Mozilla Public
2
2
  // License, v. 2.0. If a copy of the MPL was not distributed with this
3
3
  // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
- import { directoryMatcher, extensionMatcher, parentDirHintMatcher, smartMdMatcher } from "../../indexer/walk/matchers.js";
4
+ import { directoryMatcher, extensionMatcher, parentDirHintMatcher, smartMdMatcher, smartMdPathCandidates, } from "../../indexer/walk/matchers.js";
5
5
  /**
6
6
  * The four builtin matchers, in registration order. The array index IS the
7
7
  * registration index `runMatchers` uses for tie-breaking. (The `wiki` matcher
8
8
  * was removed in chunk 4 — the wiki asset-type is retired; LLM Wiki content is
9
- * served by the first-class `llm-wiki` adapter, not the akm adapter. The YAML
10
- * workflow-program matcher was removed by workflow-format-unification one
11
- * workflow format now, recognized by frontmatter `type: workflow` or
12
- * residence under `workflows/`, both already covered by the remaining four.)
9
+ * served by the first-class `llm-wiki` adapter, not the akm adapter. The old
10
+ * YAML workflow-program matcher is also gone: peer Markdown and GitHub-shaped
11
+ * `.yml` workflow sources are both path-owned by residence under `workflows/`
12
+ * (with Markdown frontmatter as an additional signal), while source IR owns
13
+ * their distinct parse/compile semantics.)
13
14
  */
14
15
  const AKM_MATCHERS = [
15
16
  extensionMatcher,
@@ -17,6 +18,17 @@ const AKM_MATCHERS = [
17
18
  parentDirHintMatcher,
18
19
  smartMdMatcher,
19
20
  ];
21
+ function winningMatch(hits) {
22
+ if (hits.length === 0)
23
+ return null;
24
+ hits.sort((a, b) => {
25
+ const specDiff = b.result.specificity - a.result.specificity;
26
+ if (specDiff !== 0)
27
+ return specDiff;
28
+ return b.index - a.index;
29
+ });
30
+ return hits[0].result;
31
+ }
20
32
  /**
21
33
  * Synchronous reproduction of `file-context.ts#runMatchers`'s arbitration
22
34
  * (`:242-265`), minus its `ensureBuiltinsRegistered()` dynamic import. Runs
@@ -32,13 +44,24 @@ export function recognizeMatch(file) {
32
44
  if (result !== null)
33
45
  hits.push({ result, index: i });
34
46
  }
35
- if (hits.length === 0)
36
- return null;
37
- hits.sort((a, b) => {
38
- const specDiff = b.result.specificity - a.result.specificity;
39
- if (specDiff !== 0)
40
- return specDiff;
41
- return b.index - a.index;
47
+ return winningMatch(hits);
48
+ }
49
+ /**
50
+ * Every AKM matcher winner possible from path fields alone. The three
51
+ * path-only matchers run exactly as production does; each possible result of
52
+ * the shared smart-Markdown fact table is then arbitrated at its real index.
53
+ */
54
+ export function recognizePathCandidateMatches(file) {
55
+ const fileContext = file;
56
+ const pathHits = [extensionMatcher, directoryMatcher, parentDirHintMatcher].flatMap((matcher, index) => {
57
+ const result = matcher(fileContext);
58
+ return result ? [{ result, index }] : [];
42
59
  });
43
- return hits[0].result;
60
+ const smartCandidates = smartMdPathCandidates(file);
61
+ const variants = smartCandidates.length > 0 ? smartCandidates : [undefined];
62
+ const winners = variants.flatMap((smart) => {
63
+ const winner = winningMatch(smart ? [...pathHits, { result: smart, index: 3 }] : [...pathHits]);
64
+ return winner ? [winner] : [];
65
+ });
66
+ return [...new Map(winners.map((winner) => [`${winner.type}\0${winner.renderer}`, winner])).values()];
44
67
  }
@@ -45,12 +45,3 @@ export function getAdapters() {
45
45
  export function adapterForId(id) {
46
46
  return BY_ID.get(id);
47
47
  }
48
- /**
49
- * DEPRECATED no-op retained for test back-compat only. The registry is a static
50
- * frozen map (normative §12.6) populated at module load, so there is nothing to
51
- * reset — `getAdapters()` / `adapterForId()` are always the full built-in set.
52
- * New code MUST NOT depend on this.
53
- */
54
- export function resetAdapterRegistryForTests() {
55
- // intentionally empty — the registry is static; see the doc comment above.
56
- }
@@ -95,16 +95,106 @@ export function resolveMetaFilePath(sourceRoot, name) {
95
95
  if (resolved !== metaRoot && !resolved.startsWith(metaRoot + path.sep)) {
96
96
  throw new UsageError("Meta ref resolves outside the stash .meta directory.", "PATH_ESCAPE_VIOLATION");
97
97
  }
98
- if (isRegularFile(resolved))
98
+ if (isSafeRegularMetaFile(sourceRoot, metaRoot, resolved))
99
99
  return resolved;
100
100
  }
101
101
  return null;
102
102
  }
103
- function isRegularFile(p) {
103
+ /**
104
+ * Resolve and read a meta doc from one stable, no-follow file descriptor.
105
+ * The descriptor/path identity check closes the swap window between path
106
+ * validation and reading: even if a writable local bundle changes the path
107
+ * concurrently, bytes are returned only when the opened inode is still the
108
+ * validated file inside the bundle's real `.meta/` directory.
109
+ */
110
+ export function readMetaFile(sourceRoot, name) {
111
+ const filePath = resolveMetaFilePath(sourceRoot, name);
112
+ if (!filePath)
113
+ return null;
114
+ let fd;
104
115
  try {
105
- return fs.statSync(p).isFile();
116
+ fd = fs.openSync(filePath, fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW);
117
+ const opened = fs.fstatSync(fd);
118
+ if (!opened.isFile())
119
+ throw metaPathEscape();
120
+ const metaRoot = path.resolve(sourceRoot, META_DIR);
121
+ if (!isSafeRegularMetaFile(sourceRoot, metaRoot, filePath))
122
+ throw metaPathEscape();
123
+ const current = fs.statSync(filePath);
124
+ if (opened.dev !== current.dev || opened.ino !== current.ino)
125
+ throw metaPathEscape();
126
+ return { path: filePath, content: fs.readFileSync(fd, "utf8") };
106
127
  }
107
- catch {
128
+ catch (error) {
129
+ if (error instanceof UsageError)
130
+ throw error;
131
+ const code = typeof error === "object" && error !== null && "code" in error ? String(error.code) : "";
132
+ if (code === "ENOENT" || code === "ENOTDIR")
133
+ return null;
134
+ if (code === "ELOOP")
135
+ throw metaPathEscape();
136
+ throw new UsageError("Meta doc could not be read safely.", "PATH_ESCAPE_VIOLATION");
137
+ }
138
+ finally {
139
+ if (fd !== undefined)
140
+ fs.closeSync(fd);
141
+ }
142
+ }
143
+ function isSafeRegularMetaFile(sourceRoot, metaRoot, filePath) {
144
+ const sourceRootReal = realPathOrNull(sourceRoot);
145
+ if (!sourceRootReal)
146
+ return false;
147
+ const metaStat = lstatOrNull(metaRoot);
148
+ if (!metaStat)
149
+ return false;
150
+ if (metaStat.isSymbolicLink())
151
+ throw metaPathEscape();
152
+ if (!metaStat.isDirectory())
153
+ return false;
154
+ const relative = path.relative(metaRoot, filePath);
155
+ if (relative === "" || relative === ".." || relative.startsWith(`..${path.sep}`) || path.isAbsolute(relative)) {
156
+ throw metaPathEscape();
157
+ }
158
+ let current = metaRoot;
159
+ for (const segment of relative.split(path.sep)) {
160
+ current = path.join(current, segment);
161
+ const stat = lstatOrNull(current);
162
+ if (!stat)
163
+ return false;
164
+ if (stat.isSymbolicLink())
165
+ throw metaPathEscape();
166
+ }
167
+ const finalStat = lstatOrNull(filePath);
168
+ if (!finalStat?.isFile())
169
+ return false;
170
+ const metaRootReal = realPathOrNull(metaRoot);
171
+ const fileReal = realPathOrNull(filePath);
172
+ if (!metaRootReal || !fileReal)
108
173
  return false;
174
+ if (!isWithin(sourceRootReal, metaRootReal) || !isWithin(metaRootReal, fileReal))
175
+ throw metaPathEscape();
176
+ return true;
177
+ }
178
+ function lstatOrNull(filePath) {
179
+ try {
180
+ return fs.lstatSync(filePath);
109
181
  }
182
+ catch {
183
+ return null;
184
+ }
185
+ }
186
+ function realPathOrNull(filePath) {
187
+ try {
188
+ return fs.realpathSync(filePath);
189
+ }
190
+ catch {
191
+ return null;
192
+ }
193
+ }
194
+ function isWithin(root, candidate) {
195
+ const relative = path.relative(root, candidate);
196
+ return relative === "" || (relative !== ".." && !relative.startsWith(`..${path.sep}`) && !path.isAbsolute(relative));
197
+ }
198
+ function metaPathEscape() {
199
+ return new UsageError("Meta doc must be a regular file inside the stash .meta directory; symlinks are refused.", "PATH_ESCAPE_VIOLATION");
110
200
  }
@@ -284,12 +284,8 @@ function isValidDirectory(dir) {
284
284
  * Read the primary stash path directly from config.json without pulling in the
285
285
  * full config module, to avoid circular dependencies.
286
286
  *
287
- * Reads ONLY the 0.9.0 `bundles`/`defaultBundle` shape. A config still carrying
288
- * the retired `stashDir`/`sources`/`installed` keys (with no usable bundles
289
- * path) is an unmigrated config: this refuses it with the same `akm-migrate
290
- * apply` hint the schema hard-reject uses (config-schema.ts), rather than
291
- * silently honouring the retired key — so every `resolveStashDir` caller gets
292
- * the coherent migrate posture instead of split-brain success.
287
+ * Reads only the current `bundles`/`defaultBundle` shape. Unsupported older
288
+ * keys are never interpreted as a second config architecture.
293
289
  */
294
290
  function readStashDirFromConfig() {
295
291
  try {
@@ -340,17 +336,16 @@ function readStashDirFromConfig() {
340
336
  }
341
337
  return bundlePath;
342
338
  }
343
- // Retired pre-cutover shape with no usable bundles path: refuse with the
344
- // migrate hint (matches the schema's hard-reject, config-schema.ts) instead
345
- // of silently resolving the old key.
339
+ // Retired pre-cutover shapes are not runtime inputs. Refuse them instead
340
+ // of silently resolving old keys.
346
341
  for (const key of ["stashDir", "sources", "installed"]) {
347
342
  if (key in raw && raw[key] !== undefined) {
348
- throw new ConfigError(`${key} is the retired pre-cutover source shape; run \`akm-migrate apply\` to convert it to bundles`, "INVALID_CONFIG_FILE");
343
+ throw new ConfigError(`${key} is not supported; configure the current bundles shape`, "INVALID_CONFIG_FILE");
349
344
  }
350
345
  }
351
346
  }
352
347
  catch (err) {
353
- // A retired-shape refusal must reach the caller; genuine missing/invalid
348
+ // An unsupported-shape refusal must reach the caller; genuine missing/invalid
354
349
  // config (read or JSON-parse failure) falls through to the platform default.
355
350
  if (err instanceof ConfigError)
356
351
  throw err;
@@ -5,12 +5,12 @@
5
5
  * Pure I/O helpers for AKM config files.
6
6
  *
7
7
  * No knowledge of the AkmConfig shape — these functions just read JSON(C) text
8
- * from disk and write JSON text back atomically. Validation and migration live
9
- * in `./config.ts` and `./config-migrate.ts`.
8
+ * from disk and write JSON text back atomically. Current-schema validation lives
9
+ * in `./config.ts`; older config shapes are rejected.
10
10
  *
11
11
  * Split out so the load path is testable without touching the filesystem
12
12
  * (`parseConfigText` is pure), and so a single atomic write path serves
13
- * `saveConfig`, the migrate command, and the setup wizard (#464.c).
13
+ * `saveConfig` and the setup wizard (#464.c).
14
14
  */
15
15
  import fs from "node:fs";
16
16
  import path from "node:path";