akm-cli 0.9.0-beta.9 → 0.9.0-rc.1

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 (381) hide show
  1. package/CHANGELOG.md +715 -0
  2. package/README.md +12 -4
  3. package/dist/akm +38 -0
  4. package/dist/akm-migrate-storage +38 -0
  5. package/dist/assets/help/help-improve.md +9 -6
  6. package/dist/assets/hints/cli-hints-full.md +6 -5
  7. package/dist/assets/profiles/default.json +9 -4
  8. package/dist/assets/profiles/frequent.json +1 -1
  9. package/dist/assets/profiles/memory-focus.json +1 -1
  10. package/dist/assets/profiles/proactive-maintenance.json +25 -0
  11. package/dist/assets/profiles/quick.json +1 -1
  12. package/dist/assets/profiles/recombine-only.json +21 -0
  13. package/dist/assets/profiles/reflect-distill.json +30 -0
  14. package/dist/assets/profiles/synthesize.json +15 -0
  15. package/dist/assets/profiles/thorough.json +1 -1
  16. package/dist/assets/prompts/consolidate-system.md +23 -0
  17. package/dist/assets/prompts/contradiction-judge.md +33 -0
  18. package/dist/assets/prompts/distill-knowledge-system.md +22 -0
  19. package/dist/assets/prompts/distill-lesson-system.md +36 -0
  20. package/dist/assets/prompts/extract-session.md +11 -3
  21. package/dist/assets/prompts/graph-extract-system.md +1 -0
  22. package/dist/assets/prompts/graph-extract-user-prompt.md +1 -1
  23. package/dist/assets/prompts/memory-infer-system.md +1 -0
  24. package/dist/assets/prompts/memory-infer-user.md +5 -0
  25. package/dist/assets/prompts/metadata-enhance-system.md +1 -0
  26. package/dist/assets/prompts/procedural-system.md +44 -0
  27. package/dist/assets/prompts/recombine-system.md +40 -0
  28. package/dist/assets/prompts/staleness-detect-system.md +6 -0
  29. package/dist/assets/prompts/validate-summary-judge.md +1 -0
  30. package/dist/assets/prompts/workflow-unit-preamble.md +26 -0
  31. package/dist/assets/stash-skeleton/facts/conventions/assets/agent.md +38 -0
  32. package/dist/assets/stash-skeleton/facts/conventions/assets/command.md +38 -0
  33. package/dist/assets/stash-skeleton/facts/conventions/assets/fact.md +39 -0
  34. package/dist/assets/stash-skeleton/facts/conventions/assets/knowledge.md +40 -0
  35. package/dist/assets/stash-skeleton/facts/conventions/assets/lesson.md +43 -0
  36. package/dist/assets/stash-skeleton/facts/conventions/assets/memory.md +38 -0
  37. package/dist/assets/stash-skeleton/facts/conventions/assets/script.md +43 -0
  38. package/dist/assets/stash-skeleton/facts/conventions/assets/skill.md +40 -0
  39. package/dist/assets/stash-skeleton/facts/conventions/assets/workflow.md +43 -0
  40. package/dist/assets/templates/html/health.html +281 -111
  41. package/dist/assets/wiki/ingest-workflow-template.md +45 -16
  42. package/dist/assets/wiki/schema-template.md +4 -4
  43. package/dist/cli/clack.js +56 -0
  44. package/dist/cli/config-migrate.js +7 -1
  45. package/dist/cli/confirm.js +1 -1
  46. package/dist/cli/parse-args.js +46 -1
  47. package/dist/cli/shared.js +28 -0
  48. package/dist/cli.js +25 -21
  49. package/dist/commands/agent/agent-dispatch.js +3 -2
  50. package/dist/commands/agent/agent-support.js +0 -7
  51. package/dist/commands/agent/contribute-cli.js +26 -7
  52. package/dist/commands/config-cli.js +26 -13
  53. package/dist/commands/env/child-env.js +47 -0
  54. package/dist/commands/env/env-binding.js +95 -0
  55. package/dist/commands/env/env-cli.js +228 -292
  56. package/dist/commands/env/env.js +14 -67
  57. package/dist/commands/env/secret-cli.js +140 -138
  58. package/dist/commands/feedback-cli.js +156 -155
  59. package/dist/commands/graph/graph-cli.js +5 -13
  60. package/dist/commands/graph/graph.js +3 -3
  61. package/dist/commands/health/advisories.js +151 -0
  62. package/dist/commands/health/checks.js +103 -16
  63. package/dist/commands/health/html-report.js +447 -81
  64. package/dist/commands/health/improve-metrics.js +771 -0
  65. package/dist/commands/health/llm-usage.js +65 -0
  66. package/dist/commands/health/md-report.js +103 -0
  67. package/dist/commands/health/metrics.js +278 -0
  68. package/dist/commands/health/stash-exposure.js +46 -0
  69. package/dist/commands/health/surfaces.js +216 -0
  70. package/dist/commands/health/task-runs.js +135 -0
  71. package/dist/commands/health/types.js +26 -0
  72. package/dist/commands/health/windows.js +195 -0
  73. package/dist/commands/health.js +91 -1091
  74. package/dist/commands/improve/anti-collapse.js +170 -0
  75. package/dist/commands/improve/calibration.js +161 -0
  76. package/dist/commands/improve/collapse-detector.js +421 -0
  77. package/dist/commands/improve/consolidate/chunking.js +141 -0
  78. package/dist/commands/improve/consolidate/eligibility.js +64 -0
  79. package/dist/commands/improve/consolidate/merge.js +145 -0
  80. package/dist/commands/improve/consolidate/sanitize.js +231 -0
  81. package/dist/commands/{lint.js → improve/consolidate/types.js} +1 -1
  82. package/dist/commands/improve/consolidate.js +1295 -1277
  83. package/dist/commands/improve/dedup.js +482 -0
  84. package/dist/commands/improve/distill/content-repair.js +202 -0
  85. package/dist/commands/improve/distill/promote-memory.js +229 -0
  86. package/dist/commands/improve/distill/quality-gate.js +236 -0
  87. package/dist/commands/improve/distill-guards.js +127 -0
  88. package/dist/commands/improve/distill-promotion-policy.js +826 -167
  89. package/dist/commands/improve/distill.js +228 -605
  90. package/dist/commands/improve/eligibility.js +434 -0
  91. package/dist/commands/improve/encoding-salience.js +205 -0
  92. package/dist/commands/improve/extract-cli.js +179 -59
  93. package/dist/commands/improve/extract-prompt.js +54 -3
  94. package/dist/commands/improve/extract-watch.js +140 -0
  95. package/dist/commands/improve/extract.js +409 -43
  96. package/dist/commands/improve/feedback-valence.js +54 -0
  97. package/dist/commands/improve/hot-probation.js +45 -0
  98. package/dist/commands/improve/improve-auto-accept.js +157 -10
  99. package/dist/commands/improve/improve-cli.js +115 -73
  100. package/dist/commands/improve/improve-profiles.js +28 -8
  101. package/dist/commands/improve/improve-result-file.js +15 -25
  102. package/dist/commands/improve/improve-session.js +58 -0
  103. package/dist/commands/improve/improve.js +485 -2764
  104. package/dist/commands/improve/locks.js +154 -0
  105. package/dist/commands/improve/loop-stages.js +1100 -0
  106. package/dist/commands/improve/memory/memory-belief.js +14 -15
  107. package/dist/commands/improve/memory/memory-contradiction-detect.js +83 -60
  108. package/dist/commands/improve/memory/memory-improve.js +27 -27
  109. package/dist/commands/improve/outcome-loop.js +270 -0
  110. package/dist/commands/improve/preparation.js +2002 -0
  111. package/dist/commands/improve/proactive-maintenance.js +37 -35
  112. package/dist/commands/improve/procedural.js +398 -0
  113. package/dist/commands/improve/recombine.js +818 -0
  114. package/dist/commands/improve/reflect-noise.js +0 -0
  115. package/dist/commands/improve/reflect.js +206 -45
  116. package/dist/commands/improve/salience.js +455 -0
  117. package/dist/commands/improve/schema-similarity-gate.js +168 -0
  118. package/dist/commands/improve/shared.js +51 -0
  119. package/dist/commands/improve/triage.js +93 -0
  120. package/dist/commands/lint/agent-linter.js +19 -24
  121. package/dist/commands/lint/base-linter.js +173 -60
  122. package/dist/commands/lint/command-linter.js +19 -24
  123. package/dist/commands/lint/env-key-rules.js +38 -1
  124. package/dist/commands/lint/fact-linter.js +39 -0
  125. package/dist/commands/lint/index.js +31 -13
  126. package/dist/commands/lint/memory-linter.js +1 -1
  127. package/dist/commands/lint/registry.js +7 -2
  128. package/dist/commands/lint/task-linter.js +3 -3
  129. package/dist/commands/lint/workflow-linter.js +26 -1
  130. package/dist/commands/observability-cli.js +4 -4
  131. package/dist/commands/proposal/drain-policies.js +13 -4
  132. package/dist/commands/proposal/drain.js +45 -51
  133. package/dist/commands/proposal/legacy-import.js +115 -0
  134. package/dist/commands/proposal/proposal-cli.js +24 -34
  135. package/dist/commands/proposal/proposal.js +2 -1
  136. package/dist/commands/proposal/propose.js +8 -3
  137. package/dist/commands/proposal/repository.js +829 -0
  138. package/dist/commands/proposal/validators/proposal-quality-validators.js +9 -8
  139. package/dist/commands/proposal/validators/proposals.js +93 -895
  140. package/dist/commands/read/curate.js +410 -111
  141. package/dist/commands/read/knowledge.js +10 -3
  142. package/dist/commands/read/remember-cli.js +133 -138
  143. package/dist/commands/read/search-cli.js +15 -8
  144. package/dist/commands/read/search.js +22 -11
  145. package/dist/commands/read/show.js +106 -14
  146. package/dist/commands/registry-cli.js +76 -87
  147. package/dist/commands/remember.js +11 -12
  148. package/dist/commands/sources/add-cli.js +91 -95
  149. package/dist/commands/sources/history.js +1 -1
  150. package/dist/commands/sources/init.js +66 -18
  151. package/dist/commands/sources/installed-stashes.js +11 -3
  152. package/dist/commands/sources/migration-help.js +7 -4
  153. package/dist/commands/sources/schema-repair.js +44 -46
  154. package/dist/commands/sources/self-update.js +2 -2
  155. package/dist/commands/sources/source-add.js +7 -3
  156. package/dist/commands/sources/sources-cli.js +3 -3
  157. package/dist/commands/sources/stash-cli.js +19 -39
  158. package/dist/commands/sources/stash-skeleton.js +57 -8
  159. package/dist/commands/tasks/default-tasks.js +15 -2
  160. package/dist/commands/tasks/tasks-cli.js +20 -29
  161. package/dist/commands/tasks/tasks.js +39 -11
  162. package/dist/commands/wiki-cli.js +23 -38
  163. package/dist/commands/workflow-cli.js +291 -13
  164. package/dist/core/asset/asset-registry.js +3 -1
  165. package/dist/core/asset/asset-spec.js +79 -5
  166. package/dist/core/asset/frontmatter.js +188 -167
  167. package/dist/core/asset/markdown.js +8 -0
  168. package/dist/core/authoring-rules.js +92 -0
  169. package/dist/core/common.js +4 -23
  170. package/dist/core/concurrent.js +10 -1
  171. package/dist/core/config/config-io.js +10 -1
  172. package/dist/core/config/config-migration.js +18 -40
  173. package/dist/core/config/config-schema.js +403 -62
  174. package/dist/core/config/config-types.js +3 -3
  175. package/dist/core/config/config.js +67 -22
  176. package/dist/core/deep-merge.js +38 -0
  177. package/dist/core/errors.js +1 -0
  178. package/dist/core/eval/rank-metrics.js +113 -0
  179. package/dist/core/events.js +4 -7
  180. package/dist/core/improve-types.js +47 -8
  181. package/dist/core/json-schema.js +142 -0
  182. package/dist/core/logs-db.js +14 -75
  183. package/dist/core/parse.js +36 -16
  184. package/dist/core/paths.js +18 -18
  185. package/dist/core/standards/resolve-standards-context.js +87 -0
  186. package/dist/core/standards/resolve-stash-standards.js +99 -0
  187. package/dist/core/standards/resolve-type-conventions.js +66 -0
  188. package/dist/core/state/migrations.js +770 -0
  189. package/dist/core/state-db.js +132 -1126
  190. package/dist/core/structured.js +69 -0
  191. package/dist/core/time.js +53 -0
  192. package/dist/core/warn.js +21 -0
  193. package/dist/core/write-source.js +37 -0
  194. package/dist/indexer/db/db.js +261 -770
  195. package/dist/indexer/db/entry-mapper.js +41 -0
  196. package/dist/indexer/db/graph-db.js +129 -86
  197. package/dist/indexer/db/llm-cache.js +2 -2
  198. package/dist/indexer/db/schema.js +516 -0
  199. package/dist/indexer/ensure-index.js +36 -92
  200. package/dist/indexer/feedback/utility-policy.js +75 -0
  201. package/dist/indexer/graph/graph-boost.js +51 -41
  202. package/dist/indexer/graph/graph-extraction.js +207 -4
  203. package/dist/indexer/index-writer-lock.js +18 -11
  204. package/dist/indexer/index-written-assets.js +105 -0
  205. package/dist/indexer/indexer.js +182 -204
  206. package/dist/indexer/passes/dir-staleness.js +114 -0
  207. package/dist/indexer/passes/memory-inference.js +13 -5
  208. package/dist/indexer/passes/metadata.js +20 -0
  209. package/dist/indexer/read-preflight.js +23 -0
  210. package/dist/indexer/search/db-search.js +89 -13
  211. package/dist/indexer/search/fts-query.js +51 -0
  212. package/dist/indexer/search/ranking-contributors.js +95 -9
  213. package/dist/indexer/search/ranking.js +79 -3
  214. package/dist/indexer/search/search-fields.js +6 -0
  215. package/dist/indexer/search/search-source.js +32 -21
  216. package/dist/indexer/search/semantic-status.js +4 -0
  217. package/dist/indexer/walk/matchers.js +48 -0
  218. package/dist/indexer/walk/walker.js +21 -13
  219. package/dist/integrations/agent/builders.js +41 -13
  220. package/dist/integrations/agent/config.js +20 -59
  221. package/dist/integrations/agent/detect.js +9 -0
  222. package/dist/integrations/agent/index.js +3 -19
  223. package/dist/integrations/agent/model-aliases.js +16 -2
  224. package/dist/integrations/agent/profiles.js +79 -6
  225. package/dist/integrations/agent/prompts.js +75 -9
  226. package/dist/integrations/agent/runner-dispatch.js +83 -0
  227. package/dist/integrations/agent/runner.js +13 -9
  228. package/dist/integrations/agent/spawn.js +206 -81
  229. package/dist/integrations/harnesses/aider/agent-builder.js +113 -0
  230. package/dist/integrations/harnesses/aider/index.js +58 -0
  231. package/dist/integrations/harnesses/aider/result-extractor.js +53 -0
  232. package/dist/integrations/harnesses/amazonq/agent-builder.js +153 -0
  233. package/dist/integrations/harnesses/amazonq/index.js +59 -0
  234. package/dist/integrations/harnesses/amazonq/result-extractor.js +48 -0
  235. package/dist/integrations/harnesses/claude/agent-builder.js +46 -7
  236. package/dist/integrations/harnesses/claude/index.js +27 -23
  237. package/dist/integrations/harnesses/claude/result-extractor.js +52 -0
  238. package/dist/integrations/harnesses/claude/session-log.js +10 -0
  239. package/dist/integrations/harnesses/codex/agent-builder.js +137 -0
  240. package/dist/integrations/harnesses/codex/index.js +63 -0
  241. package/dist/integrations/harnesses/codex/result-extractor.js +73 -0
  242. package/dist/integrations/harnesses/copilot/agent-builder.js +122 -0
  243. package/dist/integrations/harnesses/copilot/index.js +60 -0
  244. package/dist/integrations/harnesses/copilot/result-extractor.js +151 -0
  245. package/dist/integrations/harnesses/gemini/agent-builder.js +121 -0
  246. package/dist/integrations/harnesses/gemini/index.js +60 -0
  247. package/dist/integrations/harnesses/gemini/result-extractor.js +121 -0
  248. package/dist/integrations/harnesses/index.js +28 -7
  249. package/dist/integrations/harnesses/opencode/agent-builder.js +1 -1
  250. package/dist/integrations/harnesses/opencode/index.js +17 -16
  251. package/dist/integrations/harnesses/opencode/session-log.js +173 -3
  252. package/dist/integrations/harnesses/opencode-sdk/harness.js +65 -0
  253. package/dist/integrations/harnesses/opencode-sdk/index.js +10 -34
  254. package/dist/integrations/harnesses/opencode-sdk/sdk-runner.js +642 -71
  255. package/dist/integrations/harnesses/openhands/agent-builder.js +126 -0
  256. package/dist/integrations/harnesses/openhands/index.js +58 -0
  257. package/dist/integrations/harnesses/openhands/result-extractor.js +103 -0
  258. package/dist/integrations/harnesses/pi/agent-builder.js +104 -0
  259. package/dist/integrations/harnesses/pi/index.js +58 -0
  260. package/dist/integrations/harnesses/pi/result-extractor.js +135 -0
  261. package/dist/integrations/harnesses/types.js +8 -0
  262. package/dist/integrations/session-logs/index.js +40 -11
  263. package/dist/llm/call-ai.js +2 -2
  264. package/dist/llm/client.js +34 -11
  265. package/dist/llm/embedder.js +67 -4
  266. package/dist/llm/embedders/cache.js +3 -1
  267. package/dist/llm/embedders/deterministic.js +66 -0
  268. package/dist/llm/embedders/local.js +73 -3
  269. package/dist/llm/feature-gate.js +16 -15
  270. package/dist/llm/graph-extract.js +67 -44
  271. package/dist/llm/memory-infer-impl.js +138 -0
  272. package/dist/llm/memory-infer.js +1 -127
  273. package/dist/llm/metadata-enhance.js +44 -31
  274. package/dist/llm/structured-call.js +49 -0
  275. package/dist/migrate-storage-node.mjs +8 -0
  276. package/dist/output/context.js +5 -5
  277. package/dist/output/renderers.js +87 -15
  278. package/dist/output/shapes/curate.js +14 -2
  279. package/dist/output/shapes/helpers.js +0 -3
  280. package/dist/output/shapes/passthrough.js +6 -1
  281. package/dist/output/text/helpers.js +241 -2
  282. package/dist/output/text/workflow.js +4 -1
  283. package/dist/registry/providers/skills-sh.js +21 -147
  284. package/dist/registry/providers/static-index.js +15 -157
  285. package/dist/registry/resolve.js +27 -9
  286. package/dist/runtime.js +25 -1
  287. package/dist/schemas/akm-config.json +14225 -0
  288. package/dist/schemas/akm-workflow.json +328 -0
  289. package/dist/scripts/migrate-storage.js +2743 -8390
  290. package/dist/scripts/migrations/import-fs-improve-runs-to-db.js +1652 -607
  291. package/dist/setup/detect.js +9 -0
  292. package/dist/setup/legacy-config.js +106 -0
  293. package/dist/setup/prompt.js +57 -0
  294. package/dist/setup/providers.js +14 -0
  295. package/dist/setup/registry-stash-loader.js +12 -0
  296. package/dist/setup/semantic-assets.js +124 -0
  297. package/dist/setup/setup.js +52 -1614
  298. package/dist/setup/steps/connection.js +734 -0
  299. package/dist/setup/steps/output.js +31 -0
  300. package/dist/setup/steps/platforms.js +124 -0
  301. package/dist/setup/steps/semantic.js +27 -0
  302. package/dist/setup/steps/sources.js +222 -0
  303. package/dist/setup/steps/stashdir.js +42 -0
  304. package/dist/setup/steps/tasks.js +152 -0
  305. package/dist/sources/include.js +6 -2
  306. package/dist/sources/providers/filesystem.js +0 -1
  307. package/dist/sources/providers/git-install.js +210 -0
  308. package/dist/sources/providers/git-provider.js +234 -0
  309. package/dist/sources/providers/git-stash.js +248 -0
  310. package/dist/sources/providers/git.js +10 -661
  311. package/dist/sources/providers/npm.js +2 -6
  312. package/dist/sources/providers/provider-utils.js +13 -7
  313. package/dist/sources/providers/sync-from-ref.js +9 -1
  314. package/dist/sources/providers/website.js +9 -5
  315. package/dist/sources/website-ingest.js +187 -29
  316. package/dist/sources/wiki-fetchers/registry.js +53 -0
  317. package/dist/sources/wiki-fetchers/youtube.js +239 -0
  318. package/dist/storage/database.js +45 -10
  319. package/dist/storage/managed-db.js +82 -0
  320. package/dist/storage/repositories/canaries-repository.js +107 -0
  321. package/dist/storage/repositories/consolidation-repository.js +38 -0
  322. package/dist/storage/repositories/embeddings-repository.js +72 -0
  323. package/dist/storage/repositories/events-repository.js +187 -0
  324. package/dist/storage/repositories/extract-sessions-repository.js +96 -0
  325. package/dist/storage/repositories/improve-runs-repository.js +146 -0
  326. package/dist/storage/repositories/index-db.js +14 -8
  327. package/dist/storage/repositories/proposals-repository.js +220 -0
  328. package/dist/storage/repositories/recombine-repository.js +213 -0
  329. package/dist/storage/repositories/registry-cache.js +93 -0
  330. package/dist/storage/repositories/registry-index-cache-repository.js +46 -0
  331. package/dist/storage/repositories/task-history-repository.js +93 -0
  332. package/dist/storage/repositories/workflow-runs-repository.js +189 -1
  333. package/dist/storage/sqlite-pragmas.js +146 -0
  334. package/dist/tasks/backends/cron.js +1 -1
  335. package/dist/tasks/backends/index.js +9 -0
  336. package/dist/tasks/backends/launchd.js +1 -1
  337. package/dist/tasks/backends/schtasks.js +1 -1
  338. package/dist/tasks/{resolveAkmBin.js → resolve-akm-bin.js} +2 -2
  339. package/dist/tasks/runner.js +15 -13
  340. package/dist/text-import-hook.mjs +1 -1
  341. package/dist/wiki/wiki.js +52 -11
  342. package/dist/workflows/authoring/authoring.js +123 -10
  343. package/dist/workflows/authoring/workflow-program-template.yaml +31 -0
  344. package/dist/workflows/cli.js +5 -0
  345. package/dist/workflows/db.js +138 -4
  346. package/dist/workflows/exec/brief.js +484 -0
  347. package/dist/workflows/exec/native-executor.js +975 -0
  348. package/dist/workflows/exec/param-secrets.js +115 -0
  349. package/dist/workflows/exec/report.js +1295 -0
  350. package/dist/workflows/exec/run-workflow.js +596 -0
  351. package/dist/workflows/exec/scheduler.js +100 -0
  352. package/dist/workflows/exec/step-work.js +1156 -0
  353. package/dist/workflows/exec/unit-writer.js +23 -0
  354. package/dist/workflows/exec/watch.js +116 -0
  355. package/dist/workflows/exec/worktree.js +171 -0
  356. package/dist/workflows/ir/compile.js +388 -0
  357. package/dist/workflows/ir/params.js +54 -0
  358. package/dist/workflows/ir/plan-hash.js +33 -0
  359. package/dist/workflows/ir/schema.js +4 -0
  360. package/dist/workflows/parser.js +3 -1
  361. package/dist/workflows/program/expressions.js +369 -0
  362. package/dist/workflows/program/parser.js +760 -0
  363. package/dist/workflows/program/project.js +105 -0
  364. package/dist/workflows/program/schema.js +54 -0
  365. package/dist/workflows/renderer.js +82 -5
  366. package/dist/workflows/runtime/agent-identity.js +59 -14
  367. package/dist/workflows/runtime/runs.js +248 -153
  368. package/dist/workflows/runtime/unit-checkin.js +45 -0
  369. package/dist/workflows/runtime/workflow-asset-loader.js +188 -0
  370. package/dist/workflows/validate-summary.js +26 -10
  371. package/dist/workflows/validator.js +1 -1
  372. package/docs/README.md +69 -18
  373. package/docs/data-and-telemetry.md +7 -5
  374. package/docs/migration/release-notes/0.7.0.md +1 -1
  375. package/docs/migration/release-notes/0.9.0-beta.60.md +19 -0
  376. package/docs/migration/release-notes/0.9.0.md +39 -0
  377. package/package.json +10 -10
  378. package/dist/assets/tasks/core/update-stashes.yml +0 -4
  379. package/dist/commands/db-cli.js +0 -23
  380. package/dist/indexer/db/db-backup.js +0 -376
  381. package/dist/indexer/passes/staleness-detect.js +0 -488
