akm-cli 0.9.0-rc.1 → 0.9.0-rc.13

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 (587) hide show
  1. package/CHANGELOG.md +1190 -52
  2. package/README.md +62 -37
  3. package/SECURITY.md +46 -31
  4. package/dist/akm +162 -38
  5. package/dist/akm-migrate +44 -0
  6. package/dist/assets/backends/schtasks-template.xml +2 -1
  7. package/dist/assets/hints/cli-hints-full.md +268 -118
  8. package/dist/assets/hints/cli-hints-short.md +87 -24
  9. package/dist/assets/{profiles → improve-strategies}/catchup.json +3 -1
  10. package/dist/assets/{profiles → improve-strategies}/consolidate.json +3 -1
  11. package/dist/assets/{profiles → improve-strategies}/default.json +6 -7
  12. package/dist/assets/improve-strategies/frequent.json +15 -0
  13. package/dist/assets/{profiles → improve-strategies}/graph-refresh.json +4 -2
  14. package/dist/assets/{profiles → improve-strategies}/memory-focus.json +4 -1
  15. package/dist/assets/{profiles → improve-strategies}/proactive-maintenance.json +5 -5
  16. package/dist/assets/{profiles → improve-strategies}/quick.json +4 -2
  17. package/dist/assets/improve-strategies/reflect-distill.json +30 -0
  18. package/dist/assets/{profiles → improve-strategies}/thorough.json +1 -1
  19. package/dist/assets/prompts/consolidate-system.md +5 -5
  20. package/dist/assets/prompts/extract-session.md +2 -6
  21. package/dist/assets/prompts/memory-infer-user.md +2 -3
  22. package/dist/assets/prompts/reflect-llm-framed-contract.md +11 -0
  23. package/dist/assets/prompts/reflect-llm-schema-contract.md +3 -0
  24. package/dist/assets/prompts/reflect-output-repair.md +3 -0
  25. package/dist/assets/stash-skeleton/README.md +38 -10
  26. package/dist/assets/stash-skeleton/facts/conventions/assets/agent.md +8 -0
  27. package/dist/assets/stash-skeleton/facts/conventions/assets/command.md +8 -0
  28. package/dist/assets/stash-skeleton/facts/conventions/assets/fact.md +14 -1
  29. package/dist/assets/stash-skeleton/facts/conventions/assets/knowledge.md +13 -1
  30. package/dist/assets/stash-skeleton/facts/conventions/assets/lesson.md +9 -1
  31. package/dist/assets/stash-skeleton/facts/conventions/assets/memory.md +11 -0
  32. package/dist/assets/stash-skeleton/facts/conventions/assets/script.md +9 -0
  33. package/dist/assets/stash-skeleton/facts/conventions/assets/skill.md +9 -0
  34. package/dist/assets/stash-skeleton/facts/conventions/assets/workflow.md +8 -0
  35. package/dist/assets/stash-skeleton/facts/conventions/backlinks.md +100 -0
  36. package/dist/assets/stash-skeleton/facts/conventions/domains.md +64 -0
  37. package/dist/assets/stash-skeleton/facts/conventions/organization.md +136 -0
  38. package/dist/assets/tasks/core/extract.yml +3 -2
  39. package/dist/assets/tasks/core/improve.yml +2 -1
  40. package/dist/assets/tasks/core/index-refresh.yml +1 -0
  41. package/dist/assets/tasks/core/sync.yml +1 -0
  42. package/dist/assets/tasks/core/version-check.yml +2 -1
  43. package/dist/assets/tasks/improve/akm-graph-refresh-weekly.yml +5 -0
  44. package/dist/assets/tasks/improve/akm-improve-catchup.yml +8 -0
  45. package/dist/assets/tasks/improve/akm-improve-consolidate.yml +5 -0
  46. package/dist/assets/tasks/improve/akm-improve-frequent.yml +5 -0
  47. package/dist/assets/tasks/improve/akm-improve-nightly.yml +5 -0
  48. package/dist/assets/templates/html/health.html +5 -4
  49. package/dist/assets/workflows/workflow-template.md +31 -15
  50. package/dist/cli/invocation.js +279 -0
  51. package/dist/cli/parse-args.js +5 -90
  52. package/dist/cli/retired-commands.js +78 -0
  53. package/dist/cli/shared.js +158 -48
  54. package/dist/cli-node.mjs +2 -1
  55. package/dist/cli.js +747 -293
  56. package/dist/commands/agent/agent-dispatch.js +19 -18
  57. package/dist/commands/agent/agent-support.js +0 -24
  58. package/dist/commands/agent/contribute-cli.js +43 -97
  59. package/dist/commands/completions.js +80 -23
  60. package/dist/commands/config-cli.js +44 -281
  61. package/dist/commands/env/env-binding.js +13 -9
  62. package/dist/commands/env/env-cli.js +76 -159
  63. package/dist/commands/env/env.js +12 -163
  64. package/dist/commands/env/marker-path.js +6 -0
  65. package/dist/commands/env/secret-cli.js +45 -61
  66. package/dist/commands/env/secret.js +32 -62
  67. package/dist/commands/feedback-cli.js +179 -85
  68. package/dist/commands/health/accept-rate.js +58 -0
  69. package/dist/commands/health/advisories.js +7 -8
  70. package/dist/commands/health/checks.js +279 -94
  71. package/dist/commands/health/html-report.js +197 -578
  72. package/dist/commands/health/improve-metrics.js +277 -246
  73. package/dist/commands/health/llm-usage.js +19 -19
  74. package/dist/commands/health/md-report.js +16 -7
  75. package/dist/commands/health/metrics.js +67 -32
  76. package/dist/commands/health/renderers.js +47 -0
  77. package/dist/commands/health/report-view-model.js +508 -0
  78. package/dist/commands/health/stash-exposure.js +1 -1
  79. package/dist/commands/health/surfaces.js +16 -56
  80. package/dist/commands/health/task-runs.js +3 -67
  81. package/dist/{migrate-storage-node.mjs → commands/health/types-checks.js} +1 -5
  82. package/dist/commands/health/types-improve.js +29 -0
  83. package/dist/{output/text/save.js → commands/health/types-metrics.js} +1 -2
  84. package/dist/commands/health/types-result.js +7 -0
  85. package/dist/commands/health/types-runs.js +4 -0
  86. package/dist/commands/health/types-session-log.js +4 -0
  87. package/dist/commands/health/types-windows.js +4 -0
  88. package/dist/commands/health/types.js +26 -21
  89. package/dist/commands/health/windows.js +2 -3
  90. package/dist/commands/health.js +296 -167
  91. package/dist/commands/improve/anti-collapse.js +5 -5
  92. package/dist/commands/improve/autonomy-gate.js +68 -0
  93. package/dist/commands/improve/collapse-detector.js +65 -52
  94. package/dist/commands/improve/consolidate/chunking.js +9 -7
  95. package/dist/commands/improve/consolidate/eligibility.js +1 -23
  96. package/dist/commands/improve/consolidate/merge.js +4 -0
  97. package/dist/commands/improve/consolidate.js +454 -1354
  98. package/dist/commands/improve/content-hash.js +39 -0
  99. package/dist/commands/improve/distill/content-repair.js +4 -10
  100. package/dist/commands/improve/distill/promote-memory.js +89 -64
  101. package/dist/commands/improve/distill/quality-gate.js +118 -42
  102. package/dist/commands/improve/distill-guards.js +1 -1
  103. package/dist/commands/improve/distill-promotion-policy.js +33 -888
  104. package/dist/commands/improve/distill.js +607 -363
  105. package/dist/commands/improve/eligibility.js +165 -79
  106. package/dist/commands/improve/extract-cli.js +35 -126
  107. package/dist/commands/improve/extract-prompt.js +6 -35
  108. package/dist/commands/improve/extract.js +640 -391
  109. package/dist/commands/improve/feedback-valence.js +2 -12
  110. package/dist/commands/improve/improve-cli.js +134 -135
  111. package/dist/commands/improve/improve-result-file.js +30 -50
  112. package/dist/commands/improve/improve-run-types.js +4 -0
  113. package/dist/commands/improve/improve-strategies.js +135 -0
  114. package/dist/commands/improve/improve.js +904 -701
  115. package/dist/commands/improve/locks.js +64 -111
  116. package/dist/commands/improve/loop-stages.js +1110 -923
  117. package/dist/commands/improve/memory/derived-ref.js +124 -0
  118. package/dist/commands/improve/memory/memory-belief.js +79 -7
  119. package/dist/commands/improve/memory/memory-contradiction-detect.js +49 -52
  120. package/dist/commands/improve/memory/memory-improve.js +25 -37
  121. package/dist/commands/improve/outcome-loop.js +25 -88
  122. package/dist/commands/improve/preparation.js +1034 -813
  123. package/dist/commands/improve/proactive-maintenance.js +34 -9
  124. package/dist/commands/improve/proposal-envelope.js +31 -0
  125. package/dist/commands/improve/reflect.js +983 -794
  126. package/dist/commands/improve/run-context.js +119 -0
  127. package/dist/commands/improve/salience.js +24 -127
  128. package/dist/commands/improve/session-asset.js +7 -3
  129. package/dist/commands/improve/shared.js +14 -34
  130. package/dist/commands/improve/source-identity.js +28 -0
  131. package/dist/commands/improve/triage.js +20 -17
  132. package/dist/commands/lint/base-linter.js +340 -313
  133. package/dist/commands/lint/env-key-rules.js +31 -47
  134. package/dist/commands/lint/index.js +185 -30
  135. package/dist/commands/{events.js → log.js} +28 -38
  136. package/dist/commands/migrate-cli.js +54 -0
  137. package/dist/commands/migration-tool.js +55 -0
  138. package/dist/commands/observability-cli.js +70 -208
  139. package/dist/commands/proposal/diff-format.js +50 -0
  140. package/dist/commands/proposal/drain-policies.js +0 -6
  141. package/dist/commands/proposal/drain.js +91 -40
  142. package/dist/commands/proposal/proposal-cli.js +134 -132
  143. package/dist/commands/proposal/proposal-types.js +56 -0
  144. package/dist/commands/proposal/proposal.js +83 -65
  145. package/dist/commands/proposal/propose-cli.js +88 -0
  146. package/dist/commands/proposal/propose.js +105 -88
  147. package/dist/commands/proposal/repository.js +1303 -278
  148. package/dist/commands/proposal/validators/proposal-quality-validators.js +16 -6
  149. package/dist/commands/proposal/validators/proposal-validators.js +61 -12
  150. package/dist/commands/proposal/validators/proposals.js +6 -8
  151. package/dist/commands/read/curate.js +78 -73
  152. package/dist/commands/read/knowledge.js +510 -13
  153. package/dist/commands/read/registry-search.js +2 -2
  154. package/dist/commands/read/remember-cli.js +84 -15
  155. package/dist/commands/read/search-cli.js +203 -96
  156. package/dist/commands/read/search.js +126 -94
  157. package/dist/commands/read/show.js +226 -250
  158. package/dist/commands/registry-cli.js +34 -60
  159. package/dist/commands/remember.js +18 -57
  160. package/dist/commands/sources/add-cli.js +104 -49
  161. package/dist/commands/sources/bundle-cli.js +166 -0
  162. package/dist/commands/sources/bundle-config-ops.js +63 -0
  163. package/dist/commands/sources/info.js +27 -15
  164. package/dist/commands/sources/init.js +30 -40
  165. package/dist/commands/sources/installed-stashes.js +469 -172
  166. package/dist/commands/sources/schema-repair.js +10 -9
  167. package/dist/commands/sources/self-update.js +182 -121
  168. package/dist/commands/sources/source-add.js +169 -178
  169. package/dist/commands/sources/source-clone.js +144 -41
  170. package/dist/commands/sources/source-manage.js +94 -59
  171. package/dist/commands/sources/sources-cli.js +64 -205
  172. package/dist/commands/sources/stash-cli.js +91 -54
  173. package/dist/commands/sources/stash-skeleton.js +1 -1
  174. package/dist/commands/tasks/tasks-cli.js +106 -104
  175. package/dist/commands/tasks/tasks.js +445 -262
  176. package/dist/commands/workflow-cli.js +75 -228
  177. package/dist/core/action-contributors.js +1 -1
  178. package/dist/core/activation-policy.js +49 -0
  179. package/dist/core/adapter/adapters/agent-skills-adapter.js +181 -0
  180. package/dist/core/adapter/adapters/akm-adapter.js +528 -0
  181. package/dist/core/adapter/adapters/akm-lint.js +392 -0
  182. package/dist/core/adapter/adapters/akm-metadata.js +387 -0
  183. package/dist/core/adapter/adapters/akm-task-adapter.js +149 -0
  184. package/dist/core/adapter/adapters/akm-workflow-adapter.js +180 -0
  185. package/dist/core/adapter/adapters/claude-adapter.js +61 -0
  186. package/dist/core/adapter/adapters/dotenv-adapter.js +187 -0
  187. package/dist/core/adapter/adapters/generic-files-adapter.js +119 -0
  188. package/dist/core/adapter/adapters/index.js +80 -0
  189. package/dist/core/adapter/adapters/llm-wiki-adapter.js +419 -0
  190. package/dist/core/adapter/adapters/okf-adapter.js +391 -0
  191. package/dist/core/adapter/adapters/opencode-adapter.js +68 -0
  192. package/dist/core/adapter/adapters/shared.js +286 -0
  193. package/dist/core/adapter/adapters/tool-dir-shared.js +217 -0
  194. package/dist/core/adapter/adapters/website-snapshot-adapter.js +155 -0
  195. package/dist/core/adapter/bundle-adapter.js +4 -0
  196. package/dist/core/adapter/detect-adapter.js +17 -0
  197. package/dist/core/adapter/recognize-match.js +44 -0
  198. package/dist/core/adapter/registry.js +56 -0
  199. package/dist/core/adapter/types.js +4 -0
  200. package/dist/core/asset/akm-markdown.js +30 -0
  201. package/dist/core/asset/asset-placement.js +243 -0
  202. package/dist/core/asset/asset-ref.js +110 -79
  203. package/dist/core/asset/asset-serialize.js +20 -0
  204. package/dist/core/asset/frontmatter.js +28 -12
  205. package/dist/core/asset/markdown.js +40 -51
  206. package/dist/core/asset/resolve-ref.js +274 -0
  207. package/dist/core/asset/stash-meta.js +2 -2
  208. package/dist/core/bundle-id.js +51 -0
  209. package/dist/core/common.js +281 -86
  210. package/dist/core/config/config-io.js +42 -128
  211. package/dist/core/config/config-schema.js +233 -855
  212. package/dist/core/config/config-sources.js +162 -39
  213. package/dist/core/config/config-types.js +16 -11
  214. package/dist/core/config/config-version.js +29 -0
  215. package/dist/core/config/config-walker.js +126 -37
  216. package/dist/core/config/config.js +154 -331
  217. package/dist/core/config/deep-merge.js +41 -0
  218. package/dist/core/config/engine-semantics.js +28 -0
  219. package/dist/core/config/experimental.js +21 -0
  220. package/dist/core/config/schema/embedding.js +38 -0
  221. package/dist/core/config/schema/engines.js +116 -0
  222. package/dist/core/config/schema/experimental.js +47 -0
  223. package/dist/core/config/schema/feedback.js +31 -0
  224. package/dist/core/config/schema/improve-processes.js +389 -0
  225. package/dist/core/config/schema/improve.js +94 -0
  226. package/dist/core/config/schema/index-config.js +176 -0
  227. package/dist/core/config/schema/output.js +18 -0
  228. package/dist/core/config/schema/primitives.js +94 -0
  229. package/dist/core/config/schema/search.js +30 -0
  230. package/dist/core/config/schema/setup.js +18 -0
  231. package/dist/core/config/schema/sources-bundles.js +169 -0
  232. package/dist/core/config/schema/workflow.js +29 -0
  233. package/dist/core/env-secret-ref.js +155 -20
  234. package/dist/core/errors.js +17 -15
  235. package/dist/core/events-types.js +4 -0
  236. package/dist/core/events.js +46 -128
  237. package/dist/core/extra-params.js +62 -0
  238. package/dist/core/file-change.js +17 -0
  239. package/dist/core/file-lock.js +202 -57
  240. package/dist/core/fs-txn.js +392 -0
  241. package/dist/core/git-message.js +59 -0
  242. package/dist/core/improve-result.js +167 -0
  243. package/dist/core/lesson-lint.js +1 -17
  244. package/dist/core/logs-db.js +1 -1
  245. package/dist/core/maintenance-barrier.js +135 -0
  246. package/dist/core/migration-operation.js +44 -0
  247. package/dist/core/mutation-target.js +78 -0
  248. package/dist/core/paths.js +22 -25
  249. package/dist/core/platform.js +10 -0
  250. package/dist/core/recognition-util.js +128 -0
  251. package/dist/core/redaction.js +392 -0
  252. package/dist/core/standards/resolve-standards-context.js +36 -65
  253. package/dist/core/standards/resolve-stash-standards.js +2 -2
  254. package/dist/core/standards/resolve-type-conventions.js +5 -5
  255. package/dist/core/state/migrations.js +242 -11
  256. package/dist/core/state-db.js +98 -10
  257. package/dist/core/structured.js +1 -1
  258. package/dist/core/subprocess.js +303 -0
  259. package/dist/core/text-truncation.js +9 -5
  260. package/dist/core/time.js +20 -0
  261. package/dist/core/type-presentation.js +130 -0
  262. package/dist/core/warn.js +0 -3
  263. package/dist/core/write-source.js +834 -118
  264. package/dist/indexer/bundle-identity-guard.js +92 -0
  265. package/dist/indexer/db/graph-db.js +1 -25
  266. package/dist/indexer/db/llm-cache.js +1 -1
  267. package/dist/indexer/ensure-index.js +30 -9
  268. package/dist/indexer/graph/graph-boost.js +9 -30
  269. package/dist/indexer/graph/graph-extraction.js +41 -27
  270. package/dist/indexer/graph/graph-types.js +4 -0
  271. package/dist/indexer/index-writer-lock.js +93 -49
  272. package/dist/indexer/index-written-assets.js +100 -53
  273. package/dist/indexer/indexer.js +746 -329
  274. package/dist/indexer/init.js +18 -25
  275. package/dist/indexer/installations.js +142 -0
  276. package/dist/indexer/passes/dir-staleness.js +18 -10
  277. package/dist/indexer/passes/memory-inference.js +25 -15
  278. package/dist/indexer/passes/metadata.js +412 -243
  279. package/dist/indexer/scan/doc-to-entry.js +160 -0
  280. package/dist/indexer/scan/drain-dir.js +134 -0
  281. package/dist/indexer/search/db-search.js +292 -108
  282. package/dist/indexer/search/fts-query.js +64 -0
  283. package/dist/indexer/search/ranking-contributors.js +145 -25
  284. package/dist/indexer/search/ranking-types.js +4 -0
  285. package/dist/indexer/search/ranking.js +28 -71
  286. package/dist/indexer/search/search-attribution.js +67 -0
  287. package/dist/indexer/search/search-fields.js +18 -3
  288. package/dist/indexer/search/search-hit-enrichers.js +30 -40
  289. package/dist/indexer/search/search-source.js +157 -111
  290. package/dist/indexer/search/semantic-status.js +4 -1
  291. package/dist/indexer/usage/usage-events.js +10 -30
  292. package/dist/indexer/walk/file-context.js +3 -45
  293. package/dist/indexer/walk/matchers.js +42 -73
  294. package/dist/indexer/walk/path-resolver.js +11 -5
  295. package/dist/indexer/walk/walker.js +42 -14
  296. package/dist/integrations/agent/builder-shared.js +7 -0
  297. package/dist/integrations/agent/builders.js +5 -58
  298. package/dist/integrations/agent/config.js +3 -143
  299. package/dist/integrations/agent/detect.js +17 -2
  300. package/dist/integrations/agent/engine-resolution.js +231 -0
  301. package/dist/integrations/agent/index.js +1 -2
  302. package/dist/integrations/agent/model-aliases.js +8 -3
  303. package/dist/integrations/agent/profiles.js +6 -99
  304. package/dist/integrations/agent/prompts.js +46 -18
  305. package/dist/integrations/agent/runner-dispatch.js +78 -13
  306. package/dist/integrations/agent/runner.js +76 -208
  307. package/dist/integrations/agent/spawn.js +48 -279
  308. package/dist/integrations/harnesses/aider/agent-builder.js +9 -8
  309. package/dist/integrations/harnesses/aider/index.js +2 -12
  310. package/dist/integrations/harnesses/amazonq/agent-builder.js +10 -16
  311. package/dist/integrations/harnesses/amazonq/index.js +3 -17
  312. package/dist/integrations/harnesses/claude/agent-builder.js +2 -3
  313. package/dist/integrations/harnesses/claude/config-import.js +1 -3
  314. package/dist/integrations/harnesses/claude/index.js +1 -14
  315. package/dist/integrations/harnesses/claude/session-log.js +27 -75
  316. package/dist/integrations/harnesses/codex/agent-builder.js +8 -7
  317. package/dist/integrations/harnesses/codex/index.js +2 -13
  318. package/dist/integrations/harnesses/copilot/agent-builder.js +9 -9
  319. package/dist/integrations/harnesses/copilot/index.js +1 -13
  320. package/dist/integrations/harnesses/gemini/agent-builder.js +8 -9
  321. package/dist/integrations/harnesses/gemini/index.js +1 -13
  322. package/dist/integrations/harnesses/ids.js +24 -0
  323. package/dist/integrations/harnesses/index.js +31 -33
  324. package/dist/integrations/harnesses/opencode/agent-builder.js +23 -5
  325. package/dist/integrations/harnesses/opencode/config-import.js +1 -3
  326. package/dist/integrations/harnesses/opencode/index.js +1 -18
  327. package/dist/integrations/harnesses/opencode/session-log.js +67 -125
  328. package/dist/integrations/harnesses/opencode-sdk/harness.js +3 -17
  329. package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +274 -191
  330. package/dist/integrations/harnesses/openhands/agent-builder.js +12 -10
  331. package/dist/integrations/harnesses/openhands/index.js +2 -12
  332. package/dist/integrations/harnesses/pi/agent-builder.js +11 -18
  333. package/dist/integrations/harnesses/pi/index.js +3 -16
  334. package/dist/integrations/harnesses/shared.js +17 -0
  335. package/dist/integrations/harnesses/types.js +38 -34
  336. package/dist/integrations/lockfile.js +211 -24
  337. package/dist/integrations/session-logs/index.js +24 -40
  338. package/dist/integrations/session-logs/provider-base.js +113 -0
  339. package/dist/llm/client.js +182 -110
  340. package/dist/llm/embedders/deterministic.js +2 -2
  341. package/dist/llm/embedders/remote.js +21 -9
  342. package/dist/llm/feature-gate.js +17 -57
  343. package/dist/llm/graph-extract.js +12 -13
  344. package/dist/llm/index-passes.js +8 -42
  345. package/dist/llm/memory-infer.js +144 -1
  346. package/dist/llm/metadata-enhance.js +45 -30
  347. package/dist/llm/structured-call.js +16 -8
  348. package/dist/llm/usage-persist.js +30 -5
  349. package/dist/llm/usage-telemetry.js +59 -6
  350. package/dist/output/cli-hints.js +1 -2
  351. package/dist/output/command-registry.js +27 -0
  352. package/dist/output/context.js +22 -7
  353. package/dist/output/format-exempt.js +80 -0
  354. package/dist/output/generic-render.js +251 -0
  355. package/dist/output/html-render.js +11 -16
  356. package/dist/output/render-registry.js +57 -0
  357. package/dist/output/renderers.js +15 -281
  358. package/dist/output/shapes/curate.js +10 -1
  359. package/dist/output/shapes/events.js +12 -7
  360. package/dist/output/shapes/helpers.js +58 -84
  361. package/dist/output/shapes/passthrough.js +8 -40
  362. package/dist/output/shapes/proposal/producer.js +15 -7
  363. package/dist/output/shapes/registry.js +12 -6
  364. package/dist/output/shapes.js +0 -9
  365. package/dist/output/text/{init.js → bundle-create.js} +3 -1
  366. package/dist/output/text/bundle-show.js +7 -0
  367. package/dist/output/text/command-format.js +562 -0
  368. package/dist/output/text/env.js +1 -3
  369. package/dist/output/text/events.js +8 -7
  370. package/dist/output/text/helpers.js +15 -1375
  371. package/dist/output/text/proposal/producer.js +4 -2
  372. package/dist/output/text/proposal-format.js +202 -0
  373. package/dist/output/text/registry-commands.js +1 -2
  374. package/dist/output/text/registry.js +12 -6
  375. package/dist/output/text/show-directives.js +117 -0
  376. package/dist/output/text/show-format.js +103 -0
  377. package/dist/output/text/sync.js +5 -0
  378. package/dist/output/text/workflow-format.js +332 -0
  379. package/dist/output/text/workflow.js +1 -2
  380. package/dist/output/text.js +10 -19
  381. package/dist/registry/factory.js +4 -6
  382. package/dist/registry/origin-resolve.js +16 -27
  383. package/dist/registry/providers/skills-sh.js +3 -3
  384. package/dist/registry/providers/static-index.js +15 -25
  385. package/dist/registry/resolve.js +43 -94
  386. package/dist/registry/semver.js +43 -0
  387. package/dist/runtime.js +81 -12
  388. package/dist/scripts/akm-migrate.js +35529 -0
  389. package/dist/setup/detect.js +5 -7
  390. package/dist/setup/detected-engines.js +136 -0
  391. package/dist/setup/engine-config.js +100 -0
  392. package/dist/setup/registry-stash-loader.js +3 -3
  393. package/dist/setup/semantic-assets.js +12 -9
  394. package/dist/setup/setup.js +444 -208
  395. package/dist/setup/steps/connection-shared.js +120 -0
  396. package/dist/setup/steps/connection.js +108 -305
  397. package/dist/setup/steps/platforms.js +13 -12
  398. package/dist/setup/steps/semantic.js +15 -3
  399. package/dist/setup/steps/sources.js +21 -15
  400. package/dist/setup/steps/stashdir.js +6 -4
  401. package/dist/setup/steps/tasks.js +236 -119
  402. package/dist/setup/steps.js +3 -2
  403. package/dist/sources/freshness.js +39 -0
  404. package/dist/sources/provider-factory.js +11 -17
  405. package/dist/sources/providers/filesystem.js +2 -3
  406. package/dist/sources/providers/git-install.js +278 -34
  407. package/dist/sources/providers/git-provider.js +54 -56
  408. package/dist/sources/providers/git-stash.js +420 -91
  409. package/dist/sources/providers/git.js +2 -2
  410. package/dist/sources/providers/npm.js +16 -19
  411. package/dist/sources/providers/provider-utils.js +47 -22
  412. package/dist/sources/providers/sync-from-ref.js +3 -9
  413. package/dist/sources/providers/website.js +2 -2
  414. package/dist/sources/resolve.js +11 -10
  415. package/dist/sources/snapshot-fetchers/types.js +4 -0
  416. package/dist/sources/{website-ingest.js → snapshot-fetchers/website-ingest.js} +110 -41
  417. package/dist/storage/database.js +60 -4
  418. package/dist/storage/engines/sqlite-migrations.js +156 -5
  419. package/dist/storage/locations.js +1 -2
  420. package/dist/storage/repositories/canaries-repository.js +1 -1
  421. package/dist/storage/repositories/events-repository.js +51 -11
  422. package/dist/storage/repositories/improve-runs-repository.js +6 -32
  423. package/dist/storage/repositories/index-connection.js +79 -0
  424. package/dist/storage/repositories/index-db.js +4 -3
  425. package/dist/storage/repositories/index-entries-repository.js +863 -0
  426. package/dist/{indexer/db/entry-mapper.js → storage/repositories/index-entry-mapper.js} +19 -2
  427. package/dist/storage/repositories/index-entry-types.js +4 -0
  428. package/dist/storage/repositories/index-fts-repository.js +167 -0
  429. package/dist/storage/repositories/index-llm-cache-repository.js +108 -0
  430. package/dist/storage/repositories/index-meta-repository.js +49 -0
  431. package/dist/{indexer/db/schema.js → storage/repositories/index-schema.js} +226 -100
  432. package/dist/storage/repositories/index-sql.js +12 -0
  433. package/dist/storage/repositories/index-utility-repository.js +356 -0
  434. package/dist/storage/repositories/index-vec-repository.js +250 -0
  435. package/dist/storage/repositories/outcome-repository.js +119 -0
  436. package/dist/storage/repositories/proposals-repository.js +317 -75
  437. package/dist/storage/repositories/registry-cache.js +1 -1
  438. package/dist/storage/repositories/salience-repository.js +172 -0
  439. package/dist/storage/repositories/task-history-repository.js +110 -3
  440. package/dist/storage/repositories/workflow-runs-repository.js +68 -35
  441. package/dist/tasks/backends/cron.js +169 -46
  442. package/dist/tasks/backends/exec-utils.js +76 -3
  443. package/dist/tasks/backends/index.js +6 -9
  444. package/dist/tasks/backends/launchd.js +292 -55
  445. package/dist/tasks/backends/schtasks.js +557 -70
  446. package/dist/tasks/backends/types.js +4 -0
  447. package/dist/tasks/command-executable.js +93 -0
  448. package/dist/tasks/embedded.js +56 -38
  449. package/dist/tasks/parser.js +156 -64
  450. package/dist/tasks/resolve-akm-bin.js +144 -51
  451. package/dist/tasks/runner.js +377 -209
  452. package/dist/tasks/schedule.js +108 -19
  453. package/dist/tasks/scheduler-invocation.js +296 -0
  454. package/dist/tasks/schema.js +1 -1
  455. package/dist/tasks/task-id.js +35 -0
  456. package/dist/tasks/validator.js +30 -16
  457. package/dist/workflows/authoring/authoring.js +96 -148
  458. package/dist/workflows/authoring/scope-key.js +1 -1
  459. package/dist/workflows/cli.js +0 -20
  460. package/dist/workflows/concurrency-policy.js +15 -0
  461. package/dist/workflows/exec/brief.js +25 -59
  462. package/dist/workflows/exec/frozen-judge.js +47 -0
  463. package/dist/workflows/exec/native-executor.js +157 -94
  464. package/dist/workflows/exec/report.js +365 -200
  465. package/dist/workflows/exec/run-workflow.js +46 -40
  466. package/dist/workflows/exec/scheduler.js +12 -41
  467. package/dist/workflows/exec/step-work.js +239 -205
  468. package/dist/workflows/exec/workflow-engine-gate.js +67 -0
  469. package/dist/workflows/ir/compile.js +141 -283
  470. package/dist/workflows/ir/freeze.js +233 -0
  471. package/dist/workflows/ir/plan-hash.js +40 -5
  472. package/dist/workflows/ir/schema.js +537 -1
  473. package/dist/workflows/parser.js +878 -306
  474. package/dist/workflows/program/expressions.js +20 -208
  475. package/dist/workflows/program/schema.js +7 -10
  476. package/dist/workflows/renderer.js +99 -121
  477. package/dist/workflows/resource-limits.js +22 -0
  478. package/dist/workflows/runtime/checkin.js +1 -1
  479. package/dist/workflows/runtime/plan-classifier.js +131 -0
  480. package/dist/workflows/runtime/runs.js +200 -113
  481. package/dist/workflows/runtime/unit-checkin.js +1 -1
  482. package/dist/workflows/runtime/unit-phases.js +20 -0
  483. package/dist/workflows/runtime/workflow-asset-loader.js +235 -97
  484. package/dist/workflows/schema.js +1 -11
  485. package/dist/workflows/validate-summary.js +4 -26
  486. package/dist/workflows/validator.js +52 -30
  487. package/docs/README.md +42 -78
  488. package/docs/migration/README.md +8 -0
  489. package/docs/migration/release-notes/0.6.0.md +1 -1
  490. package/docs/migration/release-notes/0.7.0.md +9 -8
  491. package/docs/migration/release-notes/0.9.0.md +158 -14
  492. package/docs/migration/v0.7-to-v0.8.md +46 -47
  493. package/docs/migration/v0.8-to-v0.9.md +844 -0
  494. package/docs/reference/README.md +12 -0
  495. package/docs/reference/data-and-telemetry.md +333 -0
  496. package/package.json +21 -17
  497. package/schemas/akm-asset-envelope.json +93 -0
  498. package/schemas/akm-config.json +4636 -0
  499. package/schemas/akm-task.json +87 -0
  500. package/{dist/schemas → schemas}/akm-workflow.json +127 -82
  501. package/dist/akm-migrate-storage +0 -38
  502. package/dist/assets/help/help-accept.md +0 -12
  503. package/dist/assets/help/help-improve.md +0 -84
  504. package/dist/assets/help/help-proposals.md +0 -17
  505. package/dist/assets/help/help-propose.md +0 -17
  506. package/dist/assets/help/help-reject.md +0 -11
  507. package/dist/assets/profiles/frequent.json +0 -13
  508. package/dist/assets/profiles/recombine-only.json +0 -21
  509. package/dist/assets/profiles/reflect-distill.json +0 -30
  510. package/dist/assets/profiles/synthesize.json +0 -15
  511. package/dist/assets/prompts/procedural-system.md +0 -44
  512. package/dist/assets/prompts/recombine-system.md +0 -40
  513. package/dist/assets/prompts/staleness-detect-system.md +0 -6
  514. package/dist/assets/tasks/core/backup.yml +0 -4
  515. package/dist/assets/tasks/graph-refresh-weekly.yml +0 -10
  516. package/dist/assets/templates/html/default.html +0 -78
  517. package/dist/assets/templates/html/vendor/echarts.min.js +0 -45
  518. package/dist/assets/wiki/index-template.md +0 -12
  519. package/dist/assets/wiki/ingest-workflow-template.md +0 -83
  520. package/dist/assets/wiki/log-template.md +0 -8
  521. package/dist/assets/wiki/schema-template.md +0 -61
  522. package/dist/cli/config-migrate.js +0 -150
  523. package/dist/cli/config-validate.js +0 -39
  524. package/dist/commands/graph/graph-cli.js +0 -124
  525. package/dist/commands/graph/graph.js +0 -487
  526. package/dist/commands/improve/calibration.js +0 -161
  527. package/dist/commands/improve/dedup.js +0 -482
  528. package/dist/commands/improve/extract-watch.js +0 -140
  529. package/dist/commands/improve/hot-probation.js +0 -45
  530. package/dist/commands/improve/improve-auto-accept.js +0 -276
  531. package/dist/commands/improve/improve-profiles.js +0 -168
  532. package/dist/commands/improve/procedural.js +0 -398
  533. package/dist/commands/improve/recombine.js +0 -818
  534. package/dist/commands/improve/schema-similarity-gate.js +0 -168
  535. package/dist/commands/lint/agent-linter.js +0 -44
  536. package/dist/commands/lint/command-linter.js +0 -44
  537. package/dist/commands/lint/default-linter.js +0 -16
  538. package/dist/commands/lint/fact-linter.js +0 -39
  539. package/dist/commands/lint/knowledge-linter.js +0 -16
  540. package/dist/commands/lint/memory-linter.js +0 -61
  541. package/dist/commands/lint/registry.js +0 -41
  542. package/dist/commands/lint/skill-linter.js +0 -45
  543. package/dist/commands/lint/task-linter.js +0 -50
  544. package/dist/commands/lint/workflow-linter.js +0 -81
  545. package/dist/commands/proposal/legacy-import.js +0 -115
  546. package/dist/commands/sources/history.js +0 -196
  547. package/dist/commands/tasks/default-tasks.js +0 -186
  548. package/dist/commands/wiki-cli.js +0 -292
  549. package/dist/core/asset/asset-registry.js +0 -76
  550. package/dist/core/asset/asset-spec.js +0 -316
  551. package/dist/core/config/config-migration.js +0 -602
  552. package/dist/core/deep-merge.js +0 -38
  553. package/dist/core/eval/rank-metrics.js +0 -113
  554. package/dist/core/ripgrep/install.js +0 -163
  555. package/dist/core/ripgrep/resolve.js +0 -81
  556. package/dist/indexer/db/db.js +0 -1414
  557. package/dist/indexer/manifest.js +0 -170
  558. package/dist/indexer/passes/metadata-contributors.js +0 -31
  559. package/dist/indexer/usage/unmigrated-vaults-guard.js +0 -94
  560. package/dist/integrations/harnesses/opencode-sdk/index.js +0 -25
  561. package/dist/llm/call-ai.js +0 -62
  562. package/dist/llm/memory-infer-impl.js +0 -138
  563. package/dist/output/shapes/distill.js +0 -14
  564. package/dist/output/shapes/history.js +0 -11
  565. package/dist/output/text/distill.js +0 -6
  566. package/dist/output/text/enable-disable.js +0 -8
  567. package/dist/output/text/history.js +0 -6
  568. package/dist/output/text/wiki.js +0 -16
  569. package/dist/registry/build-index.js +0 -386
  570. package/dist/schemas/akm-config.json +0 -14225
  571. package/dist/scripts/migrate-storage.js +0 -13169
  572. package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +0 -10169
  573. package/dist/scripts/migrations/v16-to-v17.js +0 -141
  574. package/dist/setup/legacy-config.js +0 -106
  575. package/dist/storage/repositories/consolidation-repository.js +0 -38
  576. package/dist/storage/repositories/recombine-repository.js +0 -213
  577. package/dist/wiki/wiki-templates.js +0 -15
  578. package/dist/wiki/wiki.js +0 -1012
  579. package/dist/workflows/authoring/workflow-program-template.yaml +0 -31
  580. package/dist/workflows/db.js +0 -350
  581. package/dist/workflows/exec/watch.js +0 -116
  582. package/dist/workflows/program/parser.js +0 -760
  583. package/dist/workflows/program/project.js +0 -105
  584. package/docs/data-and-telemetry.md +0 -227
  585. package/docs/migration/release-notes/0.9.0-beta.60.md +0 -19
  586. /package/dist/sources/{wiki-fetchers → snapshot-fetchers}/registry.js +0 -0
  587. /package/dist/sources/{wiki-fetchers → snapshot-fetchers}/youtube.js +0 -0
