akm-cli 0.9.0-rc.0 → 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 (598) hide show
  1. package/CHANGELOG.md +1283 -22
  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/prompts/workflow-unit-preamble.md +26 -0
  26. package/dist/assets/stash-skeleton/README.md +38 -10
  27. package/dist/assets/stash-skeleton/facts/conventions/assets/agent.md +8 -0
  28. package/dist/assets/stash-skeleton/facts/conventions/assets/command.md +8 -0
  29. package/dist/assets/stash-skeleton/facts/conventions/assets/fact.md +14 -1
  30. package/dist/assets/stash-skeleton/facts/conventions/assets/knowledge.md +13 -1
  31. package/dist/assets/stash-skeleton/facts/conventions/assets/lesson.md +9 -1
  32. package/dist/assets/stash-skeleton/facts/conventions/assets/memory.md +11 -0
  33. package/dist/assets/stash-skeleton/facts/conventions/assets/script.md +9 -0
  34. package/dist/assets/stash-skeleton/facts/conventions/assets/skill.md +9 -0
  35. package/dist/assets/stash-skeleton/facts/conventions/assets/workflow.md +8 -0
  36. package/dist/assets/stash-skeleton/facts/conventions/backlinks.md +100 -0
  37. package/dist/assets/stash-skeleton/facts/conventions/domains.md +64 -0
  38. package/dist/assets/stash-skeleton/facts/conventions/organization.md +136 -0
  39. package/dist/assets/tasks/core/extract.yml +3 -2
  40. package/dist/assets/tasks/core/improve.yml +2 -1
  41. package/dist/assets/tasks/core/index-refresh.yml +1 -0
  42. package/dist/assets/tasks/core/sync.yml +1 -0
  43. package/dist/assets/tasks/core/version-check.yml +2 -1
  44. package/dist/assets/tasks/improve/akm-graph-refresh-weekly.yml +5 -0
  45. package/dist/assets/tasks/improve/akm-improve-catchup.yml +8 -0
  46. package/dist/assets/tasks/improve/akm-improve-consolidate.yml +5 -0
  47. package/dist/assets/tasks/improve/akm-improve-frequent.yml +5 -0
  48. package/dist/assets/tasks/improve/akm-improve-nightly.yml +5 -0
  49. package/dist/assets/templates/html/health.html +5 -4
  50. package/dist/assets/workflows/workflow-template.md +31 -15
  51. package/dist/cli/invocation.js +279 -0
  52. package/dist/cli/parse-args.js +5 -90
  53. package/dist/cli/retired-commands.js +78 -0
  54. package/dist/cli/shared.js +158 -48
  55. package/dist/cli-node.mjs +2 -1
  56. package/dist/cli.js +747 -293
  57. package/dist/commands/agent/agent-dispatch.js +19 -18
  58. package/dist/commands/agent/agent-support.js +0 -24
  59. package/dist/commands/agent/contribute-cli.js +43 -97
  60. package/dist/commands/completions.js +80 -23
  61. package/dist/commands/config-cli.js +44 -281
  62. package/dist/commands/env/env-binding.js +99 -0
  63. package/dist/commands/env/env-cli.js +84 -224
  64. package/dist/commands/env/env.js +12 -163
  65. package/dist/commands/env/marker-path.js +6 -0
  66. package/dist/commands/env/secret-cli.js +45 -61
  67. package/dist/commands/env/secret.js +32 -62
  68. package/dist/commands/feedback-cli.js +179 -85
  69. package/dist/commands/health/accept-rate.js +58 -0
  70. package/dist/commands/health/advisories.js +7 -8
  71. package/dist/commands/health/checks.js +279 -94
  72. package/dist/commands/health/html-report.js +197 -578
  73. package/dist/commands/health/improve-metrics.js +277 -246
  74. package/dist/commands/health/llm-usage.js +19 -19
  75. package/dist/commands/health/md-report.js +16 -7
  76. package/dist/commands/health/metrics.js +67 -32
  77. package/dist/commands/health/renderers.js +47 -0
  78. package/dist/commands/health/report-view-model.js +508 -0
  79. package/dist/commands/health/stash-exposure.js +1 -1
  80. package/dist/commands/health/surfaces.js +16 -56
  81. package/dist/commands/health/task-runs.js +3 -67
  82. package/dist/{migrate-storage-node.mjs → commands/health/types-checks.js} +1 -5
  83. package/dist/commands/health/types-improve.js +29 -0
  84. package/dist/{output/text/save.js → commands/health/types-metrics.js} +1 -2
  85. package/dist/commands/health/types-result.js +7 -0
  86. package/dist/commands/health/types-runs.js +4 -0
  87. package/dist/commands/health/types-session-log.js +4 -0
  88. package/dist/commands/health/types-windows.js +4 -0
  89. package/dist/commands/health/types.js +26 -21
  90. package/dist/commands/health/windows.js +2 -3
  91. package/dist/commands/health.js +296 -167
  92. package/dist/commands/improve/anti-collapse.js +5 -5
  93. package/dist/commands/improve/autonomy-gate.js +68 -0
  94. package/dist/commands/improve/collapse-detector.js +65 -52
  95. package/dist/commands/improve/consolidate/chunking.js +9 -7
  96. package/dist/commands/improve/consolidate/eligibility.js +1 -23
  97. package/dist/commands/improve/consolidate/merge.js +4 -0
  98. package/dist/commands/improve/consolidate.js +454 -1354
  99. package/dist/commands/improve/content-hash.js +39 -0
  100. package/dist/commands/improve/distill/content-repair.js +4 -10
  101. package/dist/commands/improve/distill/promote-memory.js +89 -64
  102. package/dist/commands/improve/distill/quality-gate.js +118 -42
  103. package/dist/commands/improve/distill-guards.js +1 -1
  104. package/dist/commands/improve/distill-promotion-policy.js +33 -888
  105. package/dist/commands/improve/distill.js +607 -363
  106. package/dist/commands/improve/eligibility.js +165 -79
  107. package/dist/commands/improve/extract-cli.js +35 -126
  108. package/dist/commands/improve/extract-prompt.js +6 -35
  109. package/dist/commands/improve/extract.js +640 -391
  110. package/dist/commands/improve/feedback-valence.js +2 -12
  111. package/dist/commands/improve/improve-cli.js +134 -135
  112. package/dist/commands/improve/improve-result-file.js +30 -50
  113. package/dist/commands/improve/improve-run-types.js +4 -0
  114. package/dist/commands/improve/improve-strategies.js +135 -0
  115. package/dist/commands/improve/improve.js +904 -701
  116. package/dist/commands/improve/locks.js +64 -111
  117. package/dist/commands/improve/loop-stages.js +1110 -923
  118. package/dist/commands/improve/memory/derived-ref.js +124 -0
  119. package/dist/commands/improve/memory/memory-belief.js +79 -7
  120. package/dist/commands/improve/memory/memory-contradiction-detect.js +49 -52
  121. package/dist/commands/improve/memory/memory-improve.js +25 -37
  122. package/dist/commands/improve/outcome-loop.js +25 -88
  123. package/dist/commands/improve/preparation.js +1034 -813
  124. package/dist/commands/improve/proactive-maintenance.js +34 -9
  125. package/dist/commands/improve/proposal-envelope.js +31 -0
  126. package/dist/commands/improve/reflect.js +983 -794
  127. package/dist/commands/improve/run-context.js +119 -0
  128. package/dist/commands/improve/salience.js +24 -127
  129. package/dist/commands/improve/session-asset.js +7 -3
  130. package/dist/commands/improve/shared.js +14 -34
  131. package/dist/commands/improve/source-identity.js +28 -0
  132. package/dist/commands/improve/triage.js +20 -17
  133. package/dist/commands/lint/base-linter.js +340 -313
  134. package/dist/commands/lint/env-key-rules.js +31 -47
  135. package/dist/commands/lint/index.js +185 -30
  136. package/dist/commands/{events.js → log.js} +28 -38
  137. package/dist/commands/migrate-cli.js +54 -0
  138. package/dist/commands/migration-tool.js +55 -0
  139. package/dist/commands/observability-cli.js +70 -208
  140. package/dist/commands/proposal/diff-format.js +50 -0
  141. package/dist/commands/proposal/drain-policies.js +0 -6
  142. package/dist/commands/proposal/drain.js +91 -40
  143. package/dist/commands/proposal/proposal-cli.js +134 -132
  144. package/dist/commands/proposal/proposal-types.js +56 -0
  145. package/dist/commands/proposal/proposal.js +83 -65
  146. package/dist/commands/proposal/propose-cli.js +88 -0
  147. package/dist/commands/proposal/propose.js +105 -88
  148. package/dist/commands/proposal/repository.js +1303 -278
  149. package/dist/commands/proposal/validators/proposal-quality-validators.js +16 -6
  150. package/dist/commands/proposal/validators/proposal-validators.js +61 -12
  151. package/dist/commands/proposal/validators/proposals.js +6 -8
  152. package/dist/commands/read/curate.js +78 -73
  153. package/dist/commands/read/knowledge.js +510 -13
  154. package/dist/commands/read/registry-search.js +2 -2
  155. package/dist/commands/read/remember-cli.js +84 -15
  156. package/dist/commands/read/search-cli.js +203 -96
  157. package/dist/commands/read/search.js +126 -94
  158. package/dist/commands/read/show.js +226 -250
  159. package/dist/commands/registry-cli.js +34 -60
  160. package/dist/commands/remember.js +18 -57
  161. package/dist/commands/sources/add-cli.js +104 -49
  162. package/dist/commands/sources/bundle-cli.js +166 -0
  163. package/dist/commands/sources/bundle-config-ops.js +63 -0
  164. package/dist/commands/sources/info.js +27 -15
  165. package/dist/commands/sources/init.js +30 -40
  166. package/dist/commands/sources/installed-stashes.js +469 -172
  167. package/dist/commands/sources/migration-help.js +7 -4
  168. package/dist/commands/sources/schema-repair.js +10 -9
  169. package/dist/commands/sources/self-update.js +182 -121
  170. package/dist/commands/sources/source-add.js +169 -178
  171. package/dist/commands/sources/source-clone.js +144 -41
  172. package/dist/commands/sources/source-manage.js +94 -59
  173. package/dist/commands/sources/sources-cli.js +64 -205
  174. package/dist/commands/sources/stash-cli.js +91 -54
  175. package/dist/commands/sources/stash-skeleton.js +1 -1
  176. package/dist/commands/tasks/tasks-cli.js +106 -104
  177. package/dist/commands/tasks/tasks.js +445 -262
  178. package/dist/commands/workflow-cli.js +232 -121
  179. package/dist/core/action-contributors.js +1 -1
  180. package/dist/core/activation-policy.js +49 -0
  181. package/dist/core/adapter/adapters/agent-skills-adapter.js +181 -0
  182. package/dist/core/adapter/adapters/akm-adapter.js +528 -0
  183. package/dist/core/adapter/adapters/akm-lint.js +392 -0
  184. package/dist/core/adapter/adapters/akm-metadata.js +387 -0
  185. package/dist/core/adapter/adapters/akm-task-adapter.js +149 -0
  186. package/dist/core/adapter/adapters/akm-workflow-adapter.js +180 -0
  187. package/dist/core/adapter/adapters/claude-adapter.js +61 -0
  188. package/dist/core/adapter/adapters/dotenv-adapter.js +187 -0
  189. package/dist/core/adapter/adapters/generic-files-adapter.js +119 -0
  190. package/dist/core/adapter/adapters/index.js +80 -0
  191. package/dist/core/adapter/adapters/llm-wiki-adapter.js +419 -0
  192. package/dist/core/adapter/adapters/okf-adapter.js +391 -0
  193. package/dist/core/adapter/adapters/opencode-adapter.js +68 -0
  194. package/dist/core/adapter/adapters/shared.js +286 -0
  195. package/dist/core/adapter/adapters/tool-dir-shared.js +217 -0
  196. package/dist/core/adapter/adapters/website-snapshot-adapter.js +155 -0
  197. package/dist/core/adapter/bundle-adapter.js +4 -0
  198. package/dist/core/adapter/detect-adapter.js +17 -0
  199. package/dist/core/adapter/recognize-match.js +44 -0
  200. package/dist/core/adapter/registry.js +56 -0
  201. package/dist/core/adapter/types.js +4 -0
  202. package/dist/core/asset/akm-markdown.js +30 -0
  203. package/dist/core/asset/asset-placement.js +243 -0
  204. package/dist/core/asset/asset-ref.js +110 -79
  205. package/dist/core/asset/asset-serialize.js +20 -0
  206. package/dist/core/asset/frontmatter.js +28 -12
  207. package/dist/core/asset/markdown.js +40 -51
  208. package/dist/core/asset/resolve-ref.js +274 -0
  209. package/dist/core/asset/stash-meta.js +2 -2
  210. package/dist/core/bundle-id.js +51 -0
  211. package/dist/core/common.js +281 -86
  212. package/dist/core/config/config-io.js +42 -128
  213. package/dist/core/config/config-schema.js +233 -834
  214. package/dist/core/config/config-sources.js +162 -39
  215. package/dist/core/config/config-types.js +16 -11
  216. package/dist/core/config/config-version.js +29 -0
  217. package/dist/core/config/config-walker.js +126 -37
  218. package/dist/core/config/config.js +154 -331
  219. package/dist/core/config/deep-merge.js +41 -0
  220. package/dist/core/config/engine-semantics.js +28 -0
  221. package/dist/core/config/experimental.js +21 -0
  222. package/dist/core/config/schema/embedding.js +38 -0
  223. package/dist/core/config/schema/engines.js +116 -0
  224. package/dist/core/config/schema/experimental.js +47 -0
  225. package/dist/core/config/schema/feedback.js +31 -0
  226. package/dist/core/config/schema/improve-processes.js +389 -0
  227. package/dist/core/config/schema/improve.js +94 -0
  228. package/dist/core/config/schema/index-config.js +176 -0
  229. package/dist/core/config/schema/output.js +18 -0
  230. package/dist/core/config/schema/primitives.js +94 -0
  231. package/dist/core/config/schema/search.js +30 -0
  232. package/dist/core/config/schema/setup.js +18 -0
  233. package/dist/core/config/schema/sources-bundles.js +169 -0
  234. package/dist/core/config/schema/workflow.js +29 -0
  235. package/dist/core/env-secret-ref.js +155 -20
  236. package/dist/core/errors.js +17 -15
  237. package/dist/core/events-types.js +4 -0
  238. package/dist/core/events.js +46 -128
  239. package/dist/core/extra-params.js +62 -0
  240. package/dist/core/file-change.js +17 -0
  241. package/dist/core/file-lock.js +202 -57
  242. package/dist/core/fs-txn.js +392 -0
  243. package/dist/core/git-message.js +59 -0
  244. package/dist/core/improve-result.js +167 -0
  245. package/dist/core/json-schema.js +142 -0
  246. package/dist/core/lesson-lint.js +1 -17
  247. package/dist/core/logs-db.js +1 -1
  248. package/dist/core/maintenance-barrier.js +135 -0
  249. package/dist/core/migration-operation.js +44 -0
  250. package/dist/core/mutation-target.js +78 -0
  251. package/dist/core/paths.js +22 -25
  252. package/dist/core/platform.js +10 -0
  253. package/dist/core/recognition-util.js +128 -0
  254. package/dist/core/redaction.js +392 -0
  255. package/dist/core/standards/resolve-standards-context.js +36 -65
  256. package/dist/core/standards/resolve-stash-standards.js +2 -2
  257. package/dist/core/standards/resolve-type-conventions.js +5 -5
  258. package/dist/core/state/migrations.js +242 -11
  259. package/dist/core/state-db.js +98 -10
  260. package/dist/core/structured.js +1 -1
  261. package/dist/core/subprocess.js +303 -0
  262. package/dist/core/text-truncation.js +9 -5
  263. package/dist/core/time.js +20 -0
  264. package/dist/core/type-presentation.js +130 -0
  265. package/dist/core/warn.js +0 -3
  266. package/dist/core/write-source.js +834 -118
  267. package/dist/indexer/bundle-identity-guard.js +92 -0
  268. package/dist/indexer/db/graph-db.js +1 -25
  269. package/dist/indexer/db/llm-cache.js +1 -1
  270. package/dist/indexer/ensure-index.js +30 -9
  271. package/dist/indexer/graph/graph-boost.js +9 -30
  272. package/dist/indexer/graph/graph-extraction.js +41 -27
  273. package/dist/indexer/graph/graph-types.js +4 -0
  274. package/dist/indexer/index-writer-lock.js +93 -49
  275. package/dist/indexer/index-written-assets.js +100 -53
  276. package/dist/indexer/indexer.js +746 -329
  277. package/dist/indexer/init.js +18 -25
  278. package/dist/indexer/installations.js +142 -0
  279. package/dist/indexer/passes/dir-staleness.js +18 -10
  280. package/dist/indexer/passes/memory-inference.js +25 -15
  281. package/dist/indexer/passes/metadata.js +412 -243
  282. package/dist/indexer/scan/doc-to-entry.js +160 -0
  283. package/dist/indexer/scan/drain-dir.js +134 -0
  284. package/dist/indexer/search/db-search.js +292 -108
  285. package/dist/indexer/search/fts-query.js +64 -0
  286. package/dist/indexer/search/ranking-contributors.js +145 -25
  287. package/dist/indexer/search/ranking-types.js +4 -0
  288. package/dist/indexer/search/ranking.js +28 -71
  289. package/dist/indexer/search/search-attribution.js +67 -0
  290. package/dist/indexer/search/search-fields.js +18 -3
  291. package/dist/indexer/search/search-hit-enrichers.js +30 -40
  292. package/dist/indexer/search/search-source.js +157 -111
  293. package/dist/indexer/search/semantic-status.js +4 -1
  294. package/dist/indexer/usage/usage-events.js +10 -30
  295. package/dist/indexer/walk/file-context.js +3 -45
  296. package/dist/indexer/walk/matchers.js +42 -34
  297. package/dist/indexer/walk/path-resolver.js +11 -5
  298. package/dist/indexer/walk/walker.js +42 -14
  299. package/dist/integrations/agent/builder-shared.js +7 -0
  300. package/dist/integrations/agent/builders.js +5 -56
  301. package/dist/integrations/agent/config.js +3 -143
  302. package/dist/integrations/agent/detect.js +17 -2
  303. package/dist/integrations/agent/engine-resolution.js +231 -0
  304. package/dist/integrations/agent/index.js +1 -2
  305. package/dist/integrations/agent/model-aliases.js +16 -2
  306. package/dist/integrations/agent/profiles.js +36 -62
  307. package/dist/integrations/agent/prompts.js +46 -18
  308. package/dist/integrations/agent/runner-dispatch.js +93 -4
  309. package/dist/integrations/agent/runner.js +76 -208
  310. package/dist/integrations/agent/spawn.js +88 -196
  311. package/dist/integrations/harnesses/aider/agent-builder.js +114 -0
  312. package/dist/integrations/harnesses/aider/index.js +48 -0
  313. package/dist/integrations/harnesses/aider/result-extractor.js +53 -0
  314. package/dist/integrations/harnesses/amazonq/agent-builder.js +147 -0
  315. package/dist/integrations/harnesses/amazonq/index.js +45 -0
  316. package/dist/integrations/harnesses/amazonq/result-extractor.js +48 -0
  317. package/dist/integrations/harnesses/claude/agent-builder.js +46 -8
  318. package/dist/integrations/harnesses/claude/config-import.js +1 -3
  319. package/dist/integrations/harnesses/claude/index.js +24 -35
  320. package/dist/integrations/harnesses/claude/result-extractor.js +52 -0
  321. package/dist/integrations/harnesses/claude/session-log.js +27 -75
  322. package/dist/integrations/harnesses/codex/agent-builder.js +138 -0
  323. package/dist/integrations/harnesses/codex/index.js +52 -0
  324. package/dist/integrations/harnesses/codex/result-extractor.js +73 -0
  325. package/dist/integrations/harnesses/copilot/agent-builder.js +122 -0
  326. package/dist/integrations/harnesses/copilot/index.js +48 -0
  327. package/dist/integrations/harnesses/copilot/result-extractor.js +151 -0
  328. package/dist/integrations/harnesses/gemini/agent-builder.js +120 -0
  329. package/dist/integrations/harnesses/gemini/index.js +48 -0
  330. package/dist/integrations/harnesses/gemini/result-extractor.js +121 -0
  331. package/dist/integrations/harnesses/ids.js +24 -0
  332. package/dist/integrations/harnesses/index.js +54 -34
  333. package/dist/integrations/harnesses/opencode/agent-builder.js +23 -5
  334. package/dist/integrations/harnesses/opencode/config-import.js +1 -3
  335. package/dist/integrations/harnesses/opencode/index.js +14 -32
  336. package/dist/integrations/harnesses/opencode/session-log.js +67 -125
  337. package/dist/integrations/harnesses/opencode-sdk/harness.js +51 -0
  338. package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +681 -108
  339. package/dist/integrations/harnesses/openhands/agent-builder.js +128 -0
  340. package/dist/integrations/harnesses/openhands/index.js +48 -0
  341. package/dist/integrations/harnesses/openhands/result-extractor.js +103 -0
  342. package/dist/integrations/harnesses/pi/agent-builder.js +97 -0
  343. package/dist/integrations/harnesses/pi/index.js +45 -0
  344. package/dist/integrations/harnesses/pi/result-extractor.js +135 -0
  345. package/dist/integrations/harnesses/shared.js +17 -0
  346. package/dist/integrations/harnesses/types.js +43 -32
  347. package/dist/integrations/lockfile.js +211 -24
  348. package/dist/integrations/session-logs/index.js +36 -39
  349. package/dist/integrations/session-logs/provider-base.js +113 -0
  350. package/dist/llm/client.js +182 -110
  351. package/dist/llm/embedders/deterministic.js +2 -2
  352. package/dist/llm/embedders/remote.js +21 -9
  353. package/dist/llm/feature-gate.js +17 -57
  354. package/dist/llm/graph-extract.js +12 -13
  355. package/dist/llm/index-passes.js +8 -42
  356. package/dist/llm/memory-infer.js +144 -1
  357. package/dist/llm/metadata-enhance.js +45 -30
  358. package/dist/llm/structured-call.js +16 -8
  359. package/dist/llm/usage-persist.js +30 -5
  360. package/dist/llm/usage-telemetry.js +59 -6
  361. package/dist/output/cli-hints.js +1 -2
  362. package/dist/output/command-registry.js +27 -0
  363. package/dist/output/context.js +22 -7
  364. package/dist/output/format-exempt.js +80 -0
  365. package/dist/output/generic-render.js +251 -0
  366. package/dist/output/html-render.js +11 -16
  367. package/dist/output/render-registry.js +57 -0
  368. package/dist/output/renderers.js +14 -279
  369. package/dist/output/shapes/curate.js +10 -1
  370. package/dist/output/shapes/events.js +12 -7
  371. package/dist/output/shapes/helpers.js +58 -84
  372. package/dist/output/shapes/passthrough.js +11 -39
  373. package/dist/output/shapes/proposal/producer.js +15 -7
  374. package/dist/output/shapes/registry.js +12 -6
  375. package/dist/output/shapes.js +0 -9
  376. package/dist/output/text/{init.js → bundle-create.js} +3 -1
  377. package/dist/output/text/bundle-show.js +7 -0
  378. package/dist/output/text/command-format.js +562 -0
  379. package/dist/output/text/env.js +1 -3
  380. package/dist/output/text/events.js +8 -7
  381. package/dist/output/text/helpers.js +15 -1164
  382. package/dist/output/text/proposal/producer.js +4 -2
  383. package/dist/output/text/proposal-format.js +202 -0
  384. package/dist/output/text/registry-commands.js +1 -2
  385. package/dist/output/text/registry.js +12 -6
  386. package/dist/output/text/show-directives.js +117 -0
  387. package/dist/output/text/show-format.js +103 -0
  388. package/dist/output/text/sync.js +5 -0
  389. package/dist/output/text/workflow-format.js +332 -0
  390. package/dist/output/text/workflow.js +3 -2
  391. package/dist/output/text.js +10 -19
  392. package/dist/registry/factory.js +4 -6
  393. package/dist/registry/origin-resolve.js +16 -27
  394. package/dist/registry/providers/skills-sh.js +3 -3
  395. package/dist/registry/providers/static-index.js +15 -25
  396. package/dist/registry/resolve.js +43 -94
  397. package/dist/registry/semver.js +43 -0
  398. package/dist/runtime.js +81 -12
  399. package/dist/scripts/akm-migrate.js +35529 -0
  400. package/dist/setup/detect.js +5 -7
  401. package/dist/setup/detected-engines.js +136 -0
  402. package/dist/setup/engine-config.js +100 -0
  403. package/dist/setup/registry-stash-loader.js +3 -3
  404. package/dist/setup/semantic-assets.js +12 -9
  405. package/dist/setup/setup.js +444 -208
  406. package/dist/setup/steps/connection-shared.js +120 -0
  407. package/dist/setup/steps/connection.js +108 -305
  408. package/dist/setup/steps/platforms.js +13 -12
  409. package/dist/setup/steps/semantic.js +15 -3
  410. package/dist/setup/steps/sources.js +21 -15
  411. package/dist/setup/steps/stashdir.js +6 -4
  412. package/dist/setup/steps/tasks.js +236 -119
  413. package/dist/setup/steps.js +3 -2
  414. package/dist/sources/freshness.js +39 -0
  415. package/dist/sources/provider-factory.js +11 -17
  416. package/dist/sources/providers/filesystem.js +2 -3
  417. package/dist/sources/providers/git-install.js +278 -34
  418. package/dist/sources/providers/git-provider.js +54 -56
  419. package/dist/sources/providers/git-stash.js +420 -91
  420. package/dist/sources/providers/git.js +2 -2
  421. package/dist/sources/providers/npm.js +16 -19
  422. package/dist/sources/providers/provider-utils.js +47 -22
  423. package/dist/sources/providers/sync-from-ref.js +3 -9
  424. package/dist/sources/providers/website.js +2 -2
  425. package/dist/sources/resolve.js +11 -10
  426. package/dist/sources/snapshot-fetchers/types.js +4 -0
  427. package/dist/sources/{website-ingest.js → snapshot-fetchers/website-ingest.js} +110 -41
  428. package/dist/storage/database.js +60 -4
  429. package/dist/storage/engines/sqlite-migrations.js +156 -5
  430. package/dist/storage/locations.js +1 -2
  431. package/dist/storage/repositories/canaries-repository.js +1 -1
  432. package/dist/storage/repositories/events-repository.js +51 -11
  433. package/dist/storage/repositories/improve-runs-repository.js +6 -32
  434. package/dist/storage/repositories/index-connection.js +79 -0
  435. package/dist/storage/repositories/index-db.js +4 -3
  436. package/dist/storage/repositories/index-entries-repository.js +863 -0
  437. package/dist/{indexer/db/entry-mapper.js → storage/repositories/index-entry-mapper.js} +19 -2
  438. package/dist/storage/repositories/index-entry-types.js +4 -0
  439. package/dist/storage/repositories/index-fts-repository.js +167 -0
  440. package/dist/storage/repositories/index-llm-cache-repository.js +108 -0
  441. package/dist/storage/repositories/index-meta-repository.js +49 -0
  442. package/dist/{indexer/db/schema.js → storage/repositories/index-schema.js} +226 -100
  443. package/dist/storage/repositories/index-sql.js +12 -0
  444. package/dist/storage/repositories/index-utility-repository.js +356 -0
  445. package/dist/storage/repositories/index-vec-repository.js +250 -0
  446. package/dist/storage/repositories/outcome-repository.js +119 -0
  447. package/dist/storage/repositories/proposals-repository.js +317 -75
  448. package/dist/storage/repositories/registry-cache.js +1 -1
  449. package/dist/storage/repositories/salience-repository.js +172 -0
  450. package/dist/storage/repositories/task-history-repository.js +110 -3
  451. package/dist/storage/repositories/workflow-runs-repository.js +240 -19
  452. package/dist/tasks/backends/cron.js +169 -46
  453. package/dist/tasks/backends/exec-utils.js +76 -3
  454. package/dist/tasks/backends/index.js +6 -9
  455. package/dist/tasks/backends/launchd.js +292 -55
  456. package/dist/tasks/backends/schtasks.js +557 -70
  457. package/dist/tasks/backends/types.js +4 -0
  458. package/dist/tasks/command-executable.js +93 -0
  459. package/dist/tasks/embedded.js +56 -38
  460. package/dist/tasks/parser.js +156 -64
  461. package/dist/tasks/resolve-akm-bin.js +144 -51
  462. package/dist/tasks/runner.js +377 -209
  463. package/dist/tasks/schedule.js +108 -19
  464. package/dist/tasks/scheduler-invocation.js +296 -0
  465. package/dist/tasks/schema.js +1 -1
  466. package/dist/tasks/task-id.js +35 -0
  467. package/dist/tasks/validator.js +30 -16
  468. package/dist/text-import-hook.mjs +1 -1
  469. package/dist/workflows/authoring/authoring.js +104 -43
  470. package/dist/workflows/authoring/scope-key.js +1 -1
  471. package/dist/workflows/cli.js +0 -16
  472. package/dist/workflows/concurrency-policy.js +15 -0
  473. package/dist/workflows/exec/brief.js +450 -0
  474. package/dist/workflows/exec/frozen-judge.js +47 -0
  475. package/dist/workflows/exec/native-executor.js +1038 -0
  476. package/dist/workflows/exec/param-secrets.js +115 -0
  477. package/dist/workflows/exec/report.js +1460 -0
  478. package/dist/workflows/exec/run-workflow.js +602 -0
  479. package/dist/workflows/exec/scheduler.js +71 -0
  480. package/dist/workflows/exec/step-work.js +1190 -0
  481. package/dist/workflows/exec/unit-writer.js +23 -0
  482. package/dist/workflows/exec/workflow-engine-gate.js +67 -0
  483. package/dist/workflows/exec/worktree.js +171 -0
  484. package/dist/workflows/ir/compile.js +246 -0
  485. package/dist/workflows/ir/freeze.js +233 -0
  486. package/dist/workflows/ir/params.js +54 -0
  487. package/dist/workflows/ir/plan-hash.js +68 -0
  488. package/dist/workflows/ir/schema.js +540 -0
  489. package/dist/workflows/parser.js +878 -304
  490. package/dist/workflows/program/expressions.js +181 -0
  491. package/dist/workflows/program/schema.js +51 -0
  492. package/dist/workflows/renderer.js +100 -45
  493. package/dist/workflows/resource-limits.js +22 -0
  494. package/dist/workflows/runtime/agent-identity.js +59 -14
  495. package/dist/workflows/runtime/checkin.js +1 -1
  496. package/dist/workflows/runtime/plan-classifier.js +131 -0
  497. package/dist/workflows/runtime/runs.js +376 -119
  498. package/dist/workflows/runtime/unit-checkin.js +45 -0
  499. package/dist/workflows/runtime/unit-phases.js +20 -0
  500. package/dist/workflows/runtime/workflow-asset-loader.js +241 -40
  501. package/dist/workflows/schema.js +1 -11
  502. package/dist/workflows/validate-summary.js +2 -3
  503. package/dist/workflows/validator.js +52 -30
  504. package/docs/README.md +42 -78
  505. package/docs/migration/README.md +8 -0
  506. package/docs/migration/release-notes/0.6.0.md +1 -1
  507. package/docs/migration/release-notes/0.7.0.md +9 -8
  508. package/docs/migration/release-notes/0.9.0.md +158 -14
  509. package/docs/migration/v0.7-to-v0.8.md +46 -47
  510. package/docs/migration/v0.8-to-v0.9.md +844 -0
  511. package/docs/reference/README.md +12 -0
  512. package/docs/reference/data-and-telemetry.md +333 -0
  513. package/package.json +21 -17
  514. package/schemas/akm-asset-envelope.json +93 -0
  515. package/schemas/akm-config.json +4636 -0
  516. package/schemas/akm-task.json +87 -0
  517. package/schemas/akm-workflow.json +373 -0
  518. package/dist/akm-migrate-storage +0 -38
  519. package/dist/assets/help/help-accept.md +0 -12
  520. package/dist/assets/help/help-improve.md +0 -84
  521. package/dist/assets/help/help-proposals.md +0 -17
  522. package/dist/assets/help/help-propose.md +0 -17
  523. package/dist/assets/help/help-reject.md +0 -11
  524. package/dist/assets/profiles/frequent.json +0 -13
  525. package/dist/assets/profiles/recombine-only.json +0 -21
  526. package/dist/assets/profiles/reflect-distill.json +0 -30
  527. package/dist/assets/profiles/synthesize.json +0 -15
  528. package/dist/assets/prompts/procedural-system.md +0 -44
  529. package/dist/assets/prompts/recombine-system.md +0 -40
  530. package/dist/assets/prompts/staleness-detect-system.md +0 -6
  531. package/dist/assets/tasks/core/backup.yml +0 -4
  532. package/dist/assets/tasks/graph-refresh-weekly.yml +0 -10
  533. package/dist/assets/templates/html/default.html +0 -78
  534. package/dist/assets/templates/html/vendor/echarts.min.js +0 -45
  535. package/dist/assets/wiki/index-template.md +0 -12
  536. package/dist/assets/wiki/ingest-workflow-template.md +0 -83
  537. package/dist/assets/wiki/log-template.md +0 -8
  538. package/dist/assets/wiki/schema-template.md +0 -61
  539. package/dist/cli/config-migrate.js +0 -150
  540. package/dist/cli/config-validate.js +0 -39
  541. package/dist/commands/graph/graph-cli.js +0 -124
  542. package/dist/commands/graph/graph.js +0 -487
  543. package/dist/commands/improve/calibration.js +0 -161
  544. package/dist/commands/improve/dedup.js +0 -482
  545. package/dist/commands/improve/extract-watch.js +0 -140
  546. package/dist/commands/improve/hot-probation.js +0 -45
  547. package/dist/commands/improve/improve-auto-accept.js +0 -276
  548. package/dist/commands/improve/improve-profiles.js +0 -168
  549. package/dist/commands/improve/procedural.js +0 -398
  550. package/dist/commands/improve/recombine.js +0 -818
  551. package/dist/commands/improve/schema-similarity-gate.js +0 -168
  552. package/dist/commands/lint/agent-linter.js +0 -44
  553. package/dist/commands/lint/command-linter.js +0 -44
  554. package/dist/commands/lint/default-linter.js +0 -16
  555. package/dist/commands/lint/fact-linter.js +0 -39
  556. package/dist/commands/lint/knowledge-linter.js +0 -16
  557. package/dist/commands/lint/memory-linter.js +0 -61
  558. package/dist/commands/lint/registry.js +0 -41
  559. package/dist/commands/lint/skill-linter.js +0 -45
  560. package/dist/commands/lint/task-linter.js +0 -50
  561. package/dist/commands/lint/workflow-linter.js +0 -81
  562. package/dist/commands/proposal/legacy-import.js +0 -115
  563. package/dist/commands/sources/history.js +0 -196
  564. package/dist/commands/tasks/default-tasks.js +0 -186
  565. package/dist/commands/wiki-cli.js +0 -292
  566. package/dist/core/asset/asset-registry.js +0 -76
  567. package/dist/core/asset/asset-spec.js +0 -259
  568. package/dist/core/config/config-migration.js +0 -602
  569. package/dist/core/deep-merge.js +0 -38
  570. package/dist/core/eval/rank-metrics.js +0 -113
  571. package/dist/core/ripgrep/install.js +0 -163
  572. package/dist/core/ripgrep/resolve.js +0 -81
  573. package/dist/indexer/db/db.js +0 -1413
  574. package/dist/indexer/manifest.js +0 -170
  575. package/dist/indexer/passes/metadata-contributors.js +0 -31
  576. package/dist/indexer/usage/unmigrated-vaults-guard.js +0 -94
  577. package/dist/integrations/harnesses/opencode-sdk/index.js +0 -49
  578. package/dist/llm/call-ai.js +0 -62
  579. package/dist/llm/memory-infer-impl.js +0 -138
  580. package/dist/output/shapes/distill.js +0 -14
  581. package/dist/output/shapes/history.js +0 -11
  582. package/dist/output/text/distill.js +0 -6
  583. package/dist/output/text/enable-disable.js +0 -8
  584. package/dist/output/text/history.js +0 -6
  585. package/dist/output/text/wiki.js +0 -16
  586. package/dist/registry/build-index.js +0 -386
  587. package/dist/scripts/migrate-storage.js +0 -19108
  588. package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +0 -9411
  589. package/dist/scripts/migrations/v16-to-v17.js +0 -141
  590. package/dist/setup/legacy-config.js +0 -106
  591. package/dist/storage/repositories/consolidation-repository.js +0 -38
  592. package/dist/storage/repositories/recombine-repository.js +0 -213
  593. package/dist/wiki/wiki-templates.js +0 -15
  594. package/dist/wiki/wiki.js +0 -1012
  595. package/dist/workflows/db.js +0 -215
  596. package/docs/data-and-telemetry.md +0 -226
  597. /package/dist/sources/{wiki-fetchers → snapshot-fetchers}/registry.js +0 -0
  598. /package/dist/sources/{wiki-fetchers → snapshot-fetchers}/youtube.js +0 -0