@@ -2,25 +2,89 @@
2
2
  // License, v. 2.0. If a copy of the MPL was not distributed with this
3
3
  // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
4
  import { randomUUID } from "node:crypto";
5
- import fs from "node:fs";
6
5
  import { parseAssetRef } from "../../core/asset/asset-ref.js";
7
- import { loadConfig } from "../../core/config/config.js";
6
+ import { canonicalizeWorkflowName } from "../../core/asset/asset-spec.js";
7
+ import { getDefaultLlmConfig, loadConfig } from "../../core/config/config.js";
8
8
  import { NotFoundError, UsageError } from "../../core/errors.js";
9
9
  import { appendEvent } from "../../core/events.js";
10
- import { getDbPath } from "../../core/paths.js";
11
- import { closeDatabase, openExistingDatabase } from "../../indexer/db/db.js";
12
- import { resolveSourceEntries } from "../../indexer/search/search-source.js";
13
- import { resolveSourcesForOrigin } from "../../registry/origin-resolve.js";
14
- import { resolveAssetPath } from "../../sources/resolve.js";
10
+ import { warn } from "../../core/warn.js";
15
11
  import { withWorkflowRunsRepo, } from "../../storage/repositories/workflow-runs-repository.js";
16
- import { formatWorkflowErrors } from "../authoring/authoring.js";
17
12
  import { getCurrentWorkflowScopeKey } from "../authoring/scope-key.js";