@@ -44,9 +44,12 @@ import { UsageError } from "../../core/errors.js";
44
44
  import { appendEvent } from "../../core/events.js";
45
45
  import { validateJsonSchemaSubset } from "../../core/json-schema.js";
46
46
  import { withWorkflowRunsRepo } from "../../storage/repositories/workflow-runs-repository.js";
47
- import { computePlanHash } from "../ir/plan-hash.js";
48
- import { parseTemplate, resolveTemplate, resolveWholeValue, } from "../program/expressions.js";
49
- import { buildDefaultSummaryJudge, completeWorkflowStep, } from "../runtime/runs.js";
47
+ import { canonicalJson as canonicalJsonString } from "../ir/plan-hash.js";
48
+ import { resolveReferenceString } from "../program/expressions.js";
49
+ import { WORKFLOW_MAX_MAP_EXPANSION } from "../resource-limits.js";
50
+ import { requireExecutableWorkflowPlan } from "../runtime/plan-classifier.js";
51
+ import { completeWorkflowStep } from "../runtime/runs.js";
52
+ import { GATE_EVALUATION_PHASE } from "../runtime/unit-phases.js";
50
53
  import { enqueueUnitWrite } from "./unit-writer.js";
51
54
  /**
52
55
  * Default per-unit timeout. Deliberately NOT the 60 s agent default
@@ -67,12 +70,51 @@ const GATE_ARTIFACT_CLIP = 4_000;
67
70
  * schema directive), and hash the resolved input. Same inputs ⇒ byte-identical
68
71
  * ids/hashes/prompts — the invariant `brief` relies on to predict the engine.
69
72
  *
70
- * Whole-list failures (missing subgraph, template parse error, unresolvable /
71
- * non-array `over`, duplicate fan-out items) return `{ ok: false }`; a per-unit
72
- * expression-resolution failure is carried on that unit's `resolved` field so
73
- * the caller fails just that unit (mirroring the engine's `expression_error`
74
- * outcome), never the whole step.
73
+ * Whole-list failures (missing subgraph, unresolvable / non-array `over`,
74
+ * null or duplicate fan-out items) return `{ ok: false }`. The per-unit
75
+ * `resolved: { ok: false }` branch is STRUCTURALLY UNREACHABLE in the unified
76
+ * format — prose is never scanned for references, and everything that CAN
77
+ * fail (map.over / route.input / inputs:) resolves once per step, failing the
78
+ * whole list above. The branch is retained because brief/report/executor all
79
+ * share the shape and defensively handle it; if a future unit kind
80
+ * reintroduces per-unit resolution (e.g. an exec/shell unit with real
81
+ * substitution), the cross-surface failure plumbing is already in place.
75
82
  */
