@khoavu882/doflow 2.0.0-beta.5 → 2.0.0

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 (471) hide show
  1. package/README.md +12 -11
  2. package/bin/doflow.js +21 -11
  3. package/core/.antigravity-plugin/plugin.json +1 -2
  4. package/core/.claude-plugin/plugin.json +1 -1
  5. package/core/.codex-plugin/plugin.json +1 -1
  6. package/core/.plugin/plugin.json +1 -1
  7. package/core/harnesses/claude/hooks/lib.sh +43 -24
  8. package/core/harnesses/claude/hooks/permission-audit.sh +3 -0
  9. package/core/harnesses/claude/hooks/post-compact.sh +35 -32
  10. package/core/harnesses/claude/hooks/post-edit-lint.sh +2 -0
  11. package/core/harnesses/claude/hooks/post-shell-observe.sh +48 -0
  12. package/core/harnesses/claude/hooks/post-tool-failure.sh +3 -0
  13. package/core/harnesses/claude/hooks/pre-compact.sh +23 -27
  14. package/core/harnesses/claude/hooks/session-end.sh +23 -10
  15. package/core/harnesses/claude/hooks/skill-config-audit.sh +3 -0
  16. package/core/harnesses/claude/settings/settings.json +18 -0
  17. package/core/harnesses/claude/statusline/doflow-statusline.sh +1 -0
  18. package/core/harnesses/codex/agents/core-implementer.toml +1 -0
  19. package/core/harnesses/codex/agents/delivery-lead.toml +10 -0
  20. package/core/harnesses/codex/agents/quality-guardian.toml +1 -1
  21. package/core/harnesses/codex/agents/research-writer.toml +1 -1
  22. package/core/harnesses/codex/agents/reviewer.toml +10 -0
  23. package/core/harnesses/codex/agents/spec-analyst.toml +1 -1
  24. package/core/harnesses/codex/agents/system-architect.toml +1 -1
  25. package/core/harnesses/codex/hooks/lib.sh +44 -25
  26. package/core/harnesses/codex/hooks/post-edit-lint.impl.sh +3 -1
  27. package/core/harnesses/codex/hooks/pre-compact.impl.sh +29 -26
  28. package/core/harnesses/codex/hooks/session-end.impl.sh +23 -10
  29. package/core/harnesses/codex/hooks/subagent-audit.sh +1 -0
  30. package/core/harnesses/copilot/hooks/doflow-lib.sh +93 -0
  31. package/core/harnesses/copilot/hooks/doflow-mcp-tool-guard.sh +9 -0
  32. package/core/harnesses/copilot/hooks/doflow-pre-bash-guard.sh +9 -0
  33. package/core/harnesses/copilot/hooks/doflow-pre-implement-gate.sh +9 -0
  34. package/core/harnesses/copilot/hooks/doflow-session-start.sh +9 -0
  35. package/core/harnesses/copilot/hooks/doflow-stop-check.sh +9 -0
  36. package/core/harnesses/copilot/hooks/doflow-subagent-start.sh +9 -0
  37. package/core/harnesses/copilot/hooks/doflow-subagent-stop.sh +9 -0
  38. package/core/harnesses/copilot/hooks/doflow.json +57 -0
  39. package/core/harnesses/kiro/agents/doflow.json +1 -1
  40. package/core/harnesses/kiro/hooks/lib.sh +26 -6
  41. package/core/harnesses/kiro/hooks/mcp-tool-guard.sh +1 -0
  42. package/core/harnesses/kiro/hooks/pre-bash-guard.sh +1 -0
  43. package/core/harnesses/kiro/hooks/pre-implement-gate.sh +1 -0
  44. package/core/harnesses/kiro/hooks/session-start.sh +1 -0
  45. package/core/harnesses/kiro/hooks/stop-check.sh +1 -0
  46. package/core/harnesses/pi/extensions/doflow-guard.js +8 -0
  47. package/core/harnesses/shared/hooks/ENVELOPE_MIGRATION.md +0 -1
  48. package/core/harnesses/shared/hooks/bridge/bridge.sh +102 -9
  49. package/core/harnesses/shared/hooks/bridge/envelope.jq +87 -18
  50. package/core/harnesses/shared/hooks/bridge/observe-model.sh +75 -0
  51. package/core/harnesses/shared/hooks/bridge/respond.sh +10 -2
  52. package/core/harnesses/shared/hooks/policies/blocked-patterns.conf +28 -14
  53. package/core/harnesses/shared/hooks/policies/capture-failure.sh +48 -15
  54. package/core/harnesses/shared/hooks/policies/compact-text.sh +46 -0
  55. package/core/harnesses/shared/hooks/policies/contract.sh +3 -0
  56. package/core/harnesses/shared/hooks/policies/deny-json.sh +2 -11
  57. package/core/harnesses/shared/hooks/policies/emission-rules.sh +37 -0
  58. package/core/harnesses/shared/hooks/policies/emission.jq +209 -0
  59. package/core/harnesses/shared/hooks/policies/emission.sh +383 -0
  60. package/core/harnesses/shared/hooks/policies/identity.sh +177 -0
  61. package/core/harnesses/shared/hooks/policies/lib.sh +63 -40
  62. package/core/harnesses/shared/hooks/policies/mcp-tool-guard.sh +3 -2
  63. package/core/harnesses/shared/hooks/policies/pre-bash-guard.sh +213 -12
  64. package/core/harnesses/shared/hooks/policies/pre-implementation-gate.sh +114 -38
  65. package/core/harnesses/shared/hooks/policies/session-context.sh +30 -1
  66. package/core/harnesses/shared/hooks/policies/stop-check.sh +20 -9
  67. package/core/harnesses/shared/hooks/policies/subagent-audit.sh +5 -2
  68. package/core/harnesses/shared/hooks/policies/summary-store.sh +347 -0
  69. package/core/harnesses/shared/hooks/policies/user-prompt-submit.sh +60 -155
  70. package/core/harnesses/shared/hooks/stream-hook-runner.js +47 -8
  71. package/core/harnesses/shared/locator/doflow-run +2 -3
  72. package/core/registry/advice.json +3 -3
  73. package/core/registry/assets.json +55 -82
  74. package/core/registry/capabilities.json +12 -0
  75. package/core/registry/contracts.json +58 -31
  76. package/core/registry/contribution-points.json +32 -0
  77. package/core/registry/gates.json +32 -0
  78. package/core/registry/guidance-caps.json +40 -0
  79. package/core/registry/harnesses.json +1442 -363
  80. package/core/registry/lifecycle.json +485 -304
  81. package/core/registry/mcp.json +5 -1
  82. package/core/registry/models.json +7 -1
  83. package/core/registry/package-templates/README.md +19 -0
  84. package/core/registry/package-templates/agent/files/agent.md +9 -0
  85. package/core/registry/package-templates/agent/template.json +4 -0
  86. package/core/registry/package-templates/capability-provider/files/provider.json +9 -0
  87. package/core/registry/package-templates/capability-provider/template.json +11 -0
  88. package/core/registry/package-templates/debate-preset/files/preset.json +26 -0
  89. package/core/registry/package-templates/debate-preset/template.json +4 -0
  90. package/core/registry/package-templates/review-preset/files/preset.json +30 -0
  91. package/core/registry/package-templates/review-preset/template.json +4 -0
  92. package/core/registry/package-templates/skill/files/SKILL.md +8 -0
  93. package/core/registry/package-templates/skill/template.json +4 -0
  94. package/core/registry/package-templates/worker/files/worker.json +92 -0
  95. package/core/registry/package-templates/worker/template.json +11 -0
  96. package/core/registry/packages/doflow/doflow-package.json +15 -0
  97. package/core/registry/packages/doflow/presets/debate-3.json +26 -0
  98. package/core/registry/protocols.json +61 -0
  99. package/core/registry/routes.json +17 -0
  100. package/core/registry/rules.json +431 -0
  101. package/core/registry/settings.json +276 -0
  102. package/core/registry/storage.json +679 -31
  103. package/core/registry/telemetry.json +236 -0
  104. package/core/registry/transcripts.json +142 -0
  105. package/core/registry/verification.json +76 -1
  106. package/core/registry/workflows.json +55 -0
  107. package/core/shared/agent-specs/core-implementer.md +3 -0
  108. package/core/shared/agent-specs/delivery-lead.md +68 -0
  109. package/core/shared/agent-specs/quality-guardian.md +5 -1
  110. package/core/shared/agent-specs/research-writer.md +10 -3
  111. package/core/shared/agent-specs/reviewer.md +53 -0
  112. package/core/shared/agent-specs/spec-analyst.md +5 -6
  113. package/core/shared/agent-specs/system-architect.md +5 -1
  114. package/core/shared/guidance/DOFLOW_CORE.md +1 -5
  115. package/core/shared/guidance/modes/MODE_DeepResearch.md +1 -1
  116. package/core/shared/guidance/modes/MODE_Orchestration.md +4 -3
  117. package/core/shared/guidance/modes/MODE_Task_Management.md +14 -14
  118. package/core/shared/guidance/pointers/codex.md +1 -4
  119. package/core/shared/guidance/references/ARTIFACT_FORMAT.md +30 -16
  120. package/core/shared/guidance/references/ARTIFACT_VOICE.md +3 -2
  121. package/core/shared/guidance/references/CONSTITUTION_BASE.md +17 -15
  122. package/core/shared/guidance/references/DIAGRAM_GUIDE.md +1 -1
  123. package/core/shared/guidance/references/DOFLOW_CHAIN.md +17 -19
  124. package/core/shared/guidance/references/EVIDENCE_LEDGER.md +7 -8
  125. package/core/shared/guidance/references/MODEL_SELECTION.md +5 -3
  126. package/core/shared/guidance/references/ORCHESTRATION.md +1 -2
  127. package/core/shared/guidance/rules/RULES.md +45 -0
  128. package/core/shared/scripts/doflow/bash/do-args.sh +37 -0
  129. package/core/shared/scripts/doflow/bash/do-exec-paths.sh +4 -0
  130. package/core/shared/scripts/doflow/bash/do-git-state.sh +15 -4
  131. package/core/shared/scripts/doflow/bash/do-parallel-check.sh +4 -0
  132. package/core/shared/scripts/doflow/bash/do-paths.sh +40 -2
  133. package/core/shared/scripts/doflow/bash/do-prereqs.sh +2 -1
  134. package/core/shared/scripts/doflow/bash/do-review-package.sh +4 -0
  135. package/core/shared/scripts/doflow/bash/do-task-brief.sh +58 -16
  136. package/core/shared/scripts/doflow/bash/render-audit.sh +26 -2
  137. package/core/shared/scripts/doflow/bash/render-diagrams.sh +1 -0
  138. package/core/shared/scripts/doflow/bash/render-puml.sh +2 -1
  139. package/core/shared/scripts/doflow/bash/validate-artifacts.sh +48 -4
  140. package/core/shared/scripts/doflow/bin/doflow-run +149 -34
  141. package/core/shared/skills/do/SKILL.md +12 -10
  142. package/core/shared/skills/do/references/advice_calls.md +26 -0
  143. package/core/shared/skills/do/references/pm_routing.md +1 -2
  144. package/core/shared/skills/do/references/program_mode.md +31 -0
  145. package/core/shared/skills/do-brainstorm/SKILL.md +20 -74
  146. package/core/shared/skills/do-brainstorm/references/handoff_branches.md +35 -0
  147. package/core/shared/skills/do-brainstorm/references/intent_seed.md +21 -0
  148. package/core/shared/skills/do-brainstorm/references/round_log.md +17 -0
  149. package/core/shared/skills/do-code-review/SKILL.md +41 -144
  150. package/core/shared/skills/do-code-review/content-types/markdown.md +24 -2
  151. package/core/shared/skills/do-code-review/references/tools.md +90 -0
  152. package/core/shared/skills/do-code-review/scripts/doc_quality_checker.py +549 -7
  153. package/core/shared/skills/do-design/SKILL.md +18 -17
  154. package/core/shared/skills/do-diagnose/SKILL.md +14 -4
  155. package/core/shared/skills/do-document/SKILL.md +11 -1
  156. package/core/shared/skills/do-document/references/deep_research.md +1 -2
  157. package/core/shared/skills/do-document/references/embedded-research.md +18 -6
  158. package/core/shared/skills/do-document/references/implementation-flow.md +5 -6
  159. package/core/shared/skills/do-execute-plan/SKILL.md +15 -58
  160. package/core/shared/skills/do-execute-plan/references/handoff_record.md +7 -2
  161. package/core/shared/skills/do-execute-plan/references/parallel_dispatch.md +13 -0
  162. package/core/shared/skills/do-execute-plan/references/readiness_gate.md +26 -1
  163. package/core/shared/skills/do-execute-plan/references/run_position.md +13 -1
  164. package/core/shared/skills/do-execute-plan/references/scaffold.md +17 -9
  165. package/core/shared/skills/do-execute-plan/references/state_checkpoint.md +11 -0
  166. package/core/shared/skills/do-execute-plan/references/task_attempts.md +78 -24
  167. package/core/shared/skills/do-flow/SKILL.md +8 -9
  168. package/core/shared/skills/do-flow/references/task_classes.md +2 -2
  169. package/core/shared/skills/do-git/SKILL.md +4 -0
  170. package/core/shared/skills/do-implement/SKILL.md +4 -7
  171. package/core/shared/skills/do-plan/SKILL.md +26 -15
  172. package/core/shared/skills/do-test/SKILL.md +7 -4
  173. package/core/shared/templates/doflow/constitution-template.md +9 -3
  174. package/core/shared/templates/doflow/data-model-template.md +1 -1
  175. package/core/shared/templates/doflow/design-template.md +113 -16
  176. package/core/shared/templates/doflow/plan-template.md +11 -10
  177. package/core/shared/templates/doflow/question-log-template.md +14 -2
  178. package/core/shared/templates/doflow/requirement-template.md +1 -1
  179. package/core/worker-evidence/antigravity/checks.json +14 -0
  180. package/core/worker-evidence/codex/2026-10-18-cli-check.json +107 -0
  181. package/core/worker-evidence/codex/2026-10-18-live-run-edit.json +73 -0
  182. package/core/worker-evidence/codex/2026-10-18-live-run-read-only.json +62 -0
  183. package/core/worker-evidence/codex/2026-10-24-cli-check.json +126 -0
  184. package/core/worker-evidence/codex/checks.json +38 -0
  185. package/package.json +10 -6
  186. package/src/adapters/antigravity/index.js +143 -66
  187. package/src/adapters/claude/index.js +24 -4
  188. package/src/adapters/codex/agents.js +25 -15
  189. package/src/adapters/codex/index.js +48 -13
  190. package/src/adapters/codex/native-projection.js +6 -2
  191. package/src/adapters/copilot/index.js +58 -23
  192. package/src/adapters/copy-tree.js +290 -44
  193. package/src/adapters/guidance-manifest.js +71 -0
  194. package/src/adapters/guidance-pointer.js +19 -0
  195. package/src/adapters/hook-commands.js +6 -10
  196. package/src/adapters/index.js +1 -0
  197. package/src/adapters/kiro/index.js +23 -12
  198. package/src/adapters/opencode/index.js +8 -8
  199. package/src/adapters/pi/hooks.js +2 -1
  200. package/src/adapters/pi/index.js +14 -7
  201. package/src/adapters/resolve-source.js +26 -2
  202. package/src/cli/commands/install.js +2 -1
  203. package/src/cli/commands/reconcile.js +53 -9
  204. package/src/cli/commands/remove.js +2 -2
  205. package/src/cli/commands/rollback.js +6 -3
  206. package/src/cli/commands/self-update.js +18 -9
  207. package/src/cli/commands/status.js +21 -2
  208. package/src/cli/commands/ui.js +99 -0
  209. package/src/cli/commands/update.js +2 -1
  210. package/src/cli/index.js +322 -29
  211. package/src/cli/runtime-commands.js +209 -57
  212. package/src/cli/shared.js +0 -2
  213. package/src/helper/git.js +1 -1
  214. package/src/helper/guardrails.js +1 -1
  215. package/src/helper/marker-merge.js +31 -13
  216. package/src/helper/package-ids.js +37 -0
  217. package/src/helper/semver.js +94 -0
  218. package/src/install/backup.js +105 -15
  219. package/src/install/context.js +1 -1
  220. package/src/install/targets.js +31 -16
  221. package/src/lifecycle/coverage.js +20 -3
  222. package/src/lifecycle/hook-tier.js +160 -0
  223. package/src/lifecycle/index.js +412 -78
  224. package/src/lifecycle/retired-harnesses.js +318 -0
  225. package/src/lifecycle/view.js +35 -15
  226. package/src/registry/index.js +551 -26
  227. package/src/registry/worker-ids.js +45 -0
  228. package/src/release/build-package.js +0 -3
  229. package/src/release/docs-blocks.js +492 -0
  230. package/src/release/preflight.js +425 -0
  231. package/src/release/rehearse.js +401 -0
  232. package/src/release/release-workflow.js +733 -0
  233. package/src/release/sync-plugin-versions.js +43 -7
  234. package/src/runtime/advice/bench-gate.js +25 -0
  235. package/src/runtime/advice/cli.js +59 -16
  236. package/src/runtime/advice/client.js +32 -9
  237. package/src/runtime/advice/config.js +98 -133
  238. package/src/runtime/advice/doctor.js +1 -1
  239. package/src/runtime/advice/elevation.js +97 -0
  240. package/src/runtime/advice/endpoint-guard.js +9 -3
  241. package/src/runtime/advice/mode.js +37 -0
  242. package/src/runtime/advice/record.js +18 -5
  243. package/src/runtime/advice/registry.js +4 -1
  244. package/src/runtime/advice/shadow-config.js +229 -0
  245. package/src/runtime/advice/shadow-dispatcher.js +276 -0
  246. package/src/runtime/brief-rules.js +81 -0
  247. package/src/runtime/brief-tools.js +111 -34
  248. package/src/runtime/candidate.js +20 -11
  249. package/src/runtime/capabilities/executor.js +450 -0
  250. package/src/runtime/capabilities/loopback.js +41 -0
  251. package/src/runtime/capabilities/sanitizer.js +88 -0
  252. package/src/runtime/capability-router.js +135 -3
  253. package/src/runtime/checkouts.js +73 -11
  254. package/src/runtime/claims.js +13 -7
  255. package/src/runtime/cli.js +196 -19
  256. package/src/runtime/config-home.js +42 -0
  257. package/src/runtime/context/arbitrate.js +767 -0
  258. package/src/runtime/context/delivery-store.js +4 -6
  259. package/src/runtime/context/emission-rules.js +118 -0
  260. package/src/runtime/context/identity.js +227 -0
  261. package/src/runtime/context/primary-command.js +117 -0
  262. package/src/runtime/context/primary.js +344 -0
  263. package/src/runtime/context/summary-store.js +655 -0
  264. package/src/runtime/context-pack.js +154 -78
  265. package/src/runtime/decision-register.js +13 -4
  266. package/src/runtime/dispatch/admission.js +392 -0
  267. package/src/runtime/dispatch/attempt-end.js +274 -0
  268. package/src/runtime/dispatch/command.js +628 -0
  269. package/src/runtime/dispatch/controls.js +904 -0
  270. package/src/runtime/dispatch/envelope.js +276 -0
  271. package/src/runtime/dispatch/grants.js +310 -0
  272. package/src/runtime/dispatch/integrate.js +617 -0
  273. package/src/runtime/dispatch/loop.js +1835 -0
  274. package/src/runtime/dispatch/store.js +352 -0
  275. package/src/runtime/dispatch/verify-run.js +201 -0
  276. package/src/runtime/evidence-ledger.js +34 -7
  277. package/src/runtime/failure/capture.js +3 -1
  278. package/src/runtime/failure/cli.js +25 -9
  279. package/src/runtime/failure/home.js +57 -30
  280. package/src/runtime/failure/store.js +7 -2
  281. package/src/runtime/feature-pointer.js +174 -0
  282. package/src/runtime/feature-resolve.js +13 -2
  283. package/src/runtime/fleet/command.js +77 -0
  284. package/src/runtime/fleet/projects.js +178 -0
  285. package/src/runtime/fleet/tree.js +383 -0
  286. package/src/runtime/freshness.js +33 -3
  287. package/src/runtime/git-memo.js +46 -0
  288. package/src/runtime/guidance/ablation.js +427 -0
  289. package/src/runtime/health.js +565 -17
  290. package/src/runtime/history-compactor.js +13 -3
  291. package/src/runtime/implementation-gate.js +277 -22
  292. package/src/runtime/inventory/coverage.js +1 -1
  293. package/src/runtime/inventory/index.js +1 -1
  294. package/src/runtime/inventory/precedence.js +2 -2
  295. package/src/runtime/inventory/siblings.js +6 -4
  296. package/src/runtime/knowledge/code-corpus.js +277 -0
  297. package/src/runtime/knowledge/code-lexical.js +130 -0
  298. package/src/runtime/knowledge/code-retrieval.js +307 -0
  299. package/src/runtime/knowledge/code-validation.js +106 -0
  300. package/src/runtime/knowledge/gp3-gate.js +74 -0
  301. package/src/runtime/knowledge/graph-adapter.js +288 -0
  302. package/src/runtime/knowledge/graph-fallback.js +394 -0
  303. package/src/runtime/knowledge/hybrid.js +136 -8
  304. package/src/runtime/knowledge/retrieval.js +34 -1
  305. package/src/runtime/knowledge/rtk-compress.js +77 -0
  306. package/src/runtime/knowledge/semble-bridge.py +106 -0
  307. package/src/runtime/knowledge/semble-lifecycle.js +114 -0
  308. package/src/runtime/knowledge/semble-provider.js +365 -0
  309. package/src/runtime/lifecycle/cli.js +16 -6
  310. package/src/runtime/lifecycle/event-store.js +8 -6
  311. package/src/runtime/lifecycle/followup.js +6 -1
  312. package/src/runtime/lifecycle/overview.js +24 -1
  313. package/src/runtime/lifecycle/report-store.js +8 -10
  314. package/src/runtime/lifecycle/retention.js +95 -31
  315. package/src/runtime/lifecycle/root.js +9 -1
  316. package/src/runtime/lifecycle/status.js +34 -17
  317. package/src/runtime/lifecycle/store-upkeep.js +56 -7
  318. package/src/runtime/mask.js +134 -7
  319. package/src/runtime/mcp-consultations.js +40 -6
  320. package/src/runtime/model-router.js +79 -7
  321. package/src/runtime/models/catalogue.js +191 -0
  322. package/src/runtime/models/command.js +153 -0
  323. package/src/runtime/off-words.js +12 -0
  324. package/src/runtime/outcome.js +2 -2
  325. package/src/runtime/packages/command.js +1009 -0
  326. package/src/runtime/packages/contributions.js +116 -0
  327. package/src/runtime/packages/digest.js +132 -0
  328. package/src/runtime/packages/fetch.js +182 -0
  329. package/src/runtime/packages/grants.js +192 -0
  330. package/src/runtime/packages/index-file.js +134 -0
  331. package/src/runtime/packages/instruction.js +90 -0
  332. package/src/runtime/packages/manifest.js +331 -0
  333. package/src/runtime/packages/points.js +477 -0
  334. package/src/runtime/packages/programs.js +216 -0
  335. package/src/runtime/packages/scaffold.js +277 -0
  336. package/src/runtime/packages/source.js +90 -0
  337. package/src/runtime/packages/state.js +164 -0
  338. package/src/runtime/packages/store.js +449 -0
  339. package/src/runtime/packages/tar.js +106 -0
  340. package/src/runtime/packages/view.js +406 -0
  341. package/src/runtime/plan-check.js +59 -0
  342. package/src/runtime/plan-content.js +324 -0
  343. package/src/runtime/policy-init.js +118 -0
  344. package/src/runtime/profiles/doctor.js +96 -0
  345. package/src/runtime/profiles/resolve.js +244 -0
  346. package/src/runtime/profiles/rollback.js +254 -0
  347. package/src/runtime/profiles/sampler.js +41 -0
  348. package/src/runtime/readiness-record.js +16 -11
  349. package/src/runtime/readiness.js +46 -5
  350. package/src/runtime/research-request.js +50 -25
  351. package/src/runtime/retrieval-plan.js +10 -30
  352. package/src/runtime/retrieval-record.js +75 -5
  353. package/src/runtime/review/command.js +246 -0
  354. package/src/runtime/review/dominance.js +68 -0
  355. package/src/runtime/review/findings.js +434 -0
  356. package/src/runtime/review/identity.js +255 -0
  357. package/src/runtime/review/protocol-command.js +109 -0
  358. package/src/runtime/review/protocols.js +405 -0
  359. package/src/runtime/review/resolve-protocol.js +302 -0
  360. package/src/runtime/review/start.js +530 -0
  361. package/src/runtime/roadmap/facts.js +479 -0
  362. package/src/runtime/roadmap/gates.js +270 -0
  363. package/src/runtime/roadmap/loop.js +1636 -0
  364. package/src/runtime/roadmap/merge.js +381 -0
  365. package/src/runtime/roadmap/rows.js +1113 -0
  366. package/src/runtime/roadmap/seed.js +951 -0
  367. package/src/runtime/rules-report.js +336 -0
  368. package/src/runtime/scaffold/artifacts.js +60 -9
  369. package/src/runtime/scaffold/generate.js +21 -9
  370. package/src/runtime/settings/autopilot.js +106 -0
  371. package/src/runtime/settings/cli.js +280 -0
  372. package/src/runtime/settings/config-write.js +314 -0
  373. package/src/runtime/settings/files.js +271 -0
  374. package/src/runtime/settings/legacy.js +154 -0
  375. package/src/runtime/settings/lexical.js +56 -0
  376. package/src/runtime/settings/prepared-run.js +269 -0
  377. package/src/runtime/settings/project-layers.js +118 -0
  378. package/src/runtime/settings/refs.js +288 -0
  379. package/src/runtime/settings/registry.js +553 -0
  380. package/src/runtime/settings/resolve.js +713 -0
  381. package/src/runtime/settings/shared.js +37 -0
  382. package/src/runtime/settings/worker-billing.js +107 -0
  383. package/src/runtime/settings/write.js +269 -0
  384. package/src/runtime/slot-command.js +78 -0
  385. package/src/runtime/slots.js +671 -0
  386. package/src/runtime/source-version.js +1 -0
  387. package/src/runtime/state-root.js +217 -0
  388. package/src/runtime/storage/cli.js +12 -5
  389. package/src/runtime/storage/engine.js +9 -2
  390. package/src/runtime/storage/registry.js +14 -1
  391. package/src/runtime/storage/roots.js +66 -16
  392. package/src/runtime/storage/rules.js +82 -10
  393. package/src/runtime/storage/upkeep.js +10 -2
  394. package/src/runtime/storage/work-status.js +245 -26
  395. package/src/runtime/task-attempts.js +589 -69
  396. package/src/runtime/task-classifier.js +47 -11
  397. package/src/runtime/task-graph.js +373 -0
  398. package/src/runtime/task-run.js +778 -0
  399. package/src/runtime/task-scope.js +176 -55
  400. package/src/runtime/task-sources.js +147 -0
  401. package/src/runtime/task-state.js +374 -64
  402. package/src/runtime/team/admission.js +301 -0
  403. package/src/runtime/team/envelope.js +324 -0
  404. package/src/runtime/team/review-guard.js +156 -0
  405. package/src/runtime/trace/analyze.js +427 -0
  406. package/src/runtime/trace/baseline.js +770 -0
  407. package/src/runtime/trace/ledger.js +63 -140
  408. package/src/runtime/trace/otlp-export.js +568 -0
  409. package/src/runtime/trace/otlp-map.js +481 -0
  410. package/src/runtime/trace/spans.js +745 -0
  411. package/src/runtime/trace/telemetry.js +225 -0
  412. package/src/runtime/trace/views.js +1 -0
  413. package/src/runtime/transcripts/command.js +98 -0
  414. package/src/runtime/transcripts/cursor.js +326 -0
  415. package/src/runtime/transcripts/read.js +126 -0
  416. package/src/runtime/transcripts/readers/claude-jsonl.js +74 -0
  417. package/src/runtime/transcripts/readers/codex-rollout.js +70 -0
  418. package/src/runtime/transcripts/readers/copilot-events.js +90 -0
  419. package/src/runtime/transcripts/readers/kiro-jsonl.js +77 -0
  420. package/src/runtime/transcripts/readers/pi-jsonl.js +59 -0
  421. package/src/runtime/transcripts/registry.js +161 -0
  422. package/src/runtime/transcripts/stats.js +161 -0
  423. package/src/runtime/verification/check-key.js +628 -0
  424. package/src/runtime/verification/contract-runner.js +546 -11
  425. package/src/runtime/verification/engine.js +499 -9
  426. package/src/runtime/verification/impact.js +617 -0
  427. package/src/runtime/verification/inflight.js +205 -0
  428. package/src/runtime/verification/registry.js +176 -0
  429. package/src/runtime/verification/reuse.js +184 -0
  430. package/src/runtime/verification/telemetry-emit.js +254 -0
  431. package/src/runtime/verification/test-selection.js +67 -7
  432. package/src/runtime/worker/active.js +90 -0
  433. package/src/runtime/worker/cancel.js +222 -0
  434. package/src/runtime/worker/catalogue.js +122 -0
  435. package/src/runtime/worker/command.js +677 -0
  436. package/src/runtime/worker/evidence.js +288 -0
  437. package/src/runtime/worker/launch.js +288 -0
  438. package/src/runtime/worker/models.js +137 -0
  439. package/src/runtime/worker/parsers.js +218 -0
  440. package/src/runtime/worker/probes/index.js +706 -0
  441. package/src/runtime/worker/process-tree.js +415 -0
  442. package/src/runtime/worker/quota.js +43 -0
  443. package/src/runtime/worker/readiness.js +713 -0
  444. package/src/runtime/worker/run-store.js +166 -0
  445. package/src/runtime/worker/supervisor.js +541 -0
  446. package/src/runtime/worker/worktree-check.js +252 -0
  447. package/src/runtime/workflow-engine.js +5 -1
  448. package/src/runtime/workflow-orchestrator.js +578 -63
  449. package/src/runtime/worktree.js +149 -22
  450. package/src/state/index.js +1 -1
  451. package/src/state/lockfile.js +64 -10
  452. package/src/state/run-lock.js +1 -1
  453. package/types/node-commonjs.d.ts +106 -7
  454. package/core/harnesses/gemini/hooks/hooks.json +0 -77
  455. package/core/harnesses/gemini/hooks/lib.sh +0 -302
  456. package/core/harnesses/gemini/hooks/mcp-tool-guard.sh +0 -10
  457. package/core/harnesses/gemini/hooks/post-edit-lint.sh +0 -39
  458. package/core/harnesses/gemini/hooks/pre-bash-guard.sh +0 -10
  459. package/core/harnesses/gemini/hooks/pre-compact.sh +0 -38
  460. package/core/harnesses/gemini/hooks/pre-implement-gate.sh +0 -10
  461. package/core/harnesses/gemini/hooks/session-end.impl.sh +0 -89
  462. package/core/harnesses/gemini/hooks/session-end.sh +0 -5
  463. package/core/harnesses/gemini/hooks/session-start.sh +0 -7
  464. package/core/harnesses/gemini/settings/adapter-defaults.json +0 -4
  465. package/core/shared/guidance/FLAGS.md +0 -25
  466. package/core/shared/guidance/rules/RULE_01_SAFETY.md +0 -25
  467. package/core/shared/guidance/rules/RULE_02_WORKFLOW.md +0 -23
  468. package/core/shared/guidance/rules/RULE_03_QUALITY.md +0 -14
  469. package/core/shared/guidance/rules/RULE_04_QUESTIONS.md +0 -46
  470. package/src/adapters/gemini/hooks.js +0 -251
  471. package/src/adapters/gemini/index.js +0 -429