18
- import { parseWorkflow } from "../parser.js";
13
+ import { detectSecretShapedParams } from "../exec/param-secrets.js";
14
+ import { collectProgramWarnings } from "../ir/compile.js";
15
+ import { validateWorkflowParams } from "../ir/params.js";
16
+ import { canonicalPlanJson, computePlanHash } from "../ir/plan-hash.js";
19
17
  import { validateStepSummary } from "../validate-summary.js";
20
18
  import { resolveAgentIdentity } from "./agent-identity.js";
21
19
  import { evaluateCheckin } from "./checkin.js";
20
+ import { evaluateStaleUnits } from "./unit-checkin.js";
21
+ import { compileWorkflowAssetPlan, loadWorkflowAsset, resolveWorkflowEntryId } from "./workflow-asset-loader.js";
22
+ /** Clip bound for a unit's `result_json` on the `--units` diagnostic surface. */
23
+ const UNIT_DIAGNOSTIC_CLIP = 2000;
24
+ function toUnitDiagnostic(row, stale) {
25
+ let diagnostic = null;
26
+ if (row.result_json !== null) {
27
+ // `result_json` is a JSON-encoded value: a bare JSON string for a free-text
28
+ // unit, an object/array for a schema unit. Render the decoded string as-is
29
+ // (no surrounding quotes) and other shapes as compact JSON, then clip so a
30
+ // large artifact can't flood the diagnostic surface.
31
+ let text = row.result_json;
32
+ try {
33
+ const parsed = JSON.parse(row.result_json);
34
+ text = typeof parsed === "string" ? parsed : JSON.stringify(parsed);
35
+ }
36
+ catch {
37
+ /* leave the raw journaled text */
38
+ }
39
+ diagnostic = text.length > UNIT_DIAGNOSTIC_CLIP ? `${text.slice(0, UNIT_DIAGNOSTIC_CLIP)}…` : text;
40
+ }
41
+ return {
42
+ unitId: row.unit_id,
43
+ nodeId: row.node_id,
44
+ stepId: row.step_id,
45
+ phase: row.phase,
46
+ status: row.status,
47
+ attempts: row.attempts,
48
+ tokens: row.tokens,
49
+ failureReason: row.failure_reason,
50
+ sessionId: row.session_id,
51
+ diagnostic,
52
+ startedAt: row.started_at,
53
+ finishedAt: row.finished_at,
54
+ stale: stale !== undefined,
55
+ staleIdleMs: stale ? (Number.isFinite(stale.idleMs) ? stale.idleMs : null) : null,
56
+ claimHolder: row.claim_holder,
57
+ claimExpiresAt: row.claim_expires_at,
58
+ };
59
+ }
22
60
  export async function startWorkflowRun(ref, params = {}, options) {
23
61
  const asset = await loadWorkflowAsset(ref);
62
+ // Frozen plan (redesign addendum, R1): compile the plan ONCE at start and
63
+ // persist it on the run row in the same transaction as the insert. Every
64
+ // later invocation executes this snapshot — the asset file is never re-read
65
+ // for an in-flight run; re-planning is an explicit new run.
66
+ const plan = compileWorkflowAssetPlan(asset);
67
+ // Non-fatal WARNINGS (redesign addendum): a YAML program's untyped-step and
68
+ // undeclared-param advisories surface as `warn()` lines at start (stderr,
69
+ // consistent with the repo's other author-facing warnings) without blocking
70
+ // the run. Markdown workflows carry no `program` and warn about nothing.
71
+ if (asset.program) {
72
+ for (const w of collectProgramWarnings(asset.program)) {
73
+ warn(`workflow start: ${asset.path}:${w.line} — ${w.message}`);
74
+ }
75
+ }
76
+ // Reviewer #12: validate supplied `--params` against the frozen param
77
+ // schemas BEFORE creating the run, so a type-mismatched param (e.g. a string
78
+ // for a `{ type: array }` param) is rejected with actionable errors instead
79
+ // of flowing silently into a unit prompt. Programs without declared param
80
+ // schemas (and every Markdown workflow) validate trivially.
81
+ const paramErrors = validateWorkflowParams(plan, params);
82
+ if (paramErrors.length > 0) {
83
+ throw new UsageError(`Cannot start ${asset.ref}: the supplied --params do not satisfy the workflow's declared parameter schemas:\n` +
84
+ paramErrors.map((e) => ` - ${e}`).join("\n"), "INVALID_JSON_ARGUMENT");
85
+ }
86
+ const planJson = canonicalPlanJson(plan);
87
+ const planHash = computePlanHash(plan);
24
88
  return withWorkflowRunsRepo(async (repo) => {
25
89
  const now = new Date().toISOString();
26
90
  const runId = randomUUID();
@@ -72,21 +136,49 @@ export async function startWorkflowRun(ref, params = {}, options) {
72
136
  completionJson: step.completionCriteria ? JSON.stringify(step.completionCriteria) : null,
73
137
  sequenceIndex: step.sequenceIndex ?? 0,
74
138
  })));