83
+ /**
84
+ * Validate a fan-out item list BEFORE any identity/dispatch work: expansion
85
+ * within the resource limit, no null/undefined items, no canonical duplicates.
86
+ * Returns the failure message, or undefined when the list is dispatchable.
87
+ *
88
+ * Null items: producer garbage — there is nothing to hand the unit as its work
89
+ * item. The pre-unification format rejected them incidentally (substituting
90
+ * `${{ item }}` failed); with items attached as context instead of spliced,
91
+ * nothing later would stop a unit from being dispatched with "Item: null", so
92
+ * the rejection is explicit here. Duplicates: content-derived unit identity
93
+ * makes canonical duplicates collide on id — an authoring error caught
94
+ * deterministically, before dispatch.
95
+ */
96
+ function validateFanOutItems(stepId, items) {
97
+ if (items.length > WORKFLOW_MAX_MAP_EXPANSION) {
98
+ return `Step "${stepId}" fan-out expands to ${items.length} units, exceeding the ${WORKFLOW_MAX_MAP_EXPANSION}-unit resource limit.`;
99
+ }
100
+ const nullIndex = items.findIndex((item) => item === null || item === undefined);
101
+ if (nullIndex !== -1) {
102
+ return (`Step "${stepId}" fan-out list contains a null item (index ${nullIndex}). ` +
103
+ `Every item must be a concrete value — fix the producing step's output.`);
104
+ }
105
+ const firstIndexByCanonical = new Map();
106
+ for (let i = 0; i < items.length; i++) {
107
+ const canonical = canonicalJson(items[i]) ?? "null";
108
+ const firstIndex = firstIndexByCanonical.get(canonical);
109
+ if (firstIndex !== undefined) {
110
+ return (`Step "${stepId}" fan-out list contains duplicate items (indices ${firstIndex} and ${i}: ` +
111
+ `${clip(canonical, 200)}). Content-derived unit identity requires distinct items — ` +
112
+ `deduplicate the list this workflow fans out over.`);
113
+ }
114
+ firstIndexByCanonical.set(canonical, i);
115
+ }
116
+ return undefined;
117
+ }
76
118
  export function computeStepWorkList(plan, input) {
77
119
  const root = plan.root;
78
120
  // Route-only steps (YAML `route:`) carry no execution subgraph.
@@ -85,30 +127,29 @@ export function computeStepWorkList(plan, input) {
85
127
  const template = root.kind === "map" ? root.template : root;
86
128
  const reducer = root.kind === "map" ? root.reducer : "collect";
87
129
  const scope = { params: input.params, stepOutputs: input.stepOutputs };
88
- // Parse the instruction template ONCE (deterministic; resolution is a single
89
- // pass per unit — substituted content is never re-scanned). Only nodes the
90
- // frontend marked `templating: "expressions"` carry the `${{ … }}` grammar;
91
- // classic linear markdown is opaque verbatim text.
92
- let instructionSegments;
93
- if (template.templating === "expressions") {
94
- const parsedInstructions = parseTemplate(template.instructions);
95
- if (!parsedInstructions.ok) {
130
+ // Instructions are ALWAYS the step's body prose, byte-exact — never
131
+ // templated, never scanned for reference syntax (workflow-format-
132
+ // unification, spec §2.3). Only `map.over` / `route.input` / `inputs[]`
133
+ // carry the closed reference grammar.
134
+ // Resolve the step's declared `inputs:` ONCE (shared by every unit in this
135
+ // step — map items differ, declared inputs do not): prior-step artifacts
136
+ // attached to every dispatched unit as structured context.
137
+ const resolvedInputs = [];
138
+ for (const reference of template.inputs ?? []) {
139
+ const resolved = resolveReferenceString(reference, scope);
140
+ if (!resolved.ok) {
96
141
  return {
97
142
  ok: false,
98
- error: `Step "${plan.stepId}" instructions template failed to parse: ` +
99
- parsedInstructions.errors.map((e) => e.message).join(" "),
143
+ error: `Step "${plan.stepId}" declared input "${reference}" failed to resolve: ${resolved.error.message}`,
100
144
  };
101
145
  }
102
- instructionSegments = parsedInstructions.segments;
146
+ resolvedInputs.push({ reference, value: resolved.value });
103
147
  }
104
- else {
105
- instructionSegments = [{ kind: "literal", text: template.instructions }];
106
- }
107
- // Resolve fan-out items: `over` is a single whole-value `${{ … }}` reference
108
- // naming its producer explicitly — no ambient key search.
148
+ // Resolve fan-out items: `over` is a single whole-value reference naming
149
+ // its producer explicitly — no ambient key search.
109
150
  let items;
110
151
  if (root.kind === "map") {
111
- const source = resolveWholeValue(root.over, scope);
152
+ const source = resolveReferenceString(root.over, scope);
112
153
  if (!source.ok) {
113
154
  return {
114
155
  ok: false,
@@ -126,94 +167,103 @@ export function computeStepWorkList(plan, input) {
126
167
  else {
127
168
  items = [undefined];
128
169
  }
129
- // Content-derived unit identity: compute every id up front. Duplicate items
130
- // collide on identity — an authoring error caught HERE, deterministically.
131
170
  const isFanOut = root.kind === "map";
171
+ const fanOutProblem = isFanOut ? validateFanOutItems(plan.stepId, items) : undefined;
172
+ if (fanOutProblem)
173
+ return { ok: false, error: fanOutProblem };
174
+ // Content-derived unit identity: compute every id up front (duplicate items
175
+ // were rejected above — identity requires distinct items).
132
176
  const unitIds = items.map((item) => unitIdFor(template.id, item, isFanOut));
133
- if (isFanOut) {
134
- const firstIndexByCanonical = new Map();
135
- for (let i = 0; i < items.length; i++) {
136
- const canonical = canonicalJson(items[i]) ?? "null";
137
- const firstIndex = firstIndexByCanonical.get(canonical);
138
- if (firstIndex !== undefined) {
139
- return {
140
- ok: false,
141
- error: `Step "${plan.stepId}" fan-out list contains duplicate items (indices ${firstIndex} and ${i}: ` +
142
- `${clip(canonical, 200)}). Content-derived unit identity requires distinct items — ` +
143
- `deduplicate the list this workflow fans out over.`,
144
- };
145
- }
146
- firstIndexByCanonical.set(canonical, i);
147
- }
148
- }
149
177
  const gateLoop = input.gateLoop ?? 1;
150
- const timeoutMs = template.timeoutMs === undefined ? DEFAULT_UNIT_TIMEOUT_MS : template.timeoutMs;
178
+ const frozenInvocation = template.invocation;
179
+ if (!frozenInvocation)
180
+ return { ok: false, error: `Step "${plan.stepId}" has no frozen invocation.` };
181
+ const frozenEngine = input.engines?.[frozenInvocation.engine];
182
+ if (!frozenEngine) {
183
+ return { ok: false, error: `Step "${plan.stepId}" references missing frozen engine "${frozenInvocation.engine}".` };
184
+ }
185
+ const runner = frozenEngine.kind === "llm" ? "llm" : frozenEngine.runnerKind;
186
+ const timeoutMs = frozenInvocation.timeoutMs;
151
187
  const units = items.map((item, index) => {
152
188
  const unitId = unitIds[index];
153
189
  // Gate loops (>= 2) journal under `<unitId>~l<loop>` so loop 1's rows are
154
190
  // never clobbered; the content-derived identity (and the prompt's
155
191
  // {{UNIT_ID}}) stays the base id.
156
192
  const journalBaseId = gateLoop > 1 ? `${unitId}~l${gateLoop}` : unitId;
157
- // Single-pass resolution of the pre-parsed template against this unit's
158
- // scope. A resolution failure is deterministic authoring/data breakage.
159
- const unitScope = isFanOut ? { ...scope, item, itemIndex: index } : scope;
160
- const resolvedInstr = resolveTemplate(instructionSegments, unitScope);
161
- let resolved;
162
- if (!resolvedInstr.ok) {
163
- resolved = {
164
- ok: false,
165
- error: `instructions failed to resolve: ${resolvedInstr.errors.map((e) => e.message).join(" ")}`,
166
- };
167
- }
168
- else {
169
- const prompt = buildUnitPrompt({
170
- runId: input.runId,
171
- stepId: plan.stepId,
172
- unitId,
173
- params: input.params,
174
- ...(input.gateFeedback ? { gateFeedback: input.gateFeedback } : {}),
175
- ...(template.schema ? { schema: template.schema } : {}),
176
- instructions: resolvedInstr.text,
177
- });
178
- // Canonical dispatch-input envelope (reviewer finding #1). Every field
179
- // here is a PLAN-FROZEN input that changes what the backend is actually
180
- // asked to do, so a completed unit is reused ONLY when all of them match;
181
- // a change to any of them re-dispatches. Key order is FIXED — it is the
182
- // hash preimage (JSON.stringify preserves insertion order) — and shared
183
- // by ALL surfaces, since this is the ONE place a unit's inputHash is
184
- // computed (engine, brief, and report all call computeStepWorkList), so
185
- // the byte-identical hash across surfaces is structural, not coincidental.
186
- //
187
- // Included beyond the R4 baseline (prompt/runner/model/schema): profile,
188
- // resolved timeoutMs, the env asset ref NAMES, and isolation — each
189
- // reaches dispatch (native-executor's UnitDispatchRequest) and a changed
190
- // one yields a materially different call. `env` carries NAMES ONLY, never
191
- // resolved values: hashing a resolved secret would leak it into a
192
- // durable hash oracle and would spuriously re-dispatch on every secret
193
- // rotation. `retry`/`onError` are DELIBERATELY excluded — they govern
194
- // failed-unit re-dispatch and step-level failure reduction, not a
195
- // COMPLETED unit's inputs/output, so a completed row stays valid across
196
- // policy changes.
197
- //
198
- // Ambient config is DELIBERATELY excluded — the model-alias table, the
199
- // resolved backend/connection, and the working directory (`ctx.workDir` /
200
- // process.cwd()) are NOT plan-frozen. The frozen plan is the identity
201
- // boundary (redesign addendum determinism bar #2): config drift under an
202
- // in-flight run is out of scope by design.
203
- const inputHash = createHash("sha256")
204
- .update(JSON.stringify({
205
- prompt,
206
- runner: template.runner,
207
- profile: template.profile ?? null,
208
- model: template.model ?? null,
209
- schema: template.schema ?? null,
210
- timeoutMs,
211
- env: template.env ?? null,
212
- isolation: template.isolation ?? null,
213
- }))
214
- .digest("hex");
215
- resolved = { ok: true, prompt, inputHash };
216
- }
193
+ // Context attachment (workflow-format-unification, spec §4): every unit
194
+ // receives the run params (already in the preamble), its item + index if
195
+ // it is a map unit, and the artifacts named by its step's `inputs:`.
196
+ // Instructions reach the unit byte-exact — never interpolated.
197
+ const prompt = buildUnitPrompt({
198
+ runId: input.runId,
199
+ stepId: plan.stepId,
200
+ unitId,
201
+ params: input.params,
202
+ ...(isFanOut ? { item, itemIndex: index } : {}),
203
+ ...(resolvedInputs.length > 0 ? { inputs: resolvedInputs } : {}),
204
+ ...(input.gateFeedback ? { gateFeedback: input.gateFeedback } : {}),
205
+ ...(template.schema ? { schema: template.schema } : {}),
206
+ instructions: template.instructions,
207
+ });
208
+ // Canonical dispatch-input envelope (reviewer finding #1). Every field
209
+ // here is a PLAN-FROZEN input that changes what the backend is actually
210
+ // asked to do, so a completed unit is reused ONLY when all of them match;
211
+ // a change to any of them re-dispatches. Key order is FIXED — it is the
212
+ // hash preimage (JSON.stringify preserves insertion order) — and shared
213
+ // by ALL surfaces, since this is the ONE place a unit's inputHash is
214
+ // computed (engine, brief, and report all call computeStepWorkList), so
215
+ // the byte-identical hash across surfaces is structural, not coincidental.
216
+ //
217
+ // Unit identity (workflow-format-unification, spec §2.3/§4) hashes the
218
+ // FROZEN TEMPLATE BYTES (`template.instructions`, byte-exact, never an
219
+ // instantiated/interpolated string) + the canonical item JSON + the
220
+ // declared-input artifact hashes + the params snapshot — instead of a
221
+ // resolved/spliced prompt string, since there is no more splicing. The
222
+ // assembled `prompt` above is what the harness SEES; the hash is over the
223
+ // plan-frozen INPUTS that determine it, which is the same replay contract
224
+ // the old resolved-prompt hash gave (same inputs ⇒ same hash) with the
225
+ // interpolation step removed.
226
+ //
227
+ // Included beyond the R4 baseline (template/runner/model/schema): resolved
228
+ // timeoutMs, the env asset ref NAMES, and isolation — each reaches
229
+ // dispatch (native-executor's UnitDispatchRequest) and a changed one
230
+ // yields a materially different call. `env` carries NAMES ONLY, never
231
+ // resolved values: hashing a resolved secret would leak it into a durable
232
+ // hash oracle and would spuriously re-dispatch on every secret rotation.
233
+ // `retry`/`onError` are DELIBERATELY excluded — they govern failed-unit
234
+ // re-dispatch and step-level failure reduction, not a COMPLETED unit's
235
+ // inputs/output, so a completed row stays valid across policy changes.
236
+ //
237
+ // `gateFeedback` IS included (conditionally, so a no-feedback unit's
238
+ // preimage is byte-identical to before): it is appended to the prompt by
239
+ // `buildUnitPrompt`, so a gate loop's retry is materially a different ask
240
+ // than the rejected attempt — omitting it made loop 1 and loop 2 journal
241
+ // identical hashes for different prompts, breaking the "changed inputs ⇒
242
+ // changed hash" audit contract. Replay-safe: feedback is re-derived from
243
+ // the journaled gate decision, so a resumed retry re-hashes identically.
244
+ //
245
+ // Ambient config is DELIBERATELY excluded — the model-alias table, the
246
+ // resolved backend/connection, and the working directory (`ctx.workDir` /
247
+ // process.cwd()) are NOT plan-frozen. The frozen plan is the identity
248
+ // boundary (redesign addendum determinism bar #2): config drift under an
249
+ // in-flight run is out of scope by design.
250
+ const dispatch = transitiveDispatchSnapshot(frozenEngine, input.engines ?? {});
251
+ const inputHash = createHash("sha256")
252
+ .update(canonicalJsonString({
253
+ hashVersion: 4,
254
+ template: template.instructions,
255
+ item: isFanOut ? (item ?? null) : null,
256
+ inputs: resolvedInputs,
257
+ params: input.params,
258
+ dispatch,
259
+ invocation: frozenInvocation,
260
+ schema: template.schema ?? null,
261
+ env: template.env ?? null,
262
+ isolation: template.isolation ?? "none",
263
+ ...(input.gateFeedback ? { gateFeedback: input.gateFeedback } : {}),
264
+ }))
265
+ .digest("hex");
266
+ const resolved = { ok: true, prompt, inputHash };
217
267
  return {
218
268
  unitId,
219
269
  nodeId: template.id,
@@ -221,9 +271,17 @@ export function computeStepWorkList(plan, input) {
221
271
  item,
222
272
  isFanOut,
223
273
  journalBaseId,
224
- runner: template.runner,
225
- ...(template.profile ? { profile: template.profile } : {}),
226
- ...(template.model ? { model: template.model } : {}),
274
+ runner,
275
+ engine: frozenEngine,
276
+ ...(frozenEngine?.kind === "agent" &&
277
+ frozenEngine.fallbackLlmEngine &&
278
+ input.engines?.[frozenEngine.fallbackLlmEngine]?.kind === "llm"
279
+ ? {
280
+ fallbackEngine: input.engines[frozenEngine.fallbackLlmEngine],
281
+ }
282
+ : {}),
283
+ invocation: frozenInvocation,
284
+ ...(frozenInvocation.model ? { model: frozenInvocation.model } : {}),
227
285
  timeoutMs,
228
286
  ...(template.schema ? { schema: template.schema } : {}),
229
287
  ...(template.env ? { env: template.env } : {}),
@@ -240,13 +298,16 @@ export function computeStepWorkList(plan, input) {
240
298
  };
241
299
  }
242
300
  /**
243
- * Assemble the final prompt: engine preamble + resolved instructions
244
- * (+ gate feedback on loop re-executions, + schema directive). Workflow-
245
- * authored interpolation happened upstream via the expression module; only
246
- * the ENGINE's own preamble placeholders are substituted here.
301
+ * Assemble the final prompt: engine preamble (run params + item/index +
302
+ * declared-input artifacts, all as structured JSON context) + the step's
303
+ * BYTE-EXACT prose instructions (+ gate feedback on loop re-executions, +
304
+ * schema directive). Instructions are NEVER interpolated (workflow-format-
305
+ * unification, spec §2.3) — data reaches the unit as attached context, not
306
+ * string splices; only the ENGINE's own preamble placeholders are substituted
307
+ * here.
247
308
  */
248
309
  export function buildUnitPrompt(input) {
249
- const { runId, stepId, unitId, params, gateFeedback, schema, instructions } = input;
310
+ const { runId, stepId, unitId, params, itemIndex, item, inputs, gateFeedback, schema, instructions } = input;
250
311
  // Function replacements throughout: a string replacement would interpret
251
312
  // GetSubstitution patterns ($&, $$, $', $`) inside VALUES and silently
252
313
  // corrupt the prompt (e.g. a param value containing "$&").
@@ -255,6 +316,15 @@ export function buildUnitPrompt(input) {
255
316
  .replaceAll("{{STEP_ID}}", () => stepId)
256
317
  .replaceAll("{{UNIT_ID}}", () => unitId)
257
318
  .replaceAll("{{PARAMS_JSON}}", () => safeJson(params));
319
+ // Map-unit context: the item this unit was given, plus its index. Attached
320
+ // as structured JSON — the engine never splices it into the instructions.
321
+ const itemBlock = itemIndex !== undefined
322
+ ? `\n\n## Item (index ${itemIndex})\nYou were given this item from the fan-out list:\n${safeJson(item)}`
323
+ : "";
324
+ // Declared `inputs:` context: the prior-step artifacts this step named.
325
+ const inputsBlock = inputs && inputs.length > 0
326
+ ? `\n\n## Declared inputs\n${inputs.map((i) => `### ${i.reference}\n${safeJson(i.value)}`).join("\n\n")}`
327
+ : "";
258
328
  // Gate-loop feedback (R2 max_loops): the judge's rejection is appended so
259
329
  // the re-executed unit can address it — and so the input hash changes,
260
330
  // making the loop's re-dispatch natural instead of a durable-row reuse.
@@ -269,7 +339,7 @@ export function buildUnitPrompt(input) {
269
339
  const schemaDirective = schema
270
340
  ? `\n\nRespond with ONLY a JSON value matching this JSON Schema (no prose, no code fences):\n${safeJson(schema)}`
271
341
  : "";
272
- return `${preamble}\n${instructions}${gateBlock}${schemaDirective}`;
342
+ return `${preamble}\n${instructions}${itemBlock}${inputsBlock}${gateBlock}${schemaDirective}`;
273
343
  }
274
344
  /**
275
345
  * Content-derived unit identity (module doc): `<node_id>:<hash12>` for a
@@ -284,6 +354,16 @@ export function unitIdFor(nodeId, item, isFanOut) {
284
354
  const canonical = canonicalJson(item) ?? "null";
285
355
  return `${nodeId}:${createHash("sha256").update(canonical).digest("hex").slice(0, 12)}`;
286
356
  }
357
+ /** Include an SDK fallback in v3 call identity without copying catalog entries onto nodes. */
358
+ function transitiveDispatchSnapshot(engine, engines) {
359
+ if (engine.kind !== "agent" || !engine.fallbackLlmEngine)
360
+ return engine;
361
+ const fallback = engines[engine.fallbackLlmEngine];
362
+ if (!fallback || fallback.kind !== "llm") {
363
+ throw new UsageError(`Frozen agent engine "${engine.name}" has no valid LLM fallback snapshot.`);
364
+ }
365
+ return { engine, fallback };
366
+ }
287
367
  // ── Step outputs + reducers + typed artifacts ────────────────────────────────
288
368
  /**
289
369
  * The value `${{ steps.<id>.output }}` resolves to for ONE step, given that
@@ -558,8 +638,8 @@ export function isRetryEligibleFailure(workUnit, row, failureReason) {
558
638
  * it)? True for a unit with no terminal row (pending), a still-`running` row (a
559
639
  * live/stale claim another driver holds), or a FAILED row that is still
560
640
  * retry-eligible. False for a COMPLETED row, a terminal non-retry-eligible
561
- * FAILURE, or an UNRESOLVABLE unit (the engine's immediate `expression_error` —
562
- * never reportable). The best terminal attempt (base + `~r<n>` retries) is the
641
+ * FAILURE, or an UNRESOLVABLE unit (a currently-unreachable defensive branch —
642
+ * see the computeStepWorkList doc — never reportable). The best terminal attempt (base + `~r<n>` retries) is the
563
643
  * one consulted, the SAME reuse the engine and reducer apply.
564
644
  */
565
645
  export function unitStillNeedsReport(workUnit, dispatchRows) {
@@ -615,15 +695,8 @@ function sortKeys(value) {
615
695
  // `brief` recovers it from the journal so its loop-N work-list matches the
616
696
  // engine's (redesign addendum R3, task item 2). `native-executor.test.ts`
617
697
  // asserts the round-trip identity.
618
- /**
619
- * `phase` marker stamped on gate-evaluation unit rows. Step ids cannot contain
620
- * dots (`PROGRAM_STEP_ID_PATTERN`), so a step can never be NAMED `x.gate` and
621
- * the synthetic `<stepId>.gate` node id is collision-free against user step
622
- * ids. The phase column is nonetheless the discriminator we key on — an
623
- * explicit marker, not a `node_id` suffix match, so recovery stays robust even
624
- * if the id scheme evolves. Dispatch rows always journal `phase: null`.
625
- */
626
- export const GATE_EVALUATION_PHASE = "gate";
698
+ // GATE_EVALUATION_PHASE moved to ../runtime/unit-phases.ts (leaf) so
699
+ // unit-checkin can key on it without closing the exec ↔ runtime cycle.
627
700
  /** The unit id of a step's gate-evaluation row for a given 1-based loop. */
628
701
  export function gateUnitId(stepId, loop) {
629
702
  return `${stepId}.gate:l${loop}`;
@@ -737,8 +810,9 @@ export async function journalGateEvaluationStart(gate) {
737
810
  // seed in `driveRun` skips these so resume accounting matches live.
738
811
  phase: GATE_EVALUATION_PHASE,
739
812
  runner: "llm",
740
- model: null,
741
- inputHash: null,
813
+ engine: gate.invocation.engine,
814
+ model: gate.invocation.model,
815
+ inputHash: gate.inputHash,
742
816
  startedAt: new Date().toISOString(),
743
817
  })));
744
818
  appendEvent({
@@ -751,10 +825,7 @@ export async function journalGateEvaluationStart(gate) {
751
825
  * Finish the gate-evaluation unit row with the verdict as observed from the
752
826
  * completion outcome: a rejection journals `{ complete: false, missing,
753
827
  * feedback }`; a pass journals `{ complete: true, missing: [] }`; a judge that
754
- * threw (or, on a required gate, returned an unparseable verdict) journals a
755
- * failed row with a NULL verdict. A NON-required errored gate then fails OPEN
756
- * inside `validateStepSummary`; a REQUIRED errored gate BLOCKS the step
757
- * (`finalizeExecutedStep`, Codex round-3 finding A).
828
+ * threw journals a failed row with a NULL verdict and validation is skipped.
758
829
  */
759
830
  export async function journalGateEvaluationFinish(gate, errored, rejection) {
760
831
  const unitId = gateUnitId(gate.stepId, gate.loop);
@@ -785,7 +856,7 @@ export async function journalGateEvaluationFinish(gate, errored, rejection) {
785
856
  * comparison is exact string equality against the declared `when:` matches.
786
857
  */
787
858
  export function evaluateRoute(route, scope) {
788
- const resolved = resolveWholeValue(route.input, scope);
859
+ const resolved = resolveReferenceString(route.input, scope);
789
860
  if (!resolved.ok) {
790
861
  return { ok: false, error: `route input ${route.input} failed to resolve: ${resolved.error.message}` };
791
862
  }
@@ -988,27 +1059,9 @@ export async function finalizeExecutedStep(input) {
988
1059
  return { kind: "failed", summary: result.summary };
989
1060
  }
990
1061
  // Resolve the completion-criteria judge ONCE (reused by the gate below). A
991
- // `null` result means NO judge is available: `undefined` builds the default
992
- // from config (null when offline / misconfigured), and an explicit `null`
993
- // caller override is offline by construction.
994
- const innerJudge = input.summaryJudge === undefined ? buildDefaultSummaryJudge() : input.summaryJudge;
995
- // Reviewer #18: a REQUIRED completion gate must actually be judged. When the
996
- // gate carries criteria but no judge is available, `validateStepSummary` would
997
- // fail OPEN and silently pass the gate — exactly the offline/misconfigured
998
- // bypass a required gate exists to prevent. BLOCK the step instead (a human
999
- // resolves it via the documented manual path), rather than advance the spine
1000
- // on an unjudged gate. `gate.required` rides the frozen plan (both surfaces);
1001
- // `requireGates` is the engine's run-wide `--require-gates` override. Checked
1002
- // BEFORE route evaluation so a blocked step journals no route decision.
1003
- const gateRequired = stepPlan.gate.required === true || input.requireGates === true;
1004
- if (gateRequired && completionCriteria.length > 0 && innerJudge === null) {
1005
- const notes = `Step "${stepId}" has a REQUIRED completion gate but no summary-validation judge is available ` +
1006
- `(no LLM is configured, or default LLM resolution failed). A required gate must be judged — refusing to fail ` +
1007
- `open and silently pass it. The step is BLOCKED: configure an LLM, then \`akm workflow resume ${runId}\` to ` +
1008
- `re-evaluate the gate, or advance the step manually with \`akm workflow complete\`.`;
1009
- await completeWorkflowStep({ runId, stepId, status: "blocked", notes, evidence: result.evidence, ...lease });
1010
- return { kind: "blocked", summary: notes };
1011
- }
1062
+ // A frozen plan either supplies its judge at the dispatch boundary or has no
1063
+ // judge. Re-selecting defaults here would let config drift change a run.
1064
+ const innerJudge = input.summaryJudge ?? null;
1012
1065
  // Route evaluation BEFORE completion: an unroutable value is an
1013
1066
  // authoring/config failure that must fail the step deterministically.
1014
1067
  let summaryOverride;
@@ -1040,12 +1093,40 @@ export async function finalizeExecutedStep(input) {
1040
1093
  // Journal engine-driven judge calls as unit rows (they are LLM calls). The
1041
1094
  // wrapper's `invoked` stays false when the gate is fail-open (no criteria / no
1042
1095
  // judge) — nothing is journaled, and human approvals are never cached.
1043
- const gateUnit = { runId, workflowRef, stepId, loop: gateLoop };
1096
+ const frozenGate = innerJudge
1097
+ ? await withWorkflowRunsRepo((repo) => {
1098
+ const row = repo.getRunById(runId);
1099
+ if (!row)
1100
+ throw new UsageError(`Workflow run ${runId} was not found.`);
1101
+ const plan = requireExecutableWorkflowPlan(row);
1102
+ const invocation = plan.steps.find((step) => step.stepId === stepId)?.gate.judge ?? null;
1103
+ return invocation ? { invocation, engine: plan.execution?.engines[invocation.engine] ?? null } : null;
1104
+ })
1105
+ : null;
1106
+ const gateInvocation = frozenGate?.invocation ?? null;
1107
+ let gateUnit;
1044
1108
  const judgeState = { invoked: false, errored: false };
1045
1109
  const summaryJudge = innerJudge
1046
1110
  ? async (prompt) => {
1047
1111
  judgeState.invoked = true;
1048
- await journalGateEvaluationStart(gateUnit);
1112
+ if (gateInvocation) {
1113
+ gateUnit = {
1114
+ runId,
1115
+ workflowRef,
1116
+ stepId,
1117
+ loop: gateLoop,
1118
+ invocation: gateInvocation,
1119
+ inputHash: createHash("sha256")
1120
+ .update(canonicalJsonString({
1121
+ hashVersion: 3,
1122
+ dispatch: frozenGate?.engine ?? null,
1123
+ invocation: gateInvocation,
1124
+ prompt,
1125
+ }))
1126
+ .digest("hex"),
1127
+ };
1128
+ await journalGateEvaluationStart(gateUnit);
1129
+ }
1049
1130
  try {
1050
1131
  return await innerJudge(prompt);
1051
1132
  }
@@ -1058,8 +1139,7 @@ export async function finalizeExecutedStep(input) {
1058
1139
  // Reviewer #6: once the judge is invoked, its gate row is journaled `running`
1059
1140
  // (journalGateEvaluationStart) and MUST be finished on every exit. The
1060
1141
  // already-fixed window is the judge itself throwing (caught inside
1061
- // validateStepSummary — `judgeState.errored` records it; a non-required gate
1062
- // fails open, a required gate blocks below). The remaining
1142
+ // validateStepSummary — `judgeState.errored` records it). The remaining
1063
1143
  // window is `completeWorkflowStep` throwing AFTER the judge ran — a stolen
1064
1144
  // lease, a concurrent state change, a DB error — which would otherwise skip the
1065
1145
  // finish and strand the gate row in `running`. Finish it as an errored row (the
@@ -1073,38 +1153,17 @@ export async function finalizeExecutedStep(input) {
1073
1153
  summary,
1074
1154
  evidence: result.evidence,
1075
1155
  summaryJudge,
1076
- // Codex round-3 finding A: mark this completion's gate REQUIRED so
1077
- // `validateStepSummary` does NOT fail open when the judge throws / is
1078
- // unreachable / returns garbage — it flags `errored` and we block below.
1079
- ...(gateRequired ? { requireGate: true } : {}),
1080
1156
  ...lease,
1081
1157
  });
1082
1158
  }
1083
1159
  catch (err) {
1084
- if (judgeState.invoked)
1160
+ if (gateUnit)
1085
1161
  await journalGateEvaluationFinish(gateUnit, true, undefined);
1086
1162
  throw err;
1087
1163
  }
1088
1164
  const rejection = "ok" in completion && completion.ok === false ? completion : undefined;
1089
- // A required gate whose judge could not be evaluated is an errored gate, not a
1090
- // real rejection: journal the gate row as errored (verdict null) so the
1091
- // observed outcome is honest, driven by EITHER the wrapper catching a throw OR
1092
- // validateStepSummary flagging an unparseable verdict.
1093
- const gateErrored = judgeState.errored || rejection?.errored === true;
1094
- if (judgeState.invoked) {
1095
- await journalGateEvaluationFinish(gateUnit, gateErrored, rejection);
1096
- }
1097
- // Codex round-3 finding A: a REQUIRED gate that could not be judged (the judge
1098
- // threw, was unreachable, or returned an unparseable verdict) must NOT fail
1099
- // open and advance. The gate row is journaled errored above; BLOCK the step (a
1100
- // human resolves it) instead of silently passing an unjudged required gate.
1101
- if (rejection?.errored) {
1102
- const notes = `Step "${stepId}" has a REQUIRED completion gate but its summary-validation judge failed to return a verdict ` +
1103
- `(the LLM threw, was unreachable, or returned an unparseable response). A required gate must be judged — refusing ` +
1104
- `to fail open and silently pass it. The step is BLOCKED: fix the LLM/connection, then \`akm workflow resume ${runId}\` ` +
1105
- `to re-evaluate the gate, or advance the step manually with \`akm workflow complete\`.`;
1106
- await completeWorkflowStep({ runId, stepId, status: "blocked", notes, evidence: result.evidence, ...lease });
1107
- return { kind: "blocked", summary: notes };
1165
+ if (gateUnit) {
1166
+ await journalGateEvaluationFinish(gateUnit, judgeState.errored, rejection);
1108
1167
  }
1109
1168
  if (!rejection) {
1110
1169
  return { kind: "advanced", ...(summaryOverride !== undefined ? { summaryOverride } : {}) };
@@ -1117,31 +1176,6 @@ export async function finalizeExecutedStep(input) {
1117
1176
  gateRejection: { stepId, missing: rejection.missing, feedback: rejection.feedback },
1118
1177
  };
1119
1178
  }
1120
- // ── Frozen plan parse + integrity check (shared) ─────────────────────────────
1121
- /**
1122
- * Parse and integrity-check a run's frozen plan JSON (migration 006). Shared by
1123
- * the engine loop's plan loader (`run-workflow.ts`) and the R3 brief/report
1124
- * surfaces so all three apply the SAME corruption + hash checks — the frozen
1125
- * plan the engine executes is the exact plan brief describes and report
1126
- * validates against. A NULL `plan_json` is the CALLER's decision (the engine
1127
- * warns and compiles from the asset; brief/report error), so this helper only
1128
- * handles a PRESENT plan string.
1129
- */
1130
- export function parseFrozenPlan(runId, planJson, planHash) {
1131
- let plan;
1132
- try {
1133
- plan = JSON.parse(planJson);
1134
- }
1135
- catch {
1136
- throw new UsageError(`Workflow run ${runId} has a corrupt frozen plan (plan_json is not valid JSON). ` +
1137
- `The journaled plan cannot be executed — start a new run.`);
1138
- }
1139
- if (computePlanHash(plan) !== planHash) {
1140
- throw new UsageError(`Workflow run ${runId} failed the frozen-plan integrity check: plan_json does not match plan_hash. ` +
1141
- `The journaled plan was modified after the run started — refusing to execute it. Start a new run.`);
1142
- }
1143
- return plan;
1144
- }
1145
1179
  // ── Small helpers ────────────────────────────────────────────────────────────
1146
1180
  function safeJson(value) {
1147
1181
  try {