@@ -0,0 +1,602 @@
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
+ /**
5
+ * Engine-driven workflow execution — `akm workflow run` (orchestration plan
6
+ * P1, owner decision 5). akm itself walks the plan and dispatches units; the
7
+ * existing `next`/`complete` loop remains for manual/agent-driven advancement
8
+ * of the same runs.
9
+ *
10
+ * Invariant (plan §*Never bypass the gate spine*): every step advances
11
+ * through `completeWorkflowStep`, never by writing step rows directly, so the
12
+ * summary-validation gate and run-state derivation stay authoritative. A gate
13
+ * rejection (SummaryValidationFailure) STOPS the engine and surfaces the
14
+ * corrective feedback — a gate is a gate, even for the engine.
15
+ *
16
+ * Artifact-judging gates (redesign addendum, R2): when a step declares
17
+ * completion criteria, the engine hands the gate a summary BUILT FROM the
18
+ * step's promoted artifact (canonical JSON, clipped, prefixed with a one-line
19
+ * unit count — `buildArtifactSummary`) instead of the machine-prose execution
20
+ * summary, so the judge evaluates real results. Each engine-driven judge call
21
+ * is journaled as a unit row (`node_id "<stepId>.gate"`, `unit_id
22
+ * "<stepId>.gate:l<loop>"`, runner "llm", result_json = the verdict) through
23
+ * the writer queue — it is an LLM call like any other. Human approvals are
24
+ * never cached: a blocked gate stays blocked.
25
+ *
26
+ * Bounded gate loops (`gate.max_loops`, addendum R2): a rejection on a step
27
+ * with maxLoops > 1 re-executes the step subgraph with the judge's feedback +
28
+ * missing[] threaded into every unit prompt (`gateFeedback` on
29
+ * StepExecutionContext) — the feedback changes each unit's input hash, so the
30
+ * loop re-dispatches naturally instead of reusing the rejected rows. After
31
+ * maxLoops rejections the engine stops with the gate feedback, exactly like
32
+ * the one-shot case. A typed-artifact schema mismatch feeds the same loop
33
+ * (the validation errors are the feedback; no judge ran, so no gate unit is
34
+ * journaled for that attempt) — only the FINAL loop's mismatch fails the run.
35
+ *
36
+ * Frozen plan (redesign addendum, R1): the plan graph is read from the run
37
+ * row (`plan_json`, persisted by `startWorkflowRun` under migration 006) with
38
+ * a `plan_hash` integrity check — the workflow asset file is NEVER re-read
39
+ * for an in-flight run, so a mid-run asset edit cannot change behavior.
40
+ * Durable-row resume: re-invoking a partially-executed run re-dispatches only
41
+ * work that never completed.
42
+ *
43
+ * Run lease (redesign addendum, R2): exactly one engine invocation drives a
44
+ * run at a time. The lease (random holder id + 90s expiry on the run row) is
45
+ * acquired before any dispatch, renewed between steps, and released in a
46
+ * `finally` unless a failed run retains it as forensic state; a second
47
+ * `workflow run` on a live-leased run refuses up front,
48
+ * and an expired lease is claimable (crash recovery). While the lease is
49
+ * live, manual `workflow complete` is refused too — the engine owns the
50
+ * spine while driving (enforced inside `completeWorkflowStep`).
51
+ *
52
+ * Process-lifecycle contract (owner finding 4 — no leaked handles): the SDK
53
+ * dispatch path caches `opencode serve` CHILD PROCESSES in a per-env registry
54
+ * for reuse across units. Each live child is an OS handle that keeps Bun's
55
+ * event loop open; the registry's own teardown is wired only to
56
+ * `process.once('exit')`, which never fires while a child holds the loop open.
57
+ * That deadlock hangs a one-shot CLI (`akm workflow run` has no `process.exit`
58
+ * on success — it relies on the loop draining). The engine therefore DRAINS
59
+ * the dispatch registry ({@link disposeDispatchResources}) in its run `finally`,
60
+ * on EVERY exit path, so the process exits cleanly the moment the run resolves.
61
+ * The drain is synchronous, idempotent, and a no-op when no SDK server started.
62
+ */
63
+ import { randomUUID } from "node:crypto";
64
+ import { UsageError } from "../../core/errors.js";
65
+ import { withMaintenanceStartBarrierAsync } from "../../core/maintenance-barrier.js";
66
+ import { disposeDispatchResources } from "../../integrations/agent/runner-dispatch.js";
67
+ import { withWorkflowRunsRepo } from "../../storage/repositories/workflow-runs-repository.js";
68
+ import { assertRunParamsSatisfyPlan } from "../ir/params.js";
69
+ import { computePlanHash } from "../ir/plan-hash.js";
70
+ import { decodeWorkflowPlanV3 } from "../ir/schema.js";
71
+ import { requireExecutableWorkflowPlan } from "../runtime/plan-classifier.js";
72
+ import { completeWorkflowStep, getNextWorkflowStep } from "../runtime/runs.js";
73
+ import { GATE_EVALUATION_PHASE } from "../runtime/unit-phases.js";
74
+ import { frozenSummaryJudge } from "./frozen-judge.js";
75
+ import { executeStepPlan } from "./native-executor.js";
76
+ // Shared step semantics — route evaluation + cascaded-skip bookkeeping,
77
+ // gate-evaluation journaling, and the whole step-completion path
78
+ // (`finalizeExecutedStep`) live in step-work.ts so the engine loop and the R3
79
+ // brief/report driver protocol share ONE implementation (no drift).
80
+ import { activeGateLoop, cascadeSkippedRouter, finalizeExecutedStep, recoverGateFeedback, seedJournaledRouteDecisions, } from "./step-work.js";
81
+ export async function runWorkflowSteps(options) {
82
+ const next = await getNextWorkflowStep(options.target, options.params);
83
+ // Version/canonical/hash validation precedes every executable mutation,
84
+ // including lease acquisition. Historical rows remain inspectable/abandonable.
85
+ if (!next.done && !options.loadPlan) {
86
+ await withWorkflowRunsRepo((repo) => {
87
+ const row = repo.getRunById(next.run.id);
88
+ if (!row)
89
+ throw new UsageError(`Workflow run ${next.run.id} was not found.`);
90
+ requireExecutableWorkflowPlan(row);
91
+ });
92
+ }
93
+ // Refuse non-active runs BEFORE any dispatch — completeWorkflowStep would
94
+ // reject the completion anyway, but only after the units already ran (and
95
+ // cost money). Mirror its preflight up front.
96
+ if (!next.done && next.run.status !== "active") {
97
+ throw new UsageError(`Workflow run ${next.run.id} is ${next.run.status} and cannot be executed. ` +
98
+ `Use \`akm workflow resume ${next.run.id}\` to reopen it first.`);
99
+ }
100
+ // Run lease (R2 single-driver enforcement): claim the run BEFORE any
101
+ // dispatch — a second `akm workflow run` on a live-leased run refuses up
102
+ // front instead of racing the first engine's spine. An expired lease is
103
+ // claimable (crash recovery). Released in the finally below; renewed
104
+ // between steps inside the loop. A done run takes no lease: nothing will
105
+ // dispatch, and the status re-read below must stay a pure no-op.
106
+ const runId = next.run.id;
107
+ const leaseHolder = randomUUID();
108
+ const leased = !next.done;
109
+ if (leased) {
110
+ await acquireRunLease(runId, leaseHolder);
111
+ }
112
+ // Lease heartbeat (P1 fix): the lease TTL is renewed BETWEEN steps, but a
113
+ // single unit's dispatch can outlive the TTL (the default unit timeout is 10
114
+ // minutes, > the 90s lease). An unheartbeated lease would silently expire
115
+ // mid-dispatch, letting a second `akm workflow run` claim the run and
116
+ // re-dispatch the same units — the two engines clobber each other's journal
117
+ // rows and double-run side effects. A timer INSIDE this invocation renews the
118
+ // lease while dispatch is in flight; it is cleared in the `finally`, so it
119
+ // dies with the process — exactly when the lease SHOULD become claimable
120
+ // after TTL. A renewal that fails (the lease was genuinely stolen after an
121
+ // expiry, e.g. the process was suspended) aborts dispatch and fails the run
122
+ // loudly rather than keep double-driving.
123
+ const heartbeat = leased
124
+ ? new LeaseHeartbeat(runId, leaseHolder, options.heartbeatScheduler, options.signal)
125
+ : undefined;
126
+ heartbeat?.start();
127
+ try {
128
+ return await driveRun(options, next, leaseHolder, heartbeat);
129
+ }
130
+ finally {
131
+ heartbeat?.stop();
132
+ try {
133
+ if (leased) {
134
+ await withWorkflowRunsRepo((repo) => {
135
+ repo.releaseEngineLease(runId, leaseHolder);
136
+ });
137
+ }
138
+ }
139
+ finally {
140
+ // Process-lifecycle drain (owner finding 4): release any cached SDK server
141
+ // child processes so a one-shot CLI invocation exits cleanly instead of
142
+ // hanging on the leaked handle. Runs even if lease release itself fails;
143
+ // a teardown-time repository error must not skip dispatch cleanup.
144
+ try {
145
+ await (options.disposeDispatchResources ?? disposeDispatchResources)();
146
+ }
147
+ catch {
148
+ /* disposal is best-effort; never let cleanup mask the run outcome */
149
+ }
150
+ }
151
+ }
152
+ }
153
+ /** Lease lifetime: long enough to survive slow steps between renewals, short
154
+ * enough that a crashed engine frees the run quickly. Renewed per step. */
155
+ const RUN_LEASE_TTL_MS = 90_000;
156
+ function leaseExpiry() {
157
+ return new Date(Date.now() + RUN_LEASE_TTL_MS).toISOString();
158
+ }
159
+ /**
160
+ * Atomically claim the run lease or refuse with a UsageError naming the
161
+ * current holder + expiry. The single-UPDATE claim in the repository is the
162
+ * arbiter — two racing invocations cannot both win.
163
+ */
164
+ async function acquireRunLease(runId, holder) {
165
+ await withMaintenanceStartBarrierAsync(() => withWorkflowRunsRepo((repo) => {
166
+ if (repo.acquireEngineLease(runId, holder, leaseExpiry(), new Date().toISOString()))
167
+ return;
168
+ const row = repo.getRunById(runId);
169
+ throw new UsageError(`Workflow run ${runId} is already being driven by engine ${row?.engine_lease_holder ?? "(unknown)"} ` +
170
+ `(run lease expires ${row?.engine_lease_until ?? "(unknown)"}). A second \`akm workflow run\` would race it — ` +
171
+ `wait for that invocation to finish or for the lease to expire.`);
172
+ }));
173
+ }
174
+ /**
175
+ * Renew the lease between steps. Losing the lease mid-run (it expired during
176
+ * a long step and another engine claimed it) is a hard stop: the new owner
177
+ * drives the spine now, and continuing would race it.
178
+ */
179
+ async function renewRunLease(runId, holder) {
180
+ await withWorkflowRunsRepo((repo) => {
181
+ if (repo.renewEngineLease(runId, holder, leaseExpiry()))
182
+ return;
183
+ const row = repo.getRunById(runId);
184
+ throw new UsageError(`Workflow run ${runId} lost its run lease (now held by ${row?.engine_lease_holder ?? "(nobody)"}). ` +
185
+ `Another engine invocation claimed the run after this one's lease expired — stopping to avoid racing it.`);
186
+ });
187
+ }
188
+ /** Renew mid-dispatch this often. Well under the TTL so a slow/skipped tick
189
+ * still leaves ample margin before the lease would expire. */
190
+ const HEARTBEAT_INTERVAL_MS = RUN_LEASE_TTL_MS / 3;
191
+ /** Real timer: an unref'd interval so a live heartbeat never keeps the process alive. */
192
+ function defaultHeartbeatScheduler(tick) {
193
+ const id = setInterval(() => void tick(), HEARTBEAT_INTERVAL_MS);
194
+ id.unref?.();
195
+ return () => clearInterval(id);
196
+ }
197
+ /**
198
+ * Keeps the run lease alive while a step dispatches (P1 fix — the between-step
199
+ * renewal cannot cover a unit that runs longer than the TTL). A timer inside
200
+ * the engine invocation renews the lease through the holder-guarded
201
+ * {@link renewEngineLease}; the heartbeat owns an {@link AbortController}
202
+ * (chained onto the caller's signal) that becomes the effective DISPATCH
203
+ * signal, so a lost lease aborts in-flight dispatch PROMPTLY. After the abort,
204
+ * {@link assertAlive} throws a loud UsageError, so the engine stops instead of
205
+ * continuing to drive a run another engine now owns. No background daemon: the
206
+ * timer is cleared in the caller's `finally` and dies with the process.
207
+ */
208
+ class LeaseHeartbeat {
209
+ runId;
210
+ holder;
211
+ controller = new AbortController();
212
+ detachUpstream;
213
+ schedule;
214
+ cancel;
215
+ renewing = false;
216
+ /** Set once a renewal failed — the lease was stolen after a genuine expiry. */
217
+ lost = false;
218
+ /** The holder that stole the lease, captured for the loud error. */
219
+ stolenBy = null;
220
+ constructor(runId, holder, scheduler, upstream) {
221
+ this.runId = runId;
222
+ this.holder = holder;
223
+ this.schedule = scheduler ?? defaultHeartbeatScheduler;
224
+ // A caller abort (Ctrl-C, budget) must abort dispatch too; chain it into
225
+ // the effective signal. Distinct from a lost lease: a caller abort does
226
+ // NOT set `lost`, so `assertAlive` stays quiet and the existing graceful
227
+ // break on `options.signal` handles it.
228
+ if (upstream) {
229
+ if (upstream.aborted) {
230
+ this.controller.abort();
231
+ }
232
+ else {
233
+ const onAbort = () => this.controller.abort();
234
+ upstream.addEventListener("abort", onAbort, { once: true });
235
+ this.detachUpstream = () => upstream.removeEventListener("abort", onAbort);
236
+ }
237
+ }
238
+ }
239
+ /** The effective dispatch signal: aborts on a lost lease OR a caller abort. */
240
+ get signal() {
241
+ return this.controller.signal;
242
+ }
243
+ start() {
244
+ this.cancel ??= this.schedule(() => this.tick());
245
+ }
246
+ /** One renewal attempt. A failure marks the lease lost and aborts dispatch. */
247
+ async tick() {
248
+ if (this.lost || this.renewing || this.controller.signal.aborted)
249
+ return;
250
+ this.renewing = true;
251
+ try {
252
+ const renewed = await withWorkflowRunsRepo((repo) => repo.renewEngineLease(this.runId, this.holder, leaseExpiry()));
253
+ if (!renewed) {
254
+ this.stolenBy = await withWorkflowRunsRepo((repo) => repo.getRunById(this.runId)?.engine_lease_holder ?? null);
255
+ this.loseLease();
256
+ }
257
+ }
258
+ catch {
259
+ // A renewal that THREW (a DB error / connection failure, or the follow-up
260
+ // getRunById itself throwing) is treated exactly like a stolen lease: we
261
+ // can no longer PROVE we still hold it, so abort in-flight dispatch and let
262
+ // `assertAlive` stop the engine loudly. Swallowing the error here is what
263
+ // keeps the fire-and-forget `void tick()` in the default scheduler from
264
+ // leaking an unhandled promise rejection.
265
+ this.loseLease();
266
+ }
267
+ finally {
268
+ this.renewing = false;
269
+ }
270
+ }
271
+ /** Mark the lease lost, stop the timer, and abort in-flight dispatch — the new
272
+ * owner drives the spine now (or, on a renewal error, we can no longer prove we
273
+ * do). Idempotent: repeated calls are harmless. */
274
+ loseLease() {
275
+ this.lost = true;
276
+ this.stop();
277
+ this.controller.abort();
278
+ }
279
+ /**
280
+ * Throw loudly if a heartbeat renewal failed. Called at dispatch boundaries:
281
+ * a lost lease means another engine claimed the run mid-step, so continuing
282
+ * (completing steps, dispatching more units) would double-drive it.
283
+ */
284
+ assertAlive() {
285
+ if (!this.lost)
286
+ return;
287
+ throw new UsageError(`Workflow run ${this.runId} lost its run lease mid-dispatch (heartbeat renewal failed; lease now held by ` +
288
+ `${this.stolenBy ?? "(nobody)"}). Another engine invocation claimed the run after this one's lease expired — ` +
289
+ `aborting to avoid double-driving it.`);
290
+ }
291
+ stop() {
292
+ this.cancel?.();
293
+ this.cancel = undefined;
294
+ this.detachUpstream?.();
295
+ }
296
+ }
297
+ /** The engine loop proper — runs under the lease held by `runWorkflowSteps`. */
298
+ async function driveRun(options, initial, leaseHolder, heartbeat) {
299
+ let next = initial;
300
+ // A terminal (completed) run is a PURE no-op. `runWorkflowSteps` already
301
+ // skipped lease acquisition for a done run (`leased = !next.done`), and this
302
+ // path must ALSO refuse to read the journal or load/integrity-check the
303
+ // frozen plan: a run that finished cleanly, then had its `plan_json` corrupted
304
+ // or tampered afterwards, must still report `done` here rather than throwing a
305
+ // frozen-plan integrity error (loadFrozenPlan would). Nothing will dispatch
306
+ // and the engine_lease_* columns stay exactly as they were, so return the
307
+ // fresh run state immediately.
308
+ if (initial.done) {
309
+ const doneState = await getNextWorkflowStep(initial.run.id);
310
+ return {
311
+ run: doneState.run,
312
+ executed: [],
313
+ ...(doneState.run.status === "completed" ? { done: true } : {}),
314
+ };
315
+ }
316
+ // The effective dispatch signal: the heartbeat's controller (a lost lease or
317
+ // a caller abort aborts it) while leased, else the raw caller signal.
318
+ const dispatchSignal = heartbeat?.signal ?? options.signal;
319
+ const executed = [];
320
+ let gateRejection;
321
+ const maxSteps = options.maxSteps ?? Number.POSITIVE_INFINITY;
322
+ // Seed the lifetime unit cap AND the budget ceilings from the journal so
323
+ // both are truly per-RUN: a resumed or re-invoked run must not restart the
324
+ // runaway backstop — or a declared `budget` — at zero. Journal rows = past
325
+ // dispatch ATTEMPTS (counted against `budget.max_units`); their summed
326
+ // `tokens` column is the run's spend so far (counted against
327
+ // `budget.max_tokens`). The executor consumes both only on new dispatches
328
+ // (durable-row reuses are free), so a large partially-completed fan-out
329
+ // stays resumable.
330
+ //
331
+ // Gate-evaluation rows (`phase = "gate"`, journaled by the completion-gate
332
+ // judge below) are EXCLUDED from the seed: the live path never consumes
333
+ // DispatchBudget for a judge call, so counting its journal row on resume
334
+ // would make an interrupted run hit `max_units` (and the lifetime cap)
335
+ // earlier than the identical uninterrupted run — a spurious hard failure
336
+ // that `on_error` cannot soften. The seed must reproduce exactly what live
337
+ // accounting would have accumulated.
338
+ //
339
+ // The seed sums each dispatch row's `attempts` (migration 008), NOT the row
340
+ // COUNT: a crash between a unit's dispatch and its finish leaves a `running`
341
+ // row that resume re-dispatches under the SAME content-derived unit_id, and
342
+ // `insertUnit` REPLACES that one row while bumping `attempts`. Counting rows
343
+ // would erase every prior crash-retried dispatch from budget/lifetime
344
+ // accounting, letting the run spend past its declared ceiling; summing
345
+ // `attempts` charges each dispatch exactly once.
346
+ const journaledUnits = await withWorkflowRunsRepo((repo) => repo.getUnitsForRun(next.run.id));
347
+ const journaledDispatches = journaledUnits.filter((row) => row.phase !== GATE_EVALUATION_PHASE);
348
+ let unitsDispatched = journaledDispatches.reduce((sum, row) => sum + row.attempts, 0);
349
+ let tokensUsed = journaledDispatches.reduce((sum, row) => sum + (row.tokens ?? 0), 0);
350
+ // The decoded/hash-verified row plan is the sole execution authority. The
351
+ // loader seam may assert an expected plan in tests, but can never replace it.
352
+ const plan = await loadFrozenPlan(next.run.id);
353
+ if (options.loadPlan) {
354
+ const expected = decodeWorkflowPlanV3(await options.loadPlan(next.run.workflowRef));
355
+ if (computePlanHash(expected) !== computePlanHash(plan))
356
+ throw new UsageError(`Injected workflow plan for run ${next.run.id} differs from its frozen plan.`);
357
+ }
358
+ // Reviewer #12: the journaled params row must still satisfy the frozen param
359
+ // schemas before the engine resolves any unit prompt from it. Applied on ALL
360
+ // THREE driver surfaces (engine here, brief, report) so schema-violating
361
+ // params — post-start corruption — fail loudly and IDENTICALLY, preserving
362
+ // cross-surface parity (start already validated the params it stored).
363
+ assertRunParamsSatisfyPlan(next.run.id, plan, next.run.params ?? {});
364
+ // Route bookkeeping: targets a completed router did NOT select are skipped
365
+ // when the spine reaches them; a target ANY router selected is protected
366
+ // (two routers may share a target).
367
+ const routeSelected = new Set();
368
+ const routeUnselected = new Map();
369
+ // Resume contract: route decisions are journaled in the route step's
370
+ // evidence (`evidence.route.selected`) and must be REPLAYED into the
371
+ // bookkeeping before the spine advances — a re-invoked run (crash, Ctrl-C,
372
+ // maxSteps, gate rejection after the route completed) would otherwise reach
373
+ // the unselected targets with empty in-memory state and execute the wrong
374
+ // branch. Decisions stay pure functions of (frozen plan, params, journaled
375
+ // results) — the addendum determinism bar. A done run skips the seeding:
376
+ // nothing will dispatch, so an unrecoverable prior decision must not
377
+ // block the no-op status return below.
378
+ if (!next.done) {
379
+ seedJournaledRouteDecisions(plan, next, routeSelected, routeUnselected);
380
+ }
381
+ while (!next.done && next.step && next.run.status === "active" && executed.length < maxSteps) {
382
+ // A LOST lease (the heartbeat's renewal failed mid-step) is a loud stop —
383
+ // another engine owns the spine now. A caller abort (options.signal) is a
384
+ // graceful break, distinct from a lost lease.
385
+ heartbeat?.assertAlive();
386
+ if (options.signal?.aborted)
387
+ break;
388
+ // Renew the run lease between steps (a fresh 90s window per iteration).
389
+ // Losing it (expired mid-step + claimed by another engine) throws — the
390
+ // new owner drives the spine now.
391
+ await renewRunLease(next.run.id, leaseHolder);
392
+ const step = next.step;
393
+ const stepPlan = plan.steps.find((s) => s.stepId === step.id);
394
+ if (!stepPlan) {
395
+ throw new UsageError(`Step "${step.id}" of run ${next.run.id} is not present in the current workflow asset (${next.run.workflowRef}). ` +
396
+ `The source file changed since the run started — advance this step manually with \`akm workflow complete\`.`);
397
+ }
398
+ // A branch target no completed router selected → auto-skip, no dispatch.
399
+ const skipInfo = routeUnselected.get(step.id);
400
+ if (skipInfo && !routeSelected.has(step.id)) {
401
+ // Cascade (peer review R1): a skipped step that is ITSELF a router
402
+ // never evaluates its route, so none of its declared targets were
403
+ // selected — mark them all skip-on-reach too (a target another
404
+ // completed router selects stays protected via routeSelected). Without
405
+ // this, every branch of the skipped router would run unconditionally.
406
+ if (stepPlan.route) {
407
+ cascadeSkippedRouter(stepPlan.route, step.id, routeUnselected);
408
+ }
409
+ const notes = skipInfo.selected === null
410
+ ? `Skipped by route: step "${skipInfo.router}" was itself skipped, so none of its branch targets run.`
411
+ : `Skipped by route: step "${skipInfo.router}" selected "${skipInfo.selected}".`;
412
+ executed.push({ stepId: step.id, ok: true, unitCount: 0, failedUnits: 0, summary: notes });
413
+ await completeWorkflowStep({ runId: next.run.id, stepId: step.id, status: "skipped", notes, leaseHolder });
414
+ next = await getNextWorkflowStep(next.run.id);
415
+ continue;
416
+ }
417
+ // `dependsOn` edges (no frontend emits them today,
418
+ // but a frozen plan may carry them) are a declared ordering contract:
419
+ // every dependency must already be resolved before this step dispatches.
420
+ // Execution is sequential (spine order), so a violation means the plan
421
+ // ordered steps inconsistently with its declared edges — fail fast,
422
+ // before spending.
423
+ for (const dep of stepPlan.dependsOn ?? []) {
424
+ const depState = next.workflow.steps.find((s) => s.id === dep);
425
+ if (!depState || (depState.status !== "completed" && depState.status !== "skipped")) {
426
+ throw new UsageError(`Step "${step.id}" depends on step "${dep}", which is ${depState?.status ?? "missing"}. ` +
427
+ `Reorder the workflow so dependencies come first (execution is sequential in step order).`);
428
+ }
429
+ }
430
+ const evidence = {};
431
+ for (const s of next.workflow.steps)
432
+ evidence[s.id] = s.evidence;
433
+ // Bounded gate loop (addendum R2, `gate.max_loops`): loop 1 is the normal
434
+ // execution; a gate rejection with attempts left re-executes the subgraph
435
+ // with the judge's feedback threaded into unit prompts. `advanced` = the
436
+ // step completed and the spine may move on; `stopEngine` = failure or
437
+ // final rejection — this invocation is done.
438
+ const maxLoops = Math.max(1, stepPlan.gate.maxLoops ?? 1);
439
+ // Crash-resume gate state (Codex P1): SEED the starting gate loop from the
440
+ // journal exactly as the brief/report surfaces do — the SAME shared helpers,
441
+ // no fork. A run interrupted after a rejected gate was journaled
442
+ // (`<step>.gate:l<n>`, complete:false) must resume at loop n+1 with the
443
+ // stored corrective feedback threaded into the unit prompts; without this
444
+ // the engine restarts at loop 1, reuses the rejected loop-1 rows, overwrites
445
+ // `<step>.gate:l1`, and re-judges the stale artifact — breaking journaled
446
+ // replay and diverging from what brief computes for the same run. The rows
447
+ // are re-read here (NOT the once-at-start `journaledUnits` budget seed) so a
448
+ // step reached later within THIS same invocation still starts fresh at loop 1.
449
+ const stepJournal = await withWorkflowRunsRepo((repo) => repo.getUnitsForRun(next.run.id));
450
+ const startLoop = activeGateLoop(stepJournal, step.id);
451
+ const seededFeedback = recoverGateFeedback(stepJournal, step.id, startLoop);
452
+ // Resume AFTER the FINAL rejection (`startLoop` past the loop bound): the
453
+ // gate was already exhausted before the crash, so there is NO fresh loop to
454
+ // run — reproduce the documented gateRejection outcome from the stored
455
+ // final-loop feedback instead of re-dispatching a spurious extra loop. The
456
+ // l1..l<maxLoops> rows stay untouched and the step stays active, exactly as
457
+ // when the engine first exhausted the gate.
458
+ if (startLoop > maxLoops) {
459
+ gateRejection = {
460
+ stepId: step.id,
461
+ missing: seededFeedback?.missing ?? [],
462
+ feedback: seededFeedback?.feedback ?? "",
463
+ };
464
+ break;
465
+ }
466
+ let gateFeedback = seededFeedback;
467
+ let advanced = false;
468
+ let stopEngine = false;
469
+ for (let gateLoop = startLoop; gateLoop <= maxLoops; gateLoop++) {
470
+ // A loop re-execution dispatches a fresh round of units — renew the
471
+ // lease so a long evaluator-optimizer cycle cannot outlive the TTL.
472
+ if (gateLoop > 1)
473
+ await renewRunLease(next.run.id, leaseHolder);
474
+ // Route-only steps (YAML `route:` — no execution subgraph) dispatch no
475
+ // units; they only decide the spine's path below. Everything else
476
+ // executes its subgraph through the native executor.
477
+ const result = !stepPlan.root && stepPlan.route
478
+ ? {
479
+ ok: true,
480
+ units: [],
481
+ evidence: {},
482
+ summary: `Step "${step.id}" is a route step — no units dispatched.`,
483
+ unitsDispatched,
484
+ }
485
+ : await executeStepPlan(stepPlan, {
486
+ runId: next.run.id,
487
+ leaseHolder,
488
+ workflowRef: next.run.workflowRef,
489
+ params: next.run.params ?? {},
490
+ evidence,
491
+ unitsDispatched,
492
+ tokensUsed,
493
+ // Budget ceilings ride the FROZEN plan (addendum R2): a mid-run
494
+ // asset edit can never loosen or tighten a run's budget.
495
+ ...(plan.budget ? { budget: plan.budget } : {}),
496
+ ...(plan.execution ? { engines: plan.execution.engines } : {}),
497
+ gateLoop,
498
+ ...(gateFeedback ? { gateFeedback } : {}),
499
+ // The heartbeat's signal is the effective dispatch signal: a lost
500
+ // lease (or a caller abort) aborts in-flight units promptly.
501
+ ...(dispatchSignal ? { signal: dispatchSignal } : {}),
502
+ ...(options.dispatcher ? { dispatcher: options.dispatcher } : {}),
503
+ maxConcurrency: Math.min(options.maxConcurrency ?? Number.POSITIVE_INFINITY, plan.execution?.maxConcurrency ?? 1),
504
+ });
505
+ // If the heartbeat lost the lease WHILE this step dispatched, another
506
+ // engine now owns the run — stop loudly BEFORE finalizing the step
507
+ // (completeWorkflowStep would race the new owner's spine).
508
+ heartbeat?.assertAlive();
509
+ unitsDispatched = result.unitsDispatched;
510
+ if (result.tokensUsed !== undefined)
511
+ tokensUsed = result.tokensUsed;
512
+ executed.push({
513
+ stepId: step.id,
514
+ ok: result.ok,
515
+ unitCount: result.units.length,
516
+ failedUnits: result.units.filter((u) => !u.ok).length,
517
+ summary: result.summary,
518
+ });
519
+ // Route evaluation + artifact-judged completion gate + gate-row
520
+ // journaling + the bounded-loop rejection contract are the SHARED
521
+ // completion path (`finalizeExecutedStep`): the R3 report surface drives
522
+ // the identical sequence, so an engine-driven and a report-driven run of
523
+ // the same frozen plan promote the same artifact and advance (or reject)
524
+ // the spine identically. The engine owns only the loop control the result
525
+ // maps onto (retry re-executes; advanced moves on; failure/exhaustion
526
+ // stops this invocation).
527
+ const finalize = await finalizeExecutedStep({
528
+ runId: next.run.id,
529
+ workflowRef: next.run.workflowRef,
530
+ stepId: step.id,
531
+ stepPlan,
532
+ completionCriteria: stepPlan.gate.criteria,
533
+ gateLoop,
534
+ loopsRemaining: gateLoop < maxLoops,
535
+ result,
536
+ priorEvidence: evidence,
537
+ params: next.run.params ?? {},
538
+ routeSelected,
539
+ routeUnselected,
540
+ summaryJudge: options.summaryJudge === undefined ? frozenSummaryJudge(plan, stepPlan.gate.judge) : options.summaryJudge,
541
+ leaseHolder,
542
+ });
543
+ if (finalize.kind === "retry") {
544
+ // Re-execute the subgraph with the judge/validation feedback threaded
545
+ // into unit prompts — the changed prompt changes each unit's input
546
+ // hash, so the re-run dispatches fresh work instead of reusing rows.
547
+ gateFeedback = finalize.gateFeedback;
548
+ continue;
549
+ }
550
+ if (finalize.kind === "advanced") {
551
+ // A route-only step's summary IS its decision (finalize surfaces it).
552
+ if (finalize.summaryOverride !== undefined) {
553
+ executed[executed.length - 1] = { ...executed[executed.length - 1], summary: finalize.summaryOverride };
554
+ }
555
+ advanced = true;
556
+ break;
557
+ }
558
+ if (finalize.kind === "failed") {
559
+ // A route-failure was pushed as ok:true (the units succeeded); reflect
560
+ // the deterministic route failure in the executed report.
561
+ if (finalize.routeFailure) {
562
+ executed[executed.length - 1] = { ...executed[executed.length - 1], ok: false, summary: finalize.summary };
563
+ }
564
+ stopEngine = true;
565
+ break;
566
+ }
567
+ // gate-exhausted: rejected with no loop budget left — stop with feedback.
568
+ gateRejection = finalize.gateRejection;
569
+ stopEngine = true;
570
+ }
571
+ if (stopEngine || !advanced)
572
+ break;
573
+ next = await getNextWorkflowStep(next.run.id);
574
+ }
575
+ // Re-read for the freshest run state (the loop may have exited on maxSteps).
576
+ const finalState = await getNextWorkflowStep(next.run.id);
577
+ return {
578
+ run: finalState.run,
579
+ executed,
580
+ ...(finalState.run.status === "completed" ? { done: true } : {}),
581
+ ...(gateRejection ? { gateRejection } : {}),
582
+ };
583
+ }
584
+ /**
585
+ * Load the plan a run executes (frozen-plan contract, migration 006):
586
+ *
587
+ * - `plan_json` present → parse it and verify `plan_hash` (sha256 of the
588
+ * canonical JSON). A mismatch means the journaled plan was tampered with
589
+ * or corrupted — fail loudly, never silently recompile. The workflow
590
+ * asset file is NEVER touched on this path.
591
+ * Missing and non-current plans fail validation and are never rebuilt from a
592
+ * mutable source asset.
593
+ */
594
+ async function loadFrozenPlan(runId) {
595
+ const row = await withWorkflowRunsRepo((repo) => {
596
+ const run = repo.getRunById(runId);
597
+ return run;
598
+ });
599
+ if (!row)
600
+ throw new UsageError(`Workflow run ${runId} was not found.`);
601
+ return requireExecutableWorkflowPlan(row);
602
+ }