139
+ // Same transaction as the insert: a run row never exists without its
140
+ // frozen plan (rows with NULL plan_json are pre-006 legacy runs).
141
+ repo.setRunPlan(runId, planJson, planHash);
75
142
  });
76
143
  const result = await getWorkflowStatus(runId);
144
+ // #13: params are declared non-secret (they are copied verbatim into every
145
+ // unit prompt and hashed into the unit identity, so they cannot be redacted
146
+ // without breaking the driver protocol). Surface a loud, best-effort warning
147
+ // when a param LOOKS like a credential so the author moves it to an env
148
+ // binding. Advisory only — never blocks the start.
149
+ const secretWarnings = detectSecretShapedParams(params);
150
+ if (secretWarnings.length > 0)
151
+ result.warnings = [...(result.warnings ?? []), ...secretWarnings];
152
+ // 07 P1-B: emit only the run id + status — NOT the raw workflowTitle (which
153
+ // comes verbatim from the workflow asset's frontmatter and is therefore
154
+ // attacker-influenceable). Keeping raw titles out of the events stream
155
+ // shrinks the injectable footprint for any consumer that re-surfaces events
156
+ // into agent context.
77
157
  appendEvent({
78
158
  eventType: "workflow_started",
79
159
  ref: ref,
80
- metadata: { runId: result.run.id, title: result.run.workflowTitle },
160
+ metadata: { runId: result.run.id, status: result.run.status },
81
161
  });