@@ -0,0 +1,617 @@
1
+ 'use strict';
2
+
3
+ // Feature 082 B.4 (IC-016, IC-017, IC-018, IC-020, IC-021; DEC-004, DEC-014, DEC-022, DEC-023,
4
+ // DEC-036, DEC-049, DEC-050): integration.
5
+ //
6
+ // Worktrees. Every dispatch worktree lives under `<root>/.doflow/state/dispatch-worktrees/<id>`,
7
+ // made through `WorktreeManager` with a branch prefix per kind, a 120 s bound and an env without
8
+ // the repository variables (IC-016). The integration worktree (`p-` id, branch
9
+ // `doflow/<slug>/<p-id>`) is never removed here; it is handed to the user (DEC-004).
10
+ //
11
+ // Attempt commit (IC-017). The changed paths of an attempt worktree, filtered of the base pointer
12
+ // and of DoFlow's declared state stores, are staged into a temporary index on the attempt base and
13
+ // written as one commit `C` with parent `b` by `commit-tree`. The worktree's own index, branch and
14
+ // files are never touched. Identity is set in the child env only; git's settings are never read for
15
+ // it or changed, and no hook-skipping flag exists here (DEC-036).
16
+ //
17
+ // Integration (IC-018). One dispatch per call, under the store's lease: `cherry-pick --no-commit`
18
+ // of `C` on the integration HEAD, `write-tree`, `commit-tree` on the pre-integration head, then
19
+ // `update-ref` naming that head as the expected old value, so a concurrent move of the branch fails
20
+ // the update instead of being overwritten (DEC-050). Verification follows on the integrated HEAD
21
+ // (IC-019), then B.3's end rule finishes or releases the 080 attempt inside the dispatch lock.
22
+ // Undo is `reset --hard` to the pre-integration head, run only while the integration branch is
23
+ // checked out there and points at a head this integration made (IC-020).
24
+ //
25
+ // Locks. Each locked step is one `mutateDispatch`; its decide callback makes only the in-process
26
+ // 080 calls of `attempt-end.ts`. Git, the verification spawn and worktree removal all run between
27
+ // lock sections (IC-006). The 080 ledger is read before a section that checks staleness.
28
+ //
29
+ // This module runs git only inside DoFlow-owned worktrees and on refs under the two DoFlow branch
30
+ // prefixes; the worktree list of `root` is changed only through `WorktreeManager`.
31
+
32
+ const fs = require('node:fs');
33
+ const path = require('node:path');
34
+ const { spawnSync } = require('node:child_process');
35
+ const { WorktreeManager, BASE_FILE } = require('../worktree.js');
36
+ const { storeRules, withoutDeclaredStores } = require('../worker/worktree-check.js');
37
+ const { procStart } = require('../worker/process-tree.js');
38
+ const { readTaskState } = require('../task-state.js');
39
+ const { storeFileFor, mutateDispatch, readDispatch, transitionDispatch, setRunState, DispatchStoreError } = require('./store.js');
40
+ const { REPO_ENV, runVerification } = require('./verify-run.js');
41
+ const { endAttempt, completeAttempt, releaseAttempt, usageFor } = require('./attempt-end.js');
42
+
43
+ const WORKTREES_DIR = path.join('.doflow', 'state', 'dispatch-worktrees');
44
+ /** Bound on a git call that writes (IC-016). */
45
+ const WRITE_TIMEOUT_MS = 120000;
46
+ /** Bound on a read-only git call (IC-016). */
47
+ const READ_TIMEOUT_MS = 10000;
48
+ /** The identity every commit this module makes carries, set in the child env only (DEC-036). */
49
+ const DISPATCH_IDENTITY = Object.freeze({
50
+ GIT_AUTHOR_NAME: 'DoFlow dispatch',
51
+ GIT_AUTHOR_EMAIL: 'dispatch@doflow.invalid',
52
+ GIT_COMMITTER_NAME: 'DoFlow dispatch',
53
+ GIT_COMMITTER_EMAIL: 'dispatch@doflow.invalid',
54
+ });
55
+ const TEMP_INDEX = 'index.doflow-dispatch';
56
+ const SHA = /^[0-9a-f]{40,64}$/;
57
+ /** Dispositions whose worktree is removed once its worker cleanup is verified, or it had no worker (IC-021). */
58
+ const RELEASED_ENDS = Object.freeze(['cancelled', 'taken-over', 'stale', 'interrupted', 'released']);
59
+ /** Run states after which a failed attempt's kept worktree is removed (IC-021). */
60
+ const RUN_ENDS = Object.freeze(['completed', 'cancelled']);
61
+
62
+
63
+
64
+ /** A git failure in this module's contract: `reason` a fixed token, `exitCode` git's exit status. */
65
+ class IntegrationError extends Error {
66
+
67
+
68
+
69
+ constructor(reason , exitCode = null) {
70
+ super(`integration-error: ${reason}${exitCode === null ? '' : ` (git exit ${exitCode})`}`);
71
+ this.name = 'IntegrationError';
72
+ this.code = 'integration-error';
73
+ this.reason = reason;
74
+ this.exitCode = exitCode;
75
+ }
76
+ }
77
+
78
+ /** `env` without the variables that would point git at another repository (081 `REPO_ENV`). */
79
+ function gitEnv(env = process.env, extra = {}) {
80
+ const out = { ...env };
81
+ for (const key of REPO_ENV) delete out[key];
82
+ return { ...out, ...extra };
83
+ }
84
+
85
+ function readEnv(env ) {
86
+ return gitEnv(env, { GIT_OPTIONAL_LOCKS: '0' });
87
+ }
88
+
89
+ /** One bounded `git -C <cwd>` call. Never throws: `{ ok, status, stdout }`, `status` the exit code
90
+ * as a string, else the signal or spawn error code. */
91
+ function runGit(cwd , args , { env, timeout, input } ) {
92
+ const r = spawnSync('git', ['-C', cwd, ...args], {
93
+ env, timeout, encoding: 'utf8', input: input ?? '', maxBuffer: 64 * 1024 * 1024, stdio: ['pipe', 'pipe', 'pipe'],
94
+ });
95
+ const status = r.status ?? r.signal ?? (r.error )?.code ?? 'unknown';
96
+ return { ok: r.status === 0 && !r.error, status: String(status), stdout: r.stdout || '' };
97
+ }
98
+
99
+ const readGit = (cwd , args , env ) => runGit(cwd, args, { env: readEnv(env), timeout: READ_TIMEOUT_MS });
100
+
101
+ /** stdout of a git call that must succeed, trimmed, else an IntegrationError naming `reason`. */
102
+ function must(res , reason ) {
103
+ if (!res.ok) throw new IntegrationError(reason, res.status);
104
+ return res.stdout.trim();
105
+ }
106
+
107
+ function sha(res , reason ) {
108
+ const out = must(res, reason);
109
+ if (!SHA.test(out)) throw new IntegrationError(reason, res.status);
110
+ return out;
111
+ }
112
+
113
+ // ── worktrees (IC-016) ───────────────────────────────────────────────────────────────────────────
114
+
115
+ function worktreesDir(root ) {
116
+ return path.join(root, WORKTREES_DIR);
117
+ }
118
+
119
+ /** The manager for one worktree kind: `doflow/<slug>/` for integration, `doflow-attempt/<slug>/` for
120
+ * attempt and child worktrees. */
121
+ function managerFor(root , slug , kind , env = process.env) {
122
+ const prefix = kind === 'integration' ? `doflow/${slug}/` : `doflow-attempt/${slug}/`;
123
+ return new WorktreeManager(root, worktreesDir(root), { branchPrefix: prefix, timeoutMs: WRITE_TIMEOUT_MS, env: gitEnv(env) });
124
+ }
125
+
126
+ function created(manager , id , base ) {
127
+ if (typeof base !== 'string' || !SHA.test(base)) throw new DispatchStoreError('usage', `worktree base '${base}' is not a commit id`);
128
+ const worktree = manager.create(id, base);
129
+ return { worktree, branch: manager.branchFor(id), base };
130
+ }
131
+
132
+ /** IC-016: the run's integration worktree `p-<id>` on `doflow/<slug>/<p-id>`, cut from `base`
133
+ * (the caller's HEAD at start; read from `root` when omitted). `head` starts at the base. */
134
+ function createIntegrationWorktree({ root, slug, dispatchRunId, base = null, env = process.env } ) {
135
+ const from = base ?? sha(readGit(root, ['rev-parse', '--verify', '-q', 'HEAD^{commit}'], env), 'head-unreadable');
136
+ const made = created(managerFor(root, slug, 'integration', env), dispatchRunId, from);
137
+ return { ...made, head: from };
138
+ }
139
+
140
+ /** IC-016: the attempt worktree `d-<id>` on `doflow-attempt/<slug>/<d-id>`, cut from `base` (the
141
+ * integration HEAD recorded in the admitted slot). */
142
+ function createAttemptWorktree({ root, slug, dispatchId, base, env = process.env } ) {
143
+ return created(managerFor(root, slug, 'attempt', env), dispatchId, base);
144
+ }
145
+
146
+ /** IC-016: the child worktree `<d-id>.c<n>`, cut from the granter's attempt base. */
147
+ function createChildWorktree({ root, slug, dispatchId, n, base, env = process.env } ) {
148
+ if (!Number.isInteger(n) || n < 1) throw new DispatchStoreError('usage', `child number '${n}' is not a positive whole number`);
149
+ return created(managerFor(root, slug, 'attempt', env), `${dispatchId}.c${n}`, base);
150
+ }
151
+
152
+ // ── change lists ─────────────────────────────────────────────────────────────────────────────────
153
+
154
+ /** IC-011: `git diff --name-status -z -M a b` in `cwd` as `[{ path, change, from }]`, `change` the
155
+ * status letter without its score. */
156
+ function diffNameStatus(a , b , { cwd, env = process.env } ) {
157
+ const out = must(readGit(cwd, ['diff', '--name-status', '-z', '-M', a, b], env), 'diff-failed');
158
+ const fields = out.split('\0').filter((f) => f !== '');
159
+ const changes = [];
160
+ let i = 0;
161
+ while (i < fields.length) {
162
+ const change = fields[i][0];
163
+ const renamed = change === 'R' || change === 'C';
164
+ changes.splice(changes.length, 0, renamed
165
+ ? { path: fields[i + 2], change, from: fields[i + 1] }
166
+ : { path: fields[i + 1], change, from: null });
167
+ i += renamed ? 3 : 2;
168
+ }
169
+ return changes;
170
+ }
171
+
172
+ function nulList(text ) {
173
+ return text.split('\0').filter((f) => f !== '');
174
+ }
175
+
176
+ /** IC-017 step 1: changed and untracked paths of `worktree` against `base`, less the base pointer
177
+ * and every path under a declared DoFlow state store. */
178
+ function attemptPaths(worktree , base , env ) {
179
+ const tracked = nulList(must(readGit(worktree, ['diff', '--name-only', '-z', '--no-renames', base], env), 'diff-failed'));
180
+ const untracked = nulList(must(readGit(worktree, ['ls-files', '--others', '--exclude-standard', '-z'], env), 'ls-files-failed'));
181
+ const rules = storeRules();
182
+ if (rules === null) throw new IntegrationError('storage-registry-unreadable');
183
+ const entries = {};
184
+ for (const rel of [...tracked, ...untracked]) if (rel !== BASE_FILE) entries[rel] = '';
185
+ return Object.keys(withoutDeclaredStores(entries, rules)).sort();
186
+ }
187
+
188
+ /**
189
+ * IC-017: one commit `C` with the single parent `base` holding the attempt's changes, or
190
+ * `commit: null` for an empty change (DEC-049). `changes` is `diffNameStatus(base, C)`. Throws an
191
+ * IntegrationError for a git failure.
192
+ */
193
+ function attemptCommit({ worktree, base, dispatchId, task, token, env = process.env } ) {
194
+ if (typeof base !== 'string' || !SHA.test(base)) throw new DispatchStoreError('usage', `attempt base '${base}' is not a commit id`);
195
+ const paths = attemptPaths(worktree, base, env);
196
+ if (paths.length === 0) return { base, commit: null, changes: [] };
197
+
198
+ const indexPath = path.resolve(worktree, must(readGit(worktree, ['rev-parse', '--git-path', TEMP_INDEX], env), 'git-path-failed'));
199
+ const writeEnv = gitEnv(env, { ...DISPATCH_IDENTITY, GIT_INDEX_FILE: indexPath, GIT_LITERAL_PATHSPECS: '1' });
200
+ const write = (args , reason , input ) => must(runGit(worktree, args, { env: writeEnv, timeout: WRITE_TIMEOUT_MS, input }), reason);
201
+ let commit = null;
202
+ try {
203
+ fs.rmSync(indexPath, { force: true });
204
+ write(['read-tree', base], 'read-tree-failed');
205
+ write(['add', '-A', '--pathspec-from-file=-', '--pathspec-file-nul'], 'add-failed', `${paths.join('\0')}\0`);
206
+ const tree = write(['write-tree'], 'write-tree-failed');
207
+ const baseTree = sha(readGit(worktree, ['rev-parse', `${base}^{tree}`], env), 'rev-parse-failed');
208
+ if (tree !== baseTree) {
209
+ commit = write(['commit-tree', tree, '-p', base, '-m', `dispatch ${dispatchId} task ${task} token ${token}`], 'commit-tree-failed');
210
+ if (!SHA.test(commit)) throw new IntegrationError('commit-tree-failed');
211
+ }
212
+ } finally {
213
+ fs.rmSync(indexPath, { force: true });
214
+ }
215
+ if (commit === null) return { base, commit: null, changes: [] };
216
+ return { base, commit, changes: diffNameStatus(base, commit, { cwd: worktree, env }) };
217
+ }
218
+
219
+ // ── integration worktree checks and undo (IC-018 step 2, IC-020) ─────────────────────────────────
220
+
221
+ /** IC-018 step 2: the integration worktree is on its branch at `preHead` and lists nothing but the
222
+ * base pointer. `{ clean, reason, paths }`. */
223
+ function integrationClean({ worktree, branch, preHead, env = process.env } ) {
224
+ const ref = readGit(worktree, ['symbolic-ref', '-q', 'HEAD'], env);
225
+ if (!ref.ok || ref.stdout.trim() !== `refs/heads/${branch}`) return { clean: false, reason: 'not-integration-branch', paths: 0 };
226
+ const head = readGit(worktree, ['rev-parse', '--verify', '-q', 'HEAD'], env);
227
+ if (!head.ok || head.stdout.trim() !== preHead) return { clean: false, reason: 'head-moved', paths: 0 };
228
+ const status = readGit(worktree, ['--no-optional-locks', 'status', '--porcelain=v1', '-z', '--untracked-files=all'], env);
229
+ if (!status.ok) return { clean: false, reason: 'status-failed', paths: 0 };
230
+ const listed = nulList(status.stdout).filter((f) => f.length > 3 && f.slice(3) !== BASE_FILE);
231
+ return listed.length ? { clean: false, reason: 'dirty', paths: listed.length } : { clean: true, reason: null, paths: 0 };
232
+ }
233
+
234
+ /**
235
+ * IC-020: resets the integration worktree to `preHead`. Nothing runs unless HEAD is the run's
236
+ * integration branch (`not-integration-branch`) and points at `preHead` or at `pickedHead`, the
237
+ * commit this integration made (`head-moved`: another writer moved it, and resetting would
238
+ * overwrite that move). After the reset, HEAD must equal `preHead` and status list nothing but the
239
+ * base pointer, else `undo-failed`. `{ ok, ran, reason, exitCode }`.
240
+ */
241
+ function undo({ worktree, branch, preHead, pickedHead = null, env = process.env } ) {
242
+ const ref = readGit(worktree, ['symbolic-ref', '-q', 'HEAD'], env);
243
+ if (!ref.ok || ref.stdout.trim() !== `refs/heads/${branch}`) return { ok: false, ran: false, reason: 'not-integration-branch', exitCode: null };
244
+ const head = readGit(worktree, ['rev-parse', '--verify', '-q', 'HEAD'], env).stdout.trim();
245
+ if (head !== preHead && (pickedHead === null || head !== pickedHead)) return { ok: false, ran: false, reason: 'head-moved', exitCode: null };
246
+ const reset = runGit(worktree, ['reset', '-q', '--hard', preHead], { env: gitEnv(env), timeout: WRITE_TIMEOUT_MS });
247
+ const after = integrationClean({ worktree, branch, preHead, env });
248
+ if (!reset.ok || !after.clean) return { ok: false, ran: true, reason: 'undo-failed', exitCode: reset.ok ? null : reset.status };
249
+ return { ok: true, ran: true, reason: null, exitCode: null };
250
+ }
251
+
252
+ // ── staleness (IC-023, read before the lock) ─────────────────────────────────────────────────────
253
+
254
+ /** IC-023's stale reason for `dispatch` against an 080 ledger read just before, or null. */
255
+ function staleReason(dispatch , ledger , runKey ) {
256
+ const entry = ledger && ledger.tasks ? ledger.tasks[dispatch.task] : null;
257
+ const attempts = entry && Array.isArray(entry.attempts) ? entry.attempts : [];
258
+ const attempt = attempts.find((a) => a.token === dispatch.token);
259
+ if (!attempt) return 'token-mismatch';
260
+ if (attempt.state === 'cancelled') return 'attempt-cancelled';
261
+ if (attempt.state !== 'active') return 'attempt-ended';
262
+ const run = ledger.runs && runKey && Object.hasOwn(ledger.runs, runKey) ? ledger.runs[runKey] : null;
263
+ if (run && Number.isInteger(dispatch.epoch) && run.epoch !== dispatch.epoch) return 'epoch';
264
+ return null;
265
+ }
266
+
267
+ function readLedger(ctx ) {
268
+ return readTaskState(ctx.fsImpl || fs, ctx.ledgerFile) || { tasks: {}, runs: {} };
269
+ }
270
+
271
+ /** Whether `ledger` holds `(task, token)`'s 080 attempt `active`. */
272
+ function activeAttempt(ledger , task , token ) {
273
+ const entry = ledger && ledger.tasks ? ledger.tasks[task] : null;
274
+ return Boolean(entry && Array.isArray(entry.attempts) && entry.attempts.some((a ) => a.token === token && a.state === 'active'));
275
+ }
276
+
277
+ // ── IC-018 ───────────────────────────────────────────────────────────────────────────────────────
278
+
279
+ const INTEGRABLE_RUN_STATES = Object.freeze(['running', 'blocked']);
280
+ /** IC-030 step 3: the run states a cancel has marked. */
281
+ const CANCEL_MARKED = Object.freeze(['cancelling', 'cancelled']);
282
+
283
+ function oldestCollected(store ) {
284
+ const ready = (Object.values(store.dispatches) ).filter((d) => d.phase === 'collected' && d.outcome === 'offered');
285
+ ready.sort((a, b) => String(a.collectedAt ?? '').localeCompare(String(b.collectedAt ?? ''))
286
+ || String(a.admittedAt ?? '').localeCompare(String(b.admittedAt ?? '')) || a.dispatchId.localeCompare(b.dispatchId));
287
+ return ready[0] ?? null;
288
+ }
289
+
290
+ /** Records an integration gap and blocks the run, unless the run holds a state another writer owns
291
+ * (a cancel's, a lost driver's `resume-required`, IC-009): the gap is still recorded, the state is not moved. */
292
+ function blockRun(store , gap ) {
293
+ setRunState(store, 'blocked');
294
+ store.run.integrationGap = { cause: 'no-progress', blocks: [], ...gap };
295
+ }
296
+
297
+ /** Ends a stale dispatch in the decide callback: release while its attempt is active (the epoch
298
+ * case), else end it with no 080 call, the attempt having ended outside the loop. */
299
+ function endStale(store , dispatch , reason , ctx , now ) {
300
+ const usage = usageFor(dispatch, null, []);
301
+ if (reason === 'epoch') {
302
+ return releaseAttempt({ store, dispatchId: dispatch.dispatchId, reason: `stale:${reason}`, disposition: 'stale', usage, ctx });
303
+ }
304
+ Object.assign(dispatch, { phase: 'ended', disposition: 'stale', staleReason: reason, endedAt: now.toISOString(), usage });
305
+ return { write: true, result: { ended: true, dispatchId: dispatch.dispatchId, disposition: 'stale' } };
306
+ }
307
+
308
+ /**
309
+ * IC-018, steps 1 to 6 (`integrateNext` without the cleanup), for the oldest offered `collected` dispatch of `slug`'s store under `root`.
310
+ * `ctx` is B.3's 080 context (`ledgerFile`, `roots`, `slug`, `env`, `cwd`, `fsImpl`, `now`); `env`
311
+ * is the environment git and the verification inherit, less the repository variables. `holder` is
312
+ * the lease holder `{ pid, start }` (this process by default). `beforeUpdateRef` is a test seam
313
+ * called between `commit-tree` and `update-ref`, and `afterLease` one called between steps 1 and 2;
314
+ * the loop passes neither.
315
+ *
316
+ * Resolves `{ outcome, dispatchId, task, token, ... }`, `outcome` one of `none`, `not-running`,
317
+ * `lease-held`, `stale`, `integration-dirty`, `conflict`, `integration-error`, `verification`,
318
+ * `finish-refused`, `completed`, or `lastError` when an 080 call threw or refused.
319
+ */
320
+ async function integrateOnce({
321
+ root, slug, ctx, env = process.env, holder = null, now = () => new Date(), beforeUpdateRef = null, afterLease = null,
322
+ storeFile = null, verify = runVerification,
323
+ } ) {
324
+ const file = storeFile ?? storeFileFor(root, slug);
325
+ const lockCtx = () => ({ ...ctx, now: now() });
326
+
327
+ // Step 1, in the lock, on the 080 ledger read just before.
328
+ const ledger = readLedger(ctx);
329
+ const lessee = holder ?? { pid: process.pid, start: procStart(process.pid) };
330
+ const taken = mutateDispatch(file, (store ) => {
331
+ const run = store.run;
332
+ if (!run || !run.integration || !INTEGRABLE_RUN_STATES.includes(run.state)) return { write: false, result: { outcome: 'not-running', state: run ? run.state : null } };
333
+ if (store.lease) return { write: false, result: { outcome: 'lease-held', dispatchId: store.lease.dispatchId } };
334
+ const dispatch = oldestCollected(store);
335
+ if (!dispatch) return { write: false, result: { outcome: 'none' } };
336
+ const reason = staleReason(dispatch, ledger, dispatch.parentRunKey ?? run.runKey ?? null);
337
+ if (reason) {
338
+ const ended = endStale(store, dispatch, reason, lockCtx(), now());
339
+ return { write: ended.write, result: { outcome: ended.result.ended ? 'stale' : 'lastError', staleReason: reason, dispatchId: dispatch.dispatchId, end: ended.result } };
340
+ }
341
+ store.lease = { holder: { pid: lessee.pid, start: lessee.start ?? null }, dispatchId: dispatch.dispatchId, preHead: run.integration.head, takenAt: now().toISOString() };
342
+ dispatch.phase = 'integrating';
343
+ return {
344
+ write: true,
345
+ result: {
346
+ outcome: 'taken', dispatch: structuredClone(dispatch), integration: structuredClone(run.integration),
347
+ verify: run.verify ? structuredClone(run.verify) : null,
348
+ dispatchRunId: run.dispatchRunId ?? null,
349
+ },
350
+ };
351
+ });
352
+ if (taken.outcome !== 'taken') return taken;
353
+
354
+ const { dispatch, integration, verify: declared } = taken;
355
+ const { dispatchId, task, token } = dispatch;
356
+ const preHead = integration.head;
357
+ const where = { worktree: integration.worktree, branch: integration.branch, env };
358
+ const base = { dispatchId, task, token };
359
+
360
+ /** One locked write that clears the lease after `apply(store, dispatch)`, only while the lease is
361
+ * still this dispatch's and the dispatch still `integrating` (RT-11). Otherwise a cancel or resume
362
+ * settled this integration meanwhile: only `evidence(store)` (the verification record) is written,
363
+ * so another lease is never cleared and an ended dispatch never edited. */
364
+ const settle = (apply , evidence = null) => mutateDispatch(file, (store ) => {
365
+ const d = store.dispatches[dispatchId];
366
+ if (!store.lease || store.lease.dispatchId !== dispatchId || !d || d.phase !== 'integrating') {
367
+ const lost = { ...base, outcome: 'lease-lost', lease: store.lease ? store.lease.dispatchId : null, phase: d ? d.phase : null };
368
+ if (!evidence) return { write: false, result: lost };
369
+ evidence(store);
370
+ return { write: true, result: lost };
371
+ }
372
+ const result = apply(store, d);
373
+ store.lease = null;
374
+ return { write: true, result };
375
+ });
376
+ /** IC-020 undo, then one locked write releasing the attempt, ending it `released` and blocking the
377
+ * run `integration-error`. */
378
+ const releaseWithError = (reason , exitCode , pickedHead ) => {
379
+ const undone = undo({ ...where, preHead, pickedHead });
380
+ return settle((store, d) => {
381
+ const end = releaseAttempt({ store, dispatchId, reason: 'integration-error', usage: usageFor(d, null, []), ctx: lockCtx() });
382
+ // A release 080 threw or refused on a still-active attempt keeps `lastError` and the dispatch
383
+ // live, back at `collected` with the lease cleared (IC-024, RT-9); one refused because the
384
+ // attempt already ended outside the loop still ends the dispatch.
385
+ if (!end.result.ended && d.phase === 'integrating' && activeAttempt(readLedger(ctx), task, token)) d.phase = 'collected';
386
+ else if (d.phase !== 'ended') Object.assign(d, { phase: 'ended', disposition: 'released', endedAt: now().toISOString(), releaseReason: 'integration-error' });
387
+ blockRun(store, { task, kind: 'integration-error', reason, exitCode, ...(undone.ok ? {} : { undo: undone.reason, undoFailed: true }) });
388
+ return { ...base, outcome: 'integration-error', reason, exitCode, undo: undone, end: end.result };
389
+ });
390
+ };
391
+ /** After a failed 080 end (thrown or refused, `lastError` recorded), the dispatch goes back to
392
+ * `collected` so the next pass retries it (IC-024), unless no pass will: its attempt is no longer
393
+ * active (stale, IC-023) or a cancel marked the run (IC-030 step 6). Then the dispatch ends here,
394
+ * `stale` or `released`, so a cancelled run holds no non-ended dispatch. Read in the lock. */
395
+ const retryLater = (store , d , end = null) => {
396
+ if ((end && end.result.ended) || d.phase === 'ended') return;
397
+ const stale = staleReason(d, readLedger(ctx), d.parentRunKey ?? store.run.runKey ?? null);
398
+ if (stale) {
399
+ endStale(store, d, stale, lockCtx(), now());
400
+ if (d.phase === 'ended') return;
401
+ }
402
+ if (CANCEL_MARKED.includes(store.run.state)) {
403
+ const released = releaseAttempt({ store, dispatchId, reason: `cancelled: ${store.run.state}`, usage: usageFor(d, null, []), ctx: lockCtx() });
404
+ if (!released.result.ended && d.phase !== 'ended') Object.assign(d, { phase: 'ended', disposition: 'released', endedAt: now().toISOString(), releaseReason: 'run-cancelled' });
405
+ return;
406
+ }
407
+ d.phase = 'collected';
408
+ };
409
+
410
+ // RT-5: a throw from steps 2 to 6 (a git spawn, the verification, a store write) must not strand
411
+ // the lease under a driver that then ends cleanly. The catch undoes this integration and returns
412
+ // the dispatch to `collected` with the lease cleared, when HEAD allows it (IC-020), then rethrows;
413
+ // the pass records the throw and the run becomes `resume-required` (IC-009).
414
+ let pickedHead = null;
415
+ try {
416
+ // Step 2, outside: the integration worktree is on its branch at preHead and clean.
417
+ if (afterLease) await afterLease({ dispatchId, worktree: integration.worktree, preHead });
418
+ const check = integrationClean({ ...where, preHead });
419
+ if (!check.clean) {
420
+ return settle((store, d) => {
421
+ retryLater(store, d);
422
+ blockRun(store, { task, kind: 'integration-dirty', reason: check.reason, paths: check.paths });
423
+ return { ...base, outcome: 'integration-dirty', reason: check.reason, paths: check.paths };
424
+ });
425
+ }
426
+
427
+ // Steps 3 and 4, outside: the pick, unless the change is empty (DEC-049).
428
+ if (dispatch.commit) {
429
+ const pick = runGit(integration.worktree, ['cherry-pick', '--no-commit', dispatch.commit], { env: gitEnv(env, DISPATCH_IDENTITY), timeout: WRITE_TIMEOUT_MS });
430
+ if (!pick.ok) {
431
+ const conflicted = nulList(readGit(integration.worktree, ['diff', '--name-only', '--diff-filter=U', '-z'], env).stdout);
432
+ if (conflicted.length === 0) return releaseWithError('cherry-pick-failed', pick.status, null);
433
+ const undone = undo({ ...where, preHead });
434
+ return settle((store, d) => {
435
+ const end = endAttempt({ store, dispatchId, failureClass: 'conflict', detail: `${conflicted.length} conflicted path(s)`, usage: usageFor(d, null, []), ctx: lockCtx() });
436
+ retryLater(store, d, end);
437
+ if (!undone.ok) blockRun(store, { task, kind: 'integration-error', reason: undone.reason, exitCode: undone.exitCode, undoFailed: true });
438
+ return { ...base, outcome: 'conflict', conflicted: conflicted.length, undo: undone, end: end.result };
439
+ });
440
+ }
441
+ const identityEnv = gitEnv(env, DISPATCH_IDENTITY);
442
+ const tree = runGit(integration.worktree, ['write-tree'], { env: identityEnv, timeout: WRITE_TIMEOUT_MS });
443
+ const preTree = readGit(integration.worktree, ['rev-parse', `${preHead}^{tree}`], env);
444
+ if (!tree.ok || !preTree.ok) return releaseWithError('write-tree-failed', tree.ok ? preTree.status : tree.status, null);
445
+ if (tree.stdout.trim() !== preTree.stdout.trim()) {
446
+ const made = runGit(integration.worktree, ['commit-tree', tree.stdout.trim(), '-p', preHead, '-m', `${task}: dispatch ${dispatchId} token ${token}`], { env: identityEnv, timeout: WRITE_TIMEOUT_MS });
447
+ const commit = made.stdout.trim();
448
+ if (!made.ok || !SHA.test(commit)) return releaseWithError('commit-tree-failed', made.status, null);
449
+ if (beforeUpdateRef) await beforeUpdateRef({ worktree: integration.worktree, branch: integration.branch, preHead, commit });
450
+ const moved = runGit(integration.worktree, ['update-ref', `refs/heads/${integration.branch}`, commit, preHead], { env: identityEnv, timeout: WRITE_TIMEOUT_MS });
451
+ if (!moved.ok) return releaseWithError('update-ref-failed', moved.status, null);
452
+ pickedHead = commit;
453
+ mutateDispatch(file, (store ) => {
454
+ if (!store.lease || store.lease.dispatchId !== dispatchId) return { write: false, result: null };
455
+ store.lease.pickedHead = commit;
456
+ return { write: true, result: null };
457
+ });
458
+ }
459
+ }
460
+
461
+ // Step 5, outside: verification on the integration HEAD.
462
+ if (!declared || !Array.isArray(declared.argv)) return releaseWithError('verification-undeclared', null, pickedHead);
463
+ const verification = await verify({
464
+ root, slug, taskId: task, worktree: integration.worktree, argv: declared.argv, argvSha256: declared.argvSha256 ?? null,
465
+ seconds: declared.timeoutSeconds, env,
466
+ dispatchRunId: taken.dispatchRunId ?? null,
467
+ onSpawn: (proc ) => mutateDispatch(file, (store ) => {
468
+ store.run.verifying = proc;
469
+ return { write: true, result: null };
470
+ }),
471
+ });
472
+ const passed = verification.exitCode === 0 && verification.timedOut === false;
473
+ const record = (store ) => {
474
+ store.run.verifying = null;
475
+ store.run.verifications = [...(Array.isArray(store.run.verifications) ? store.run.verifications : []), verification];
476
+ };
477
+
478
+ // Step 6, in the lock.
479
+ if (!passed) {
480
+ const undone = undo({ ...where, preHead, pickedHead });
481
+ return settle((store, d) => {
482
+ record(store);
483
+ const end = endAttempt({ store, dispatchId, failureClass: 'verification', detail: verification.timedOut ? 'timed out' : `exit ${verification.exitCode ?? verification.error}`, usage: usageFor(d, null, []), evidenceId: verification.evidenceId, ctx: lockCtx() });
484
+ retryLater(store, d, end);
485
+ if (!undone.ok) blockRun(store, { task, kind: 'integration-error', reason: undone.reason, exitCode: undone.exitCode, undoFailed: true });
486
+ return { ...base, outcome: 'verification', verification, undo: undone, end: end.result };
487
+ }, record);
488
+ }
489
+
490
+ const after = readLedger(ctx);
491
+ const finished = mutateDispatch(file, (store ) => {
492
+ const d = store.dispatches[dispatchId];
493
+ record(store);
494
+ const stale = staleReason(d, after, d.parentRunKey ?? store.run.runKey ?? null);
495
+ const end = stale ? null : completeAttempt({ store, dispatchId, evidenceId: verification.evidenceId, usage: usageFor(d, null, []), integratedCommit: pickedHead, ctx: lockCtx() });
496
+ if (end && end.result.ended) {
497
+ store.run.integration.head = pickedHead ?? preHead;
498
+ store.run.integrationGap = null;
499
+ store.lease = null;
500
+ return { write: true, result: { ...base, outcome: 'completed', integratedCommit: pickedHead, head: pickedHead ?? preHead, verification, end: end.result } };
501
+ }
502
+ if (store.lease) store.lease.undoPending = true;
503
+ const why = stale ? `stale:${stale}` : (end.result.reason || end.result.outcome || 'refused');
504
+ return { write: true, result: { outcome: 'refused', why } };
505
+ });
506
+ if (finished.outcome === 'completed') return finished;
507
+
508
+ const undone = undo({ ...where, preHead, pickedHead });
509
+ return settle((store, d) => {
510
+ const reason = `finish-refused:${finished.why}`;
511
+ let end = null;
512
+ if (after.tasks?.[task]?.attempts?.find((a ) => a.token === token)?.state === 'active') {
513
+ end = releaseAttempt({ store, dispatchId, reason, usage: usageFor(d, null, []), ctx: lockCtx() }).result;
514
+ }
515
+ if (d.phase !== 'ended') Object.assign(d, { phase: 'ended', disposition: 'released', endedAt: now().toISOString(), releaseReason: reason });
516
+ if (!undone.ok) blockRun(store, { task, kind: 'integration-error', reason: undone.reason, exitCode: undone.exitCode, undoFailed: true });
517
+ return { ...base, outcome: 'finish-refused', reason, verification, undo: undone, end };
518
+ });
519
+ } catch (error) {
520
+ try {
521
+ const held = readDispatch(file).lease;
522
+ // RR-2: an attempt 080 already finished COMPLETED (a store write that threw after the finish
523
+ // applied) keeps its commit; the lease stays for IC-032's lease rule, which adopts it.
524
+ const attempt = readLedger(ctx).tasks?.[task]?.attempts?.find((a ) => a.token === token);
525
+ const completed = Boolean(attempt && attempt.state === 'finished' && attempt.result === 'COMPLETED');
526
+ if (held && held.dispatchId === dispatchId && !completed) {
527
+ const undone = undo({ ...where, preHead, pickedHead: held.pickedHead ?? pickedHead });
528
+ if (undone.ok) {
529
+ mutateDispatch(file, (store ) => {
530
+ if (!store.lease || store.lease.dispatchId !== dispatchId) return { write: false, result: null };
531
+ store.lease = null;
532
+ const d = store.dispatches[dispatchId];
533
+ if (d && d.phase === 'integrating') d.phase = 'collected';
534
+ return { write: true, result: null };
535
+ });
536
+ }
537
+ }
538
+ } catch {
539
+ // The lease stays for IC-032's lease rule; the original throw is the one reported.
540
+ }
541
+ throw error;
542
+ }
543
+ }
544
+
545
+ /**
546
+ * IC-018 for one dispatch (`integrateOnce`), then IC-021's cleanup rule for the dispatch it ended:
547
+ * a completed one's attempt worktree is removed, a failed one kept (`removal`).
548
+ */
549
+ async function integrateNext(options ) {
550
+ const result = await integrateOnce(options);
551
+ if (!result || typeof result.dispatchId !== 'string' || !result.outcome || ['lease-held', 'integration-dirty'].includes(result.outcome)) return result;
552
+ const file = options.storeFile ?? storeFileFor(options.root, options.slug);
553
+ const dispatch = readDispatch(file).dispatches[result.dispatchId];
554
+ if (!dispatch || dispatch.phase !== 'ended') return result;
555
+ const removal = await removeDispatchWorktree({ root: options.root, slug: options.slug, dispatchId: result.dispatchId, env: options.env, storeFile: file });
556
+ return { ...result, removal };
557
+ }
558
+
559
+ // ── cleanup (IC-021) ─────────────────────────────────────────────────────────────────────────────
560
+
561
+ /** IC-021's table: whether `dispatch`'s attempt worktree is removed now, and why it is kept if not. */
562
+ function removalDue(dispatch , run ) {
563
+ if (dispatch.phase !== 'ended') return { due: false, reason: 'not-ended' };
564
+ if (dispatch.removal && dispatch.removal.verified === true) return { due: false, reason: 'removed' };
565
+ // IC-021's last row covers every disposition: a worker run's worktree waits for verified cleanup (RT-8).
566
+ if (dispatch.workerRunId) {
567
+ const cleanup = dispatch.cleanup ? dispatch.cleanup.cleanup ?? dispatch.cleanup : null;
568
+ if (!cleanup || cleanup.status !== 'verified') return { due: false, reason: 'worker-cleanup-unverified' };
569
+ }
570
+ if (dispatch.disposition === 'completed') return { due: true, reason: null };
571
+ if (RELEASED_ENDS.includes(dispatch.disposition)) return { due: true, reason: null };
572
+ if (dispatch.disposition === 'failed') {
573
+ return run && RUN_ENDS.includes(run.state) ? { due: true, reason: null } : { due: false, reason: 'failed-kept' };
574
+ }
575
+ return { due: false, reason: 'unknown-disposition' };
576
+ }
577
+
578
+ /**
579
+ * IC-021: removes an ended dispatch's attempt worktree when the table says so, then stores the
580
+ * `Removal` add-only on the dispatch (`verified` may only move from false to true). The removal runs
581
+ * outside every lock. Resolves `{ removed, kept, reason, removal }`.
582
+ */
583
+ async function removeDispatchWorktree({ root, slug, dispatchId, env = process.env, storeFile = null } ) {
584
+ const file = storeFile ?? storeFileFor(root, slug);
585
+ const current = readDispatch(file);
586
+ const dispatch = current.dispatches[dispatchId];
587
+ if (!dispatch) return { removed: false, kept: false, reason: 'unknown-dispatch', removal: null };
588
+ const due = removalDue(dispatch, current.run);
589
+ if (!due.due) return { removed: false, kept: due.reason !== 'removed', reason: due.reason, removal: dispatch.removal ?? null };
590
+ const removal = managerFor(root, slug, 'attempt', env).removeVerified(dispatchId);
591
+ const stored = transitionDispatch(file, dispatchId, ['ended'], (d ) => {
592
+ if (!d.removal || (d.removal.verified === false && removal.verified)) d.removal = removal;
593
+ });
594
+ return { removed: removal.verified, kept: !removal.verified, reason: removal.verified ? null : 'removal-unverified', removal, stored: stored.applied };
595
+ }
596
+
597
+ module.exports = {
598
+ WORKTREES_DIR,
599
+ WRITE_TIMEOUT_MS,
600
+ READ_TIMEOUT_MS,
601
+ DISPATCH_IDENTITY,
602
+ IntegrationError,
603
+ gitEnv,
604
+ managerFor,
605
+ createIntegrationWorktree,
606
+ createAttemptWorktree,
607
+ createChildWorktree,
608
+ diffNameStatus,
609
+ attemptCommit,
610
+ integrationClean,
611
+ integrateNext,
612
+ undo,
613
+ staleReason,
614
+ activeAttempt,
615
+ removalDue,
616
+ removeDispatchWorktree,
617
+ };