82
162
  return result;
83
163
  });
84
164
  }
85
- export async function getWorkflowStatus(runId) {
165
+ export async function getWorkflowStatus(runId, opts) {
86
166
  return withWorkflowRunsRepo((repo) => {
87
167
  const run = readWorkflowRun(repo, runId);
88
168
  const steps = readWorkflowRunSteps(repo, run.id);
89
- return buildWorkflowRunDetail(run, steps);
169
+ const detail = buildWorkflowRunDetail(run, steps);
170
+ if (opts?.includeUnits) {
171
+ // The honest diagnostic surface (#22): read the unit journal straight and
172
+ // project each row, INCLUDING failures whose diagnostic text the
173
+ // deterministic evidence graph drops. Read-only; never mutates the run.
174
+ const rows = repo.getUnitsForRun(run.id);
175
+ // Codex round-3 finding B: run the SAME pure stale-claim evaluator `brief`
176
+ // uses (`now` injected for deterministic tests) so a dead driver's claimed
177
+ // `running` unit surfaces as stale here too, not just as raw `running`.
178
+ const staleById = new Map(evaluateStaleUnits(rows, opts.now ?? Date.now()).map((u) => [u.unitId, u]));
179
+ detail.units = rows.map((row) => toUnitDiagnostic(row, staleById.get(row.unit_id)));
180
+ }
181
+ return detail;
90
182
  });
91
183
  }
92
184
  export async function hasWorkflowRun(runId) {
@@ -101,7 +193,7 @@ export async function listWorkflowRuns(input) {
101
193
  if (parsed.type !== "workflow") {
102
194
  throw new UsageError(`Expected a workflow ref (workflow:<name>), got "${input.workflowRef}".`);
103
195
  }
104
- workflowRef = `${parsed.origin ? `${parsed.origin}//` : ""}workflow:${parsed.name}`;
196
+ workflowRef = `${parsed.origin ? `${parsed.origin}//` : ""}workflow:${canonicalizeWorkflowName(parsed.name)}`;
105
197
  }
106
198
  const rows = repo.listRuns({
107
199
  scopeKey,
@@ -115,31 +207,58 @@ export async function getNextWorkflowStep(specifier, params) {
115
207
  return withWorkflowRunsRepo(async (repo) => {
116
208
  const { run, autoStarted } = await resolveRunSpecifier(repo, specifier, params);
117
209
  const steps = readWorkflowRunSteps(repo, run.id);
118
- const currentStep = resolveCurrentStep(run, steps);
119
- const done = run.status === "completed" ? true : undefined;
120
- // #506: surface a check-in directive through the normal command output when
121
- // the run looks stalled. Pure timestamp evaluation — no background thread.
122
- const checkin = evaluateCheckin({
123
- status: run.status,
124
- updatedAt: run.updated_at,
125
- checkinArmedAt: run.checkin_armed_at,
126
- agentHarness: run.agent_harness,
127
- agentSessionId: run.agent_session_id,
128
- }) ?? undefined;
129
- return {
130
- run: toWorkflowRunSummary(run),
131
- workflow: {
132
- ref: run.workflow_ref,
133
- title: run.workflow_title,
134
- steps: steps.map(toWorkflowRunStepState),
135
- },
136
- step: currentStep ? toWorkflowRunStepState(currentStep) : null,
137
- ...(done ? { done } : {}),
138
- ...(autoStarted ? { autoStarted } : {}),
139
- ...(checkin ? { checkin } : {}),
140
- };
210
+ return { ...projectNextResult(run, steps), ...(autoStarted ? { autoStarted: true } : {}) };
141
211
  });
142
212
  }
213
+ /**
214
+ * Project a run row + its step rows into a {@link WorkflowNextResult}. The pure
215
+ * read-shaping half of {@link getNextWorkflowStep}, extracted so the driver
216
+ * snapshot below reproduces the exact same projection without re-running the
217
+ * auto-start-capable {@link resolveRunSpecifier}.
218
+ */
219
+ function projectNextResult(run, steps) {
220
+ const currentStep = resolveCurrentStep(run, steps);
221
+ const done = run.status === "completed" ? true : undefined;
222
+ // #506: surface a check-in directive through the normal command output when
223
+ // the run looks stalled. Pure timestamp evaluation — no background thread.
224
+ const checkin = evaluateCheckin({
225
+ status: run.status,
226
+ updatedAt: run.updated_at,
227
+ checkinArmedAt: run.checkin_armed_at,
228
+ agentHarness: run.agent_harness,
229
+ agentSessionId: run.agent_session_id,
230
+ }) ?? undefined;
231
+ return {
232
+ run: toWorkflowRunSummary(run),
233
+ workflow: {
234
+ ref: run.workflow_ref,
235
+ title: run.workflow_title,
236
+ steps: steps.map(toWorkflowRunStepState),
237
+ },
238
+ step: currentStep ? toWorkflowRunStepState(currentStep) : null,
239
+ ...(done ? { done } : {}),
240
+ ...(checkin ? { checkin } : {}),
241
+ };
242
+ }
243
+ /**
244
+ * A consistent point-in-time snapshot of a run for the harness-neutral driver
245
+ * protocol (PR #714 review round 2, #14). `brief`/`report` previously read the
246
+ * spine (`getNextWorkflowStep`) and then, in a SEPARATE connection, the run row
247
+ * + unit journal — so a concurrent `report`/`run`/manual completion could change
248
+ * the active step BETWEEN the two reads, leaving the described work-list
249
+ * inconsistent with the run row it was stamped against. This reads the run row,
250
+ * its steps, AND its unit rows inside ONE transaction (one connection, one
251
+ * snapshot) so all three agree. It never auto-starts — `runId` must already
252
+ * resolve to a concrete run — because the driver protocol never mutates on read.
253
+ */
254
+ export async function snapshotRunForDriver(runId) {
255
+ return withWorkflowRunsRepo((repo) => repo.transaction(() => {
256
+ const run = readWorkflowRun(repo, runId);
257
+ const steps = readWorkflowRunSteps(repo, run.id);
258
+ const units = repo.getUnitsForRun(run.id);
259
+ return { next: projectNextResult(run, steps), run, units };
260
+ }));
261
+ }
143
262
  export async function resumeWorkflowRun(runId) {
144
263
  return withWorkflowRunsRepo((repo) => {
145
264
  const run = readWorkflowRun(repo, runId);
@@ -164,6 +283,42 @@ export async function resumeWorkflowRun(runId) {
164
283
  return buildWorkflowRunDetail(updated, steps);
165
284
  });
166
285
  }
286
+ /**
287
+ * Give up on a run (08-F6): flip it to `failed` so it stops counting as
288
+ * active — the run-level verb the concurrency-guard message in
289
+ * {@link startWorkflowRun} advertises. Terminal-state runs are refused;
290
+ * {@link resumeWorkflowRun} can reopen an abandoned run if it was a mistake.
291
+ */
292
+ export async function abandonWorkflowRun(runId) {
293
+ return withWorkflowRunsRepo((repo) => {
294
+ const run = readWorkflowRun(repo, runId);
295
+ if (run.status === "completed" || run.status === "failed") {
296
+ throw new UsageError(`Workflow run ${run.id} is already ${run.status}.`);
297
+ }
298
+ const now = new Date().toISOString();
299
+ repo.updateRunState({
300
+ status: "failed",
301
+ currentStepId: run.current_step_id,
302
+ updatedAt: now,
303
+ completedAt: now,
304
+ checkinArmedAt: now,
305
+ runId: run.id,
306
+ });
307
+ const updated = {
308
+ ...run,
309
+ status: "failed",
310
+ updated_at: now,
311
+ completed_at: now,
312
+ checkin_armed_at: now,
313
+ };
314
+ const steps = readWorkflowRunSteps(repo, run.id);
315
+ const detail = buildWorkflowRunDetail(updated, steps);
316
+ // Same injectable-footprint rule as workflow_started (07 P1-B): ids and
317
+ // status only, never the frontmatter-derived title.
318
+ appendEvent({ eventType: "workflow_abandoned", ref: run.workflow_ref, metadata: { runId: run.id } });
319
+ return detail;
320
+ });
321
+ }
167
322
  export async function completeWorkflowStep(input) {
168
323
  // Read the step (read-only) up front so the LLM validation gate runs OUTSIDE
169
324
  // the write transaction — a slow/hung LLM must never hold a db write lock.
@@ -172,6 +327,7 @@ export async function completeWorkflowStep(input) {
172
327
  if (run.status !== "active") {
173
328
  throw new UsageError(`Workflow run ${run.id} is ${run.status} and cannot be updated.`);
174
329
  }
330
+ assertLeaseAllowsSpineAdvance(run, input.leaseHolder);
175
331
  const existing = repo.getStep(run.id, input.stepId);
176
332
  if (!existing) {
177
333
  throw new NotFoundError(`Step "${input.stepId}" was not found in workflow run ${run.id}.`);
@@ -195,7 +351,7 @@ export async function completeWorkflowStep(input) {
195
351
  if (input.status === "completed" && summary) {
196
352
  const criteria = parseJsonArray(preflight.existing.completion_json) ?? [];
197
353
  const judge = input.summaryJudge === undefined ? buildDefaultSummaryJudge() : input.summaryJudge;
198
- const verdict = await validateStepSummary({ stepTitle: preflight.existing.step_title, completionCriteria: criteria, summary }, judge ?? undefined);
354
+ const verdict = await validateStepSummary({ stepTitle: preflight.existing.step_title, completionCriteria: criteria, summary }, judge ?? undefined, { required: input.requireGate === true });
199
355
  if (!verdict.complete) {
200
356
  // Re-arm the check-in so a subsequent stall is still nudged, but leave the
201
357
  // step pending and return corrective feedback instead of completing.
@@ -208,6 +364,9 @@ export async function completeWorkflowStep(input) {
208
364
  stepId: input.stepId,
209
365
  missing: verdict.missing,
210
366
  feedback: verdict.feedback ?? "The summary does not satisfy the step's completion criteria.",
367
+ // A REQUIRED gate that could not be judged (finding A): the caller BLOCKS
368
+ // rather than treating this as a normal, retryable gate rejection.
369
+ ...(verdict.errored ? { errored: true } : {}),
211
370
  };
212
371
  }
213
372
  }
@@ -219,6 +378,10 @@ export async function completeWorkflowStep(input) {
219
378
  if (run.status !== "active") {
220
379
  throw new UsageError(`Workflow run ${run.id} is ${run.status} and cannot be updated.`);
221
380
  }
381
+ // Re-checked inside the write transaction (like every other preflight
382
+ // condition): an engine may have claimed the run while the summary gate
383
+ // above was awaiting its LLM judge.
384
+ assertLeaseAllowsSpineAdvance(run, input.leaseHolder);
222
385
  const existing = repo.getStep(run.id, input.stepId);
223
386
  if (!existing) {
224
387
  throw new NotFoundError(`Step "${input.stepId}" was not found in workflow run ${run.id}.`);
@@ -261,10 +424,16 @@ export async function completeWorkflowStep(input) {
261
424
  };
262
425
  });
263
426
  const detail = buildWorkflowRunDetail(updatedRun, refreshedSteps);
427
+ // #11: emit `workflow_step_completed` ONLY for a genuine `completed`
428
+ // transition; every other non-pending status (failed/skipped/blocked)
429
+ // carries the honest `workflow_step_updated` name. The status is ALWAYS
430
+ // in metadata so consumers never infer it from the event name. Raw `notes`
431
+ // are workflow/model-authored content — an event-stream prompt-injection
432
+ // surface — and never enter the events log; they live on the step row only.
264
433
  appendEvent({
265
- eventType: "workflow_step_completed",
434
+ eventType: input.status === "completed" ? "workflow_step_completed" : "workflow_step_updated",
266
435
  ref: detail.run.workflowRef,
267
- metadata: { runId: input.runId, stepId: input.stepId, notes: input.notes },
436
+ metadata: { runId: input.runId, stepId: input.stepId, status: input.status },
268
437
  });
269
438
  if (detail.run.status === "completed") {
270
439
  appendEvent({ eventType: "workflow_finished", ref: detail.run.workflowRef, metadata: { runId: input.runId } });
@@ -287,7 +456,7 @@ async function resolveRunSpecifier(repo, specifier, params) {
287
456
  if (parsed.type !== "workflow") {
288
457
  throw new UsageError(`Expected a workflow ref or workflow run id, got "${specifier}".`);
289
458
  }
290
- const ref = `${parsed.origin ? `${parsed.origin}//` : ""}workflow:${parsed.name}`;
459
+ const ref = `${parsed.origin ? `${parsed.origin}//` : ""}workflow:${canonicalizeWorkflowName(parsed.name)}`;
291
460
  const scopeKey = getCurrentWorkflowScopeKey();
292
461
  const active = repo.getActiveRunRowForScope(ref, scopeKey);
293
462
  if (active) {
@@ -299,115 +468,6 @@ async function resolveRunSpecifier(repo, specifier, params) {
299
468
  const started = await startWorkflowRun(ref, params ?? {});
300
469
  return { run: readWorkflowRun(repo, started.run.id), autoStarted: true };
301
470
  }
302
- async function loadWorkflowAsset(ref) {
303
- const parsed = parseAssetRef(ref);
304
- if (parsed.type !== "workflow") {
305
- throw new UsageError(`Expected a workflow ref (workflow:<name>), got "${ref}".`);
306
- }
307
- const config = loadConfig();
308
- const allSources = resolveSourceEntries(undefined, config);
309
- const searchSources = resolveSourcesForOrigin(parsed.origin, allSources);
310
- let assetPath;
311
- let sourcePath;
312
- for (const source of searchSources) {
313
- try {
314
- assetPath = await resolveAssetPath(source.path, "workflow", parsed.name);
315
- sourcePath = source.path;
316
- break;
317
- }
318
- catch {
319
- /* continue */
320
- }
321
- }
322
- if (!assetPath) {
323
- throw new NotFoundError(`Workflow not found for ref: workflow:${parsed.name}`);
324
- }
325
- const resolvedSourcePath = sourcePath ?? config.stashDir ?? assetPath;
326
- const fullRef = `${parsed.origin ? `${parsed.origin}//` : ""}workflow:${parsed.name}`;
327
- const cached = readWorkflowDocumentFromIndex(resolvedSourcePath, fullRef);
328
- const document = cached ?? loadWorkflowDocumentFromDisk(assetPath);
329
- return projectAsset(document, fullRef, assetPath, resolvedSourcePath);
330
- }
331
- function loadWorkflowDocumentFromDisk(assetPath) {
332
- const content = fs.readFileSync(assetPath, "utf8");
333
- const result = parseWorkflow(content, { path: assetPath });
334
- if (!result.ok) {
335
- throw new UsageError(formatWorkflowErrors(assetPath, result.errors));
336
- }
337
- return result.document;
338
- }
339
- function readWorkflowDocumentFromIndex(sourcePath, ref) {
340
- const dbPath = getDbPath();
341
- if (!fs.existsSync(dbPath))
342
- return null;
343
- const db = openExistingDatabase(dbPath);
344
- try {
345
- const parsed = parseAssetRef(ref);
346
- const entryKey = `${sourcePath}:${parsed.type}:${parsed.name}`;
347
- const row = db
348
- .prepare(`SELECT wd.document_json AS document_json
349
- FROM workflow_documents wd
350
- JOIN entries e ON e.id = wd.entry_id
351
- WHERE e.entry_type = 'workflow' AND e.entry_key = ?
352
- LIMIT 1`)
353
- .get(entryKey);
354
- if (!row)
355
- return null;
356
- try {
357
- return JSON.parse(row.document_json);
358
- }
359
- catch {
360
- return null;
361
- }
362
- }
363
- finally {
364
- closeDatabase(db);
365
- }
366
- }
367
- function projectAsset(doc, ref, assetPath, sourcePath) {
368
- return {
369
- ref,
370
- path: assetPath,
371
- sourcePath,
372
- title: doc.title,
373
- ...(doc.parameters
374
- ? {
375
- parameters: doc.parameters.map((p) => ({
376
- name: p.name,
377
- ...(p.description ? { description: p.description } : {}),
378
- })),
379
- }
380
- : {}),
381
- steps: doc.steps.map((s) => ({
382
- id: s.id,
383
- title: s.title,
384
- instructions: s.instructions.text,
385
- ...(s.completionCriteria ? { completionCriteria: s.completionCriteria.map((c) => c.text) } : {}),
386
- sequenceIndex: s.sequenceIndex,
387
- })),
388
- };
389
- }
390
- function resolveWorkflowEntryId(sourcePath, ref) {
391
- const dbPath = getDbPath();
392
- if (!fs.existsSync(dbPath))
393
- return null;
394
- const db = openExistingDatabase(dbPath);
395
- try {
396
- const parsed = parseAssetRef(ref);
397
- const entryKey = `${sourcePath}:${parsed.type}:${parsed.name}`;
398
- const row = db
399
- .prepare(`SELECT id
400
- FROM entries
401
- WHERE entry_type = 'workflow'
402
- AND entry_key = ?
403
- LIMIT 1`)
404
- .get(entryKey);
405
- return row?.id ?? null;
406
- }
407
- finally {
408
- closeDatabase(db);
409
- }
410
- }
411
471
  function readWorkflowRun(repo, runId) {
412
472
  const run = repo.getRunById(runId);
413
473
  if (!run) {
@@ -419,6 +479,16 @@ function readWorkflowRunSteps(repo, runId) {
419
479
  return repo.getStepsForRun(runId);
420
480
  }
421
481
  function buildWorkflowRunDetail(run, steps) {
482
+ // Review M1: `workflow status` (and every other detail-shaped response) now
483
+ // evaluates the check-in, not just `workflow next`. Pure timestamp check —
484
+ // no background thread (see checkin.ts).
485
+ const checkin = evaluateCheckin({
486
+ status: run.status,
487
+ updatedAt: run.updated_at,
488
+ checkinArmedAt: run.checkin_armed_at,
489
+ agentHarness: run.agent_harness,
490
+ agentSessionId: run.agent_session_id,
491
+ });
422
492
  return {
423
493
  run: toWorkflowRunSummary(run),
424
494
  workflow: {
@@ -426,6 +496,7 @@ function buildWorkflowRunDetail(run, steps) {
426
496
  title: run.workflow_title,
427
497
  steps: steps.map(toWorkflowRunStepState),
428
498
  },
499
+ ...(checkin ? { checkin } : {}),
429
500
  };
430
501
  }
431
502
  function toWorkflowRunSummary(run) {
@@ -443,8 +514,31 @@ function toWorkflowRunSummary(run) {
443
514
  params: parseJsonObject(run.params_json),
444
515
  agentHarness: run.agent_harness ?? null,
445
516
  agentSessionId: run.agent_session_id ?? null,
517
+ // Surface the engine lease (holder id + expiry — never workflow-authored
518
+ // content) so `workflow next`/`status` show who is driving the run.
519
+ ...(run.engine_lease_holder && run.engine_lease_until
520
+ ? { engineLease: { holder: run.engine_lease_holder, until: run.engine_lease_until } }
521
+ : {}),
446
522
  };
447
523
  }
524
+ /**
525
+ * Single-driver enforcement (R2 run lease): while a LIVE (unexpired) engine
526
+ * lease is held, only the holding engine may advance the gate spine. Manual
527
+ * `akm workflow complete` (no `leaseHolder`) — or a stale engine invocation
528
+ * whose lease was claimed by another — is refused with the holder + expiry.
529
+ * An EXPIRED lease never blocks: the engine that held it is presumed dead.
530
+ */
531
+ function assertLeaseAllowsSpineAdvance(run, leaseHolder) {
532
+ if (!run.engine_lease_holder || !run.engine_lease_until)
533
+ return;
534
+ if (leaseHolder === run.engine_lease_holder)
535
+ return;
536
+ if (run.engine_lease_until < new Date().toISOString())
537
+ return; // expired ⇒ claimable, not live
538
+ throw new UsageError(`Workflow run ${run.id} is being driven by engine ${run.engine_lease_holder} ` +
539
+ `(run lease expires ${run.engine_lease_until}). The engine owns the step spine while it runs — ` +
540
+ `wait for it to finish or for the lease to expire before advancing steps manually.`);
541
+ }
448
542
  function toWorkflowRunStepState(step) {
449
543
  return {
450
544
  id: step.step_id,
@@ -485,17 +579,18 @@ function deriveRunState(steps) {
485
579
  .at(-1);
486
580
  return { status: "completed", currentStepId: null, completedAt: completedAt ?? null };
487
581
  }
488
- /**
489
582
  /**
490
583
  * Build the default summary-validation judge from the configured LLM, or return
491
584
  * `null` when no LLM is configured (gate is then skipped — fail-open). Lazily
492
585
  * imports the client/config so the workflow engine has no hard LLM dependency.
586
+ *
587
+ * Exported for the engine loop (`exec/run-workflow.ts`), which wraps the judge
588
+ * to journal engine-driven gate evaluations as unit rows (addendum R2).
493
589
  */
494
- function buildDefaultSummaryJudge() {
590
+ export function buildDefaultSummaryJudge() {
495
591
  let llm;
496
592
  try {
497
593
  const config = loadConfig();
498
- const { getDefaultLlmConfig } = require("../../core/config/config");
499
594
  llm = getDefaultLlmConfig(config);
500
595
  }
501
596
  catch {
@@ -505,7 +600,7 @@ function buildDefaultSummaryJudge() {
505
600
  return null;
506
601
  const resolved = llm;
507
602
  return async ({ system, user }) => {
508
- const { chatCompletion } = require("../../llm/client");
603
+ const { chatCompletion } = await import("../../llm/client.js");
509
604
  return chatCompletion(resolved, [
510
605
  { role: "system", content: system },
511
606
  { role: "user", content: user },
@@ -0,0 +1,45 @@
1
+ // This Source Code Form is subject to the terms of the Mozilla Public
2
+ // License, v. 2.0. If a copy of the MPL was not distributed with this
3
+ // file, You can obtain one at https://mozilla.org/MPL/2.0/.
4
+ import { GATE_EVALUATION_PHASE } from "../exec/step-work.js";
5
+ /**
6
+ * Default staleness window. A unit claimed `running` whose last heartbeat (or,
7
+ * absent any heartbeat, its first claim) is older than this is surfaced as
8
+ * stale. Matches the run-level {@link ../runtime/checkin.CHECKIN_STALL_MS}.
9
+ */
10
+ export const UNIT_STALE_MS = 90_000;
11
+ function parseIso(value) {
12
+ if (!value)
13
+ return null;
14
+ const ms = Date.parse(value);
15
+ return Number.isNaN(ms) ? null : ms;
16
+ }
17
+ /**
18
+ * Pure stale-unit evaluator. Returns every `running` DISPATCH unit whose last
19
+ * heartbeat (`last_checkin_at`, else the first-claim `started_at`) is older than
20
+ * `staleMs`. Gate-evaluation rows (`phase = "gate"`) are excluded — they are
21
+ * synchronous engine judge calls, never driver-claimed work. A running row with
22
+ * no usable timestamp at all is treated as stale (a claim we can no longer age).
23
+ * Deterministic in `now` — free of timer flakiness.
24
+ */
25
+ export function evaluateStaleUnits(rows, now = Date.now(), staleMs = UNIT_STALE_MS) {
26
+ const stale = [];
27
+ for (const row of rows) {
28
+ if (row.status !== "running")
29
+ continue;
30
+ if (row.phase === GATE_EVALUATION_PHASE)
31
+ continue;
32
+ const lastSeenAt = row.last_checkin_at ?? row.started_at;
33
+ const lastSeen = parseIso(lastSeenAt);
34
+ const idleMs = lastSeen === null ? Number.POSITIVE_INFINITY : now - lastSeen;
35
+ if (idleMs < staleMs)
36
+ continue;
37
+ stale.push({
38
+ unitId: row.unit_id,
39
+ stepId: row.step_id,
40
+ idleMs,
41
+ lastSeenAt: lastSeenAt ?? null,
42
+ });
43
+ }
44
+ return stale;
45
+ }