@khoavu882/doflow 2.0.0-beta.4 → 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 (497) hide show
  1. package/README.md +12 -11
  2. package/bin/doflow.js +21 -11
  3. package/core/.antigravity-plugin/plugin.json +1 -3
  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/mcp-tool-guard.sh +7 -17
  9. package/core/harnesses/claude/hooks/permission-audit.sh +3 -0
  10. package/core/harnesses/claude/hooks/post-compact.sh +35 -32
  11. package/core/harnesses/claude/hooks/post-edit-lint.sh +2 -0
  12. package/core/harnesses/claude/hooks/post-shell-observe.sh +48 -0
  13. package/core/harnesses/claude/hooks/post-tool-failure.sh +3 -0
  14. package/core/harnesses/claude/hooks/pre-bash-guard.sh +7 -17
  15. package/core/harnesses/claude/hooks/pre-compact.sh +23 -27
  16. package/core/harnesses/claude/hooks/pre-implement-gate.sh +7 -18
  17. package/core/harnesses/claude/hooks/session-end.sh +23 -10
  18. package/core/harnesses/claude/hooks/session-start.sh +5 -3
  19. package/core/harnesses/claude/hooks/skill-config-audit.sh +3 -0
  20. package/core/harnesses/claude/hooks/stop-check.sh +5 -3
  21. package/core/harnesses/claude/hooks/subagent-audit.sh +5 -5
  22. package/core/harnesses/claude/hooks/user-prompt-submit.sh +5 -3
  23. package/core/harnesses/claude/settings/settings.json +18 -0
  24. package/core/harnesses/claude/statusline/doflow-statusline.sh +1 -0
  25. package/core/harnesses/codex/agents/core-implementer.toml +1 -0
  26. package/core/harnesses/codex/agents/delivery-lead.toml +10 -0
  27. package/core/harnesses/codex/agents/quality-guardian.toml +1 -1
  28. package/core/harnesses/codex/agents/research-writer.toml +1 -1
  29. package/core/harnesses/codex/agents/reviewer.toml +10 -0
  30. package/core/harnesses/codex/agents/spec-analyst.toml +1 -1
  31. package/core/harnesses/codex/agents/system-architect.toml +1 -1
  32. package/core/harnesses/codex/hooks/lib.sh +44 -25
  33. package/core/harnesses/codex/hooks/mcp-tool-guard.sh +7 -15
  34. package/core/harnesses/codex/hooks/post-edit-lint.impl.sh +3 -1
  35. package/core/harnesses/codex/hooks/pre-bash-guard.sh +7 -15
  36. package/core/harnesses/codex/hooks/pre-compact.impl.sh +29 -26
  37. package/core/harnesses/codex/hooks/pre-implement-gate.sh +7 -16
  38. package/core/harnesses/codex/hooks/session-end.impl.sh +23 -10
  39. package/core/harnesses/codex/hooks/session-start.sh +5 -3
  40. package/core/harnesses/codex/hooks/stop-check.sh +6 -5
  41. package/core/harnesses/codex/hooks/subagent-audit.sh +18 -6
  42. package/core/harnesses/codex/hooks/user-prompt-submit.sh +5 -4
  43. package/core/harnesses/copilot/hooks/doflow-lib.sh +93 -0
  44. package/core/harnesses/copilot/hooks/doflow-mcp-tool-guard.sh +9 -0
  45. package/core/harnesses/copilot/hooks/doflow-pre-bash-guard.sh +9 -0
  46. package/core/harnesses/copilot/hooks/doflow-pre-implement-gate.sh +9 -0
  47. package/core/harnesses/copilot/hooks/doflow-session-start.sh +9 -0
  48. package/core/harnesses/copilot/hooks/doflow-stop-check.sh +9 -0
  49. package/core/harnesses/copilot/hooks/doflow-subagent-start.sh +9 -0
  50. package/core/harnesses/copilot/hooks/doflow-subagent-stop.sh +9 -0
  51. package/core/harnesses/copilot/hooks/doflow.json +57 -0
  52. package/core/harnesses/kiro/agents/doflow.json +1 -1
  53. package/core/harnesses/kiro/hooks/lib.sh +26 -6
  54. package/core/harnesses/kiro/hooks/mcp-tool-guard.sh +21 -5
  55. package/core/harnesses/kiro/hooks/pre-bash-guard.sh +21 -5
  56. package/core/harnesses/kiro/hooks/pre-implement-gate.sh +21 -5
  57. package/core/harnesses/kiro/hooks/session-start.sh +12 -5
  58. package/core/harnesses/kiro/hooks/stop-check.sh +22 -30
  59. package/core/harnesses/pi/extensions/doflow-guard.js +167 -0
  60. package/core/harnesses/shared/hooks/ENVELOPE_MIGRATION.md +27 -12
  61. package/core/harnesses/shared/hooks/adapters/antigravity.js +14 -57
  62. package/core/harnesses/shared/hooks/bridge/bridge.sh +277 -0
  63. package/core/harnesses/shared/hooks/bridge/envelope.jq +129 -0
  64. package/core/harnesses/shared/hooks/bridge/observe-model.sh +75 -0
  65. package/core/harnesses/shared/hooks/bridge/respond.sh +70 -0
  66. package/core/harnesses/shared/hooks/policies/blocked-patterns.conf +28 -14
  67. package/core/harnesses/shared/hooks/policies/capture-failure.sh +51 -16
  68. package/core/harnesses/shared/hooks/policies/compact-text.sh +46 -0
  69. package/core/harnesses/shared/hooks/policies/contract.sh +78 -0
  70. package/core/harnesses/shared/hooks/policies/deny-json.sh +35 -9
  71. package/core/harnesses/shared/hooks/policies/emission-rules.sh +37 -0
  72. package/core/harnesses/shared/hooks/policies/emission.jq +209 -0
  73. package/core/harnesses/shared/hooks/policies/emission.sh +383 -0
  74. package/core/harnesses/shared/hooks/policies/identity.sh +177 -0
  75. package/core/harnesses/shared/hooks/policies/lib.sh +65 -40
  76. package/core/harnesses/shared/hooks/policies/mcp-tool-guard.sh +48 -35
  77. package/core/harnesses/shared/hooks/policies/pre-bash-guard.sh +284 -33
  78. package/core/harnesses/shared/hooks/policies/pre-implementation-gate.sh +180 -110
  79. package/core/harnesses/shared/hooks/policies/session-context.sh +66 -7
  80. package/core/harnesses/shared/hooks/policies/stop-check.sh +61 -23
  81. package/core/harnesses/shared/hooks/policies/subagent-audit.sh +39 -6
  82. package/core/harnesses/shared/hooks/policies/summary-store.sh +347 -0
  83. package/core/harnesses/shared/hooks/policies/user-prompt-submit.sh +97 -159
  84. package/core/harnesses/shared/hooks/stream-hook-runner.js +114 -187
  85. package/core/harnesses/shared/locator/doflow-run +2 -3
  86. package/core/registry/advice.json +3 -3
  87. package/core/registry/assets.json +72 -80
  88. package/core/registry/capabilities.json +12 -0
  89. package/core/registry/contracts.json +60 -33
  90. package/core/registry/contribution-points.json +32 -0
  91. package/core/registry/gates.json +32 -0
  92. package/core/registry/guidance-caps.json +40 -0
  93. package/core/registry/harnesses.json +1542 -277
  94. package/core/registry/lifecycle.json +2837 -37
  95. package/core/registry/mcp.json +5 -1
  96. package/core/registry/models.json +7 -1
  97. package/core/registry/package-templates/README.md +19 -0
  98. package/core/registry/package-templates/agent/files/agent.md +9 -0
  99. package/core/registry/package-templates/agent/template.json +4 -0
  100. package/core/registry/package-templates/capability-provider/files/provider.json +9 -0
  101. package/core/registry/package-templates/capability-provider/template.json +11 -0
  102. package/core/registry/package-templates/debate-preset/files/preset.json +26 -0
  103. package/core/registry/package-templates/debate-preset/template.json +4 -0
  104. package/core/registry/package-templates/review-preset/files/preset.json +30 -0
  105. package/core/registry/package-templates/review-preset/template.json +4 -0
  106. package/core/registry/package-templates/skill/files/SKILL.md +8 -0
  107. package/core/registry/package-templates/skill/template.json +4 -0
  108. package/core/registry/package-templates/worker/files/worker.json +92 -0
  109. package/core/registry/package-templates/worker/template.json +11 -0
  110. package/core/registry/packages/doflow/doflow-package.json +15 -0
  111. package/core/registry/packages/doflow/presets/debate-3.json +26 -0
  112. package/core/registry/protocols.json +61 -0
  113. package/core/registry/routes.json +17 -0
  114. package/core/registry/rules.json +431 -0
  115. package/core/registry/settings.json +276 -0
  116. package/core/registry/storage.json +701 -30
  117. package/core/registry/telemetry.json +236 -0
  118. package/core/registry/transcripts.json +142 -0
  119. package/core/registry/verification.json +76 -1
  120. package/core/registry/workflows.json +55 -0
  121. package/core/shared/agent-specs/core-implementer.md +3 -0
  122. package/core/shared/agent-specs/delivery-lead.md +68 -0
  123. package/core/shared/agent-specs/quality-guardian.md +5 -1
  124. package/core/shared/agent-specs/research-writer.md +10 -3
  125. package/core/shared/agent-specs/reviewer.md +53 -0
  126. package/core/shared/agent-specs/spec-analyst.md +5 -6
  127. package/core/shared/agent-specs/system-architect.md +5 -1
  128. package/core/shared/guidance/DOFLOW_CORE.md +1 -5
  129. package/core/shared/guidance/modes/MODE_DeepResearch.md +1 -1
  130. package/core/shared/guidance/modes/MODE_Orchestration.md +4 -3
  131. package/core/shared/guidance/modes/MODE_Task_Management.md +14 -14
  132. package/core/shared/guidance/pointers/codex.md +1 -4
  133. package/core/shared/guidance/references/ARTIFACT_FORMAT.md +30 -16
  134. package/core/shared/guidance/references/ARTIFACT_VOICE.md +3 -2
  135. package/core/shared/guidance/references/CONSTITUTION_BASE.md +17 -15
  136. package/core/shared/guidance/references/DIAGRAM_GUIDE.md +1 -1
  137. package/core/shared/guidance/references/DOFLOW_CHAIN.md +17 -19
  138. package/core/shared/guidance/references/EVIDENCE_LEDGER.md +7 -8
  139. package/core/shared/guidance/references/MODEL_SELECTION.md +5 -3
  140. package/core/shared/guidance/references/ORCHESTRATION.md +1 -2
  141. package/core/shared/guidance/rules/RULES.md +45 -0
  142. package/core/shared/scripts/doflow/bash/do-args.sh +37 -0
  143. package/core/shared/scripts/doflow/bash/do-exec-paths.sh +4 -0
  144. package/core/shared/scripts/doflow/bash/do-git-state.sh +15 -4
  145. package/core/shared/scripts/doflow/bash/do-parallel-check.sh +4 -0
  146. package/core/shared/scripts/doflow/bash/do-paths.sh +40 -2
  147. package/core/shared/scripts/doflow/bash/do-prereqs.sh +2 -1
  148. package/core/shared/scripts/doflow/bash/do-review-package.sh +4 -0
  149. package/core/shared/scripts/doflow/bash/do-task-brief.sh +58 -16
  150. package/core/shared/scripts/doflow/bash/render-audit.sh +26 -2
  151. package/core/shared/scripts/doflow/bash/render-diagrams.sh +1 -0
  152. package/core/shared/scripts/doflow/bash/render-puml.sh +2 -1
  153. package/core/shared/scripts/doflow/bash/validate-artifacts.sh +48 -4
  154. package/core/shared/scripts/doflow/bin/doflow-run +154 -35
  155. package/core/shared/skills/do/SKILL.md +12 -10
  156. package/core/shared/skills/do/references/advice_calls.md +26 -0
  157. package/core/shared/skills/do/references/pm_routing.md +1 -2
  158. package/core/shared/skills/do/references/program_mode.md +31 -0
  159. package/core/shared/skills/do-brainstorm/SKILL.md +20 -74
  160. package/core/shared/skills/do-brainstorm/references/handoff_branches.md +35 -0
  161. package/core/shared/skills/do-brainstorm/references/intent_seed.md +21 -0
  162. package/core/shared/skills/do-brainstorm/references/round_log.md +17 -0
  163. package/core/shared/skills/do-code-review/SKILL.md +41 -144
  164. package/core/shared/skills/do-code-review/content-types/markdown.md +24 -2
  165. package/core/shared/skills/do-code-review/references/tools.md +90 -0
  166. package/core/shared/skills/do-code-review/scripts/doc_quality_checker.py +549 -7
  167. package/core/shared/skills/do-design/SKILL.md +24 -23
  168. package/core/shared/skills/do-diagnose/SKILL.md +14 -4
  169. package/core/shared/skills/do-document/SKILL.md +11 -1
  170. package/core/shared/skills/do-document/references/deep_research.md +1 -2
  171. package/core/shared/skills/do-document/references/embedded-research.md +18 -6
  172. package/core/shared/skills/do-document/references/implementation-flow.md +5 -6
  173. package/core/shared/skills/do-execute-plan/SKILL.md +17 -59
  174. package/core/shared/skills/do-execute-plan/references/handoff_record.md +7 -2
  175. package/core/shared/skills/do-execute-plan/references/parallel_dispatch.md +13 -0
  176. package/core/shared/skills/do-execute-plan/references/readiness_gate.md +28 -1
  177. package/core/shared/skills/do-execute-plan/references/run_position.md +13 -1
  178. package/core/shared/skills/do-execute-plan/references/scaffold.md +17 -9
  179. package/core/shared/skills/do-execute-plan/references/state_checkpoint.md +11 -0
  180. package/core/shared/skills/do-execute-plan/references/task_attempts.md +78 -24
  181. package/core/shared/skills/do-flow/SKILL.md +8 -9
  182. package/core/shared/skills/do-flow/references/task_classes.md +2 -2
  183. package/core/shared/skills/do-git/SKILL.md +4 -0
  184. package/core/shared/skills/do-implement/SKILL.md +7 -9
  185. package/core/shared/skills/do-plan/SKILL.md +29 -19
  186. package/core/shared/skills/do-test/SKILL.md +7 -4
  187. package/core/shared/templates/doflow/constitution-template.md +9 -3
  188. package/core/shared/templates/doflow/data-model-template.md +1 -1
  189. package/core/shared/templates/doflow/design-template.md +113 -16
  190. package/core/shared/templates/doflow/plan-template.md +11 -10
  191. package/core/shared/templates/doflow/question-log-template.md +14 -2
  192. package/core/shared/templates/doflow/requirement-template.md +1 -1
  193. package/core/worker-evidence/antigravity/checks.json +14 -0
  194. package/core/worker-evidence/codex/2026-10-18-cli-check.json +107 -0
  195. package/core/worker-evidence/codex/2026-10-18-live-run-edit.json +73 -0
  196. package/core/worker-evidence/codex/2026-10-18-live-run-read-only.json +62 -0
  197. package/core/worker-evidence/codex/2026-10-24-cli-check.json +126 -0
  198. package/core/worker-evidence/codex/checks.json +38 -0
  199. package/package.json +10 -6
  200. package/src/adapters/antigravity/index.js +226 -113
  201. package/src/adapters/claude/index.js +39 -5
  202. package/src/adapters/codex/agents.js +25 -15
  203. package/src/adapters/codex/hooks.js +40 -5
  204. package/src/adapters/codex/index.js +100 -22
  205. package/src/adapters/codex/native-projection.js +6 -2
  206. package/src/adapters/copilot/index.js +58 -23
  207. package/src/adapters/copy-tree.js +294 -48
  208. package/src/adapters/guidance-manifest.js +71 -0
  209. package/src/adapters/guidance-pointer.js +19 -0
  210. package/src/adapters/hook-commands.js +6 -10
  211. package/src/adapters/index.js +1 -0
  212. package/src/adapters/kiro/index.js +38 -13
  213. package/src/adapters/opencode/index.js +8 -8
  214. package/src/adapters/pi/hooks.js +120 -0
  215. package/src/adapters/pi/index.js +48 -16
  216. package/src/adapters/resolve-source.js +26 -2
  217. package/src/cli/commands/install.js +3 -2
  218. package/src/cli/commands/reconcile.js +53 -9
  219. package/src/cli/commands/remove.js +2 -2
  220. package/src/cli/commands/rollback.js +6 -3
  221. package/src/cli/commands/self-update.js +18 -9
  222. package/src/cli/commands/status.js +21 -2
  223. package/src/cli/commands/ui.js +99 -0
  224. package/src/cli/commands/update.js +3 -2
  225. package/src/cli/index.js +338 -33
  226. package/src/cli/runtime-commands.js +243 -52
  227. package/src/cli/shared.js +0 -2
  228. package/src/helper/git.js +1 -1
  229. package/src/helper/guardrails.js +1 -1
  230. package/src/helper/marker-merge.js +31 -13
  231. package/src/helper/package-ids.js +37 -0
  232. package/src/helper/semver.js +94 -0
  233. package/src/install/backup.js +105 -15
  234. package/src/install/context.js +1 -1
  235. package/src/install/targets.js +31 -16
  236. package/src/lifecycle/coverage.js +74 -0
  237. package/src/lifecycle/hook-step.js +61 -0
  238. package/src/lifecycle/hook-tier.js +160 -0
  239. package/src/lifecycle/index.js +434 -86
  240. package/src/lifecycle/retired-harnesses.js +318 -0
  241. package/src/lifecycle/view.js +35 -15
  242. package/src/registry/index.js +686 -32
  243. package/src/registry/worker-ids.js +45 -0
  244. package/src/release/build-package.js +0 -3
  245. package/src/release/docs-blocks.js +492 -0
  246. package/src/release/preflight.js +425 -0
  247. package/src/release/rehearse.js +401 -0
  248. package/src/release/release-workflow.js +733 -0
  249. package/src/release/sync-plugin-versions.js +43 -7
  250. package/src/runtime/advice/bench-gate.js +25 -0
  251. package/src/runtime/advice/cli.js +59 -16
  252. package/src/runtime/advice/client.js +32 -9
  253. package/src/runtime/advice/config.js +98 -133
  254. package/src/runtime/advice/doctor.js +1 -1
  255. package/src/runtime/advice/elevation.js +97 -0
  256. package/src/runtime/advice/endpoint-guard.js +9 -3
  257. package/src/runtime/advice/mode.js +37 -0
  258. package/src/runtime/advice/record.js +18 -5
  259. package/src/runtime/advice/registry.js +4 -1
  260. package/src/runtime/advice/shadow-config.js +229 -0
  261. package/src/runtime/advice/shadow-dispatcher.js +276 -0
  262. package/src/runtime/brief-rules.js +81 -0
  263. package/src/runtime/brief-tools.js +111 -34
  264. package/src/runtime/candidate.js +514 -0
  265. package/src/runtime/capabilities/executor.js +450 -0
  266. package/src/runtime/capabilities/loopback.js +41 -0
  267. package/src/runtime/capabilities/sanitizer.js +88 -0
  268. package/src/runtime/capability-router.js +135 -3
  269. package/src/runtime/checkouts.js +73 -11
  270. package/src/runtime/claims.js +27 -12
  271. package/src/runtime/cli-result.js +11 -4
  272. package/src/runtime/cli.js +221 -277
  273. package/src/runtime/config-home.js +42 -0
  274. package/src/runtime/context/arbitrate.js +767 -0
  275. package/src/runtime/context/budget.js +203 -0
  276. package/src/runtime/context/compose.js +1367 -0
  277. package/src/runtime/context/delivery-store.js +100 -0
  278. package/src/runtime/context/emission-rules.js +118 -0
  279. package/src/runtime/context/identity.js +227 -0
  280. package/src/runtime/context/mandatory.js +594 -0
  281. package/src/runtime/context/pointer.js +142 -0
  282. package/src/runtime/context/primary-command.js +117 -0
  283. package/src/runtime/context/primary.js +344 -0
  284. package/src/runtime/context/summary-store.js +655 -0
  285. package/src/runtime/context-pack.js +1106 -245
  286. package/src/runtime/decision-register.js +13 -4
  287. package/src/runtime/dispatch/admission.js +392 -0
  288. package/src/runtime/dispatch/attempt-end.js +274 -0
  289. package/src/runtime/dispatch/command.js +628 -0
  290. package/src/runtime/dispatch/controls.js +904 -0
  291. package/src/runtime/dispatch/envelope.js +276 -0
  292. package/src/runtime/dispatch/grants.js +310 -0
  293. package/src/runtime/dispatch/integrate.js +617 -0
  294. package/src/runtime/dispatch/loop.js +1835 -0
  295. package/src/runtime/dispatch/store.js +352 -0
  296. package/src/runtime/dispatch/verify-run.js +201 -0
  297. package/src/runtime/evidence-fields.js +39 -0
  298. package/src/runtime/evidence-ledger.js +41 -7
  299. package/src/runtime/failure/capture.js +3 -1
  300. package/src/runtime/failure/cli.js +25 -9
  301. package/src/runtime/failure/home.js +57 -30
  302. package/src/runtime/failure/store.js +7 -2
  303. package/src/runtime/feature-pointer.js +174 -0
  304. package/src/runtime/feature-resolve.js +13 -2
  305. package/src/runtime/fleet/command.js +77 -0
  306. package/src/runtime/fleet/projects.js +178 -0
  307. package/src/runtime/fleet/tree.js +383 -0
  308. package/src/runtime/freshness.js +77 -10
  309. package/src/runtime/git-memo.js +46 -0
  310. package/src/runtime/guidance/ablation.js +427 -0
  311. package/src/runtime/health.js +565 -17
  312. package/src/runtime/history-compactor.js +13 -3
  313. package/src/runtime/implementation-gate.js +277 -22
  314. package/src/runtime/inventory/coverage.js +1 -1
  315. package/src/runtime/inventory/index.js +1 -1
  316. package/src/runtime/inventory/precedence.js +2 -2
  317. package/src/runtime/inventory/siblings.js +6 -4
  318. package/src/runtime/knowledge/code-corpus.js +277 -0
  319. package/src/runtime/knowledge/code-lexical.js +130 -0
  320. package/src/runtime/knowledge/code-retrieval.js +307 -0
  321. package/src/runtime/knowledge/code-validation.js +106 -0
  322. package/src/runtime/knowledge/gp3-gate.js +74 -0
  323. package/src/runtime/knowledge/graph-adapter.js +288 -0
  324. package/src/runtime/knowledge/graph-fallback.js +394 -0
  325. package/src/runtime/knowledge/hybrid.js +136 -8
  326. package/src/runtime/knowledge/retrieval.js +34 -1
  327. package/src/runtime/knowledge/rtk-compress.js +77 -0
  328. package/src/runtime/knowledge/semble-bridge.py +106 -0
  329. package/src/runtime/knowledge/semble-lifecycle.js +114 -0
  330. package/src/runtime/knowledge/semble-provider.js +365 -0
  331. package/src/runtime/lifecycle/cli.js +16 -6
  332. package/src/runtime/lifecycle/event-store.js +8 -6
  333. package/src/runtime/lifecycle/followup.js +6 -1
  334. package/src/runtime/lifecycle/overview.js +24 -1
  335. package/src/runtime/lifecycle/report-store.js +8 -10
  336. package/src/runtime/lifecycle/retention.js +95 -31
  337. package/src/runtime/lifecycle/root.js +9 -1
  338. package/src/runtime/lifecycle/status.js +34 -17
  339. package/src/runtime/lifecycle/store-upkeep.js +56 -7
  340. package/src/runtime/mask.js +134 -7
  341. package/src/runtime/mcp-consultations.js +40 -6
  342. package/src/runtime/model-router.js +79 -7
  343. package/src/runtime/models/catalogue.js +191 -0
  344. package/src/runtime/models/command.js +153 -0
  345. package/src/runtime/off-words.js +12 -0
  346. package/src/runtime/outcome.js +3 -3
  347. package/src/runtime/packages/command.js +1009 -0
  348. package/src/runtime/packages/contributions.js +116 -0
  349. package/src/runtime/packages/digest.js +132 -0
  350. package/src/runtime/packages/fetch.js +182 -0
  351. package/src/runtime/packages/grants.js +192 -0
  352. package/src/runtime/packages/index-file.js +134 -0
  353. package/src/runtime/packages/instruction.js +90 -0
  354. package/src/runtime/packages/manifest.js +331 -0
  355. package/src/runtime/packages/points.js +477 -0
  356. package/src/runtime/packages/programs.js +216 -0
  357. package/src/runtime/packages/scaffold.js +277 -0
  358. package/src/runtime/packages/source.js +90 -0
  359. package/src/runtime/packages/state.js +164 -0
  360. package/src/runtime/packages/store.js +449 -0
  361. package/src/runtime/packages/tar.js +106 -0
  362. package/src/runtime/packages/view.js +406 -0
  363. package/src/runtime/plan-check.js +59 -0
  364. package/src/runtime/plan-content.js +324 -0
  365. package/src/runtime/policy-init.js +118 -0
  366. package/src/runtime/profiles/doctor.js +96 -0
  367. package/src/runtime/profiles/resolve.js +244 -0
  368. package/src/runtime/profiles/rollback.js +254 -0
  369. package/src/runtime/profiles/sampler.js +41 -0
  370. package/src/runtime/readiness-record.js +16 -11
  371. package/src/runtime/readiness.js +127 -16
  372. package/src/runtime/research-request.js +50 -25
  373. package/src/runtime/retrieval-plan.js +402 -73
  374. package/src/runtime/retrieval-record.js +293 -0
  375. package/src/runtime/review/command.js +246 -0
  376. package/src/runtime/review/dominance.js +68 -0
  377. package/src/runtime/review/findings.js +434 -0
  378. package/src/runtime/review/identity.js +255 -0
  379. package/src/runtime/review/protocol-command.js +109 -0
  380. package/src/runtime/review/protocols.js +405 -0
  381. package/src/runtime/review/resolve-protocol.js +302 -0
  382. package/src/runtime/review/start.js +530 -0
  383. package/src/runtime/roadmap/facts.js +479 -0
  384. package/src/runtime/roadmap/gates.js +270 -0
  385. package/src/runtime/roadmap/loop.js +1636 -0
  386. package/src/runtime/roadmap/merge.js +381 -0
  387. package/src/runtime/roadmap/rows.js +1113 -0
  388. package/src/runtime/roadmap/seed.js +951 -0
  389. package/src/runtime/rules-report.js +336 -0
  390. package/src/runtime/scaffold/artifacts.js +63 -9
  391. package/src/runtime/scaffold/generate.js +21 -9
  392. package/src/runtime/settings/autopilot.js +106 -0
  393. package/src/runtime/settings/cli.js +280 -0
  394. package/src/runtime/settings/config-write.js +314 -0
  395. package/src/runtime/settings/files.js +271 -0
  396. package/src/runtime/settings/legacy.js +154 -0
  397. package/src/runtime/settings/lexical.js +56 -0
  398. package/src/runtime/settings/prepared-run.js +269 -0
  399. package/src/runtime/settings/project-layers.js +118 -0
  400. package/src/runtime/settings/refs.js +288 -0
  401. package/src/runtime/settings/registry.js +553 -0
  402. package/src/runtime/settings/resolve.js +713 -0
  403. package/src/runtime/settings/shared.js +37 -0
  404. package/src/runtime/settings/worker-billing.js +107 -0
  405. package/src/runtime/settings/write.js +269 -0
  406. package/src/runtime/slot-command.js +78 -0
  407. package/src/runtime/slots.js +671 -0
  408. package/src/runtime/source-version.js +290 -0
  409. package/src/runtime/state-root.js +217 -0
  410. package/src/runtime/storage/cli.js +12 -5
  411. package/src/runtime/storage/engine.js +9 -2
  412. package/src/runtime/storage/registry.js +14 -1
  413. package/src/runtime/storage/roots.js +66 -16
  414. package/src/runtime/storage/rules.js +82 -10
  415. package/src/runtime/storage/upkeep.js +10 -2
  416. package/src/runtime/storage/work-status.js +245 -26
  417. package/src/runtime/task-attempts.js +589 -69
  418. package/src/runtime/task-classifier.js +47 -11
  419. package/src/runtime/task-graph.js +373 -0
  420. package/src/runtime/task-run.js +778 -0
  421. package/src/runtime/task-scope.js +202 -56
  422. package/src/runtime/task-sources.js +147 -0
  423. package/src/runtime/task-state.js +440 -65
  424. package/src/runtime/team/admission.js +301 -0
  425. package/src/runtime/team/envelope.js +324 -0
  426. package/src/runtime/team/review-guard.js +156 -0
  427. package/src/runtime/trace/analyze.js +427 -0
  428. package/src/runtime/trace/baseline.js +770 -0
  429. package/src/runtime/trace/ledger.js +63 -140
  430. package/src/runtime/trace/otlp-export.js +568 -0
  431. package/src/runtime/trace/otlp-map.js +481 -0
  432. package/src/runtime/trace/spans.js +745 -0
  433. package/src/runtime/trace/telemetry.js +225 -0
  434. package/src/runtime/trace/views.js +1 -0
  435. package/src/runtime/transcripts/command.js +98 -0
  436. package/src/runtime/transcripts/cursor.js +326 -0
  437. package/src/runtime/transcripts/read.js +126 -0
  438. package/src/runtime/transcripts/readers/claude-jsonl.js +74 -0
  439. package/src/runtime/transcripts/readers/codex-rollout.js +70 -0
  440. package/src/runtime/transcripts/readers/copilot-events.js +90 -0
  441. package/src/runtime/transcripts/readers/kiro-jsonl.js +77 -0
  442. package/src/runtime/transcripts/readers/pi-jsonl.js +59 -0
  443. package/src/runtime/transcripts/registry.js +161 -0
  444. package/src/runtime/transcripts/stats.js +161 -0
  445. package/src/runtime/verification/check-key.js +628 -0
  446. package/src/runtime/verification/contract-runner.js +546 -11
  447. package/src/runtime/verification/engine.js +499 -9
  448. package/src/runtime/verification/impact.js +617 -0
  449. package/src/runtime/verification/inflight.js +205 -0
  450. package/src/runtime/verification/registry.js +176 -0
  451. package/src/runtime/verification/reuse.js +184 -0
  452. package/src/runtime/verification/telemetry-emit.js +254 -0
  453. package/src/runtime/verification/test-selection.js +67 -7
  454. package/src/runtime/worker/active.js +90 -0
  455. package/src/runtime/worker/cancel.js +222 -0
  456. package/src/runtime/worker/catalogue.js +122 -0
  457. package/src/runtime/worker/command.js +677 -0
  458. package/src/runtime/worker/evidence.js +288 -0
  459. package/src/runtime/worker/launch.js +288 -0
  460. package/src/runtime/worker/models.js +137 -0
  461. package/src/runtime/worker/parsers.js +218 -0
  462. package/src/runtime/worker/probes/index.js +706 -0
  463. package/src/runtime/worker/process-tree.js +415 -0
  464. package/src/runtime/worker/quota.js +43 -0
  465. package/src/runtime/worker/readiness.js +713 -0
  466. package/src/runtime/worker/run-store.js +166 -0
  467. package/src/runtime/worker/supervisor.js +541 -0
  468. package/src/runtime/worker/worktree-check.js +252 -0
  469. package/src/runtime/workflow-engine.js +5 -1
  470. package/src/runtime/workflow-orchestrator.js +578 -63
  471. package/src/runtime/worktree.js +149 -22
  472. package/src/state/index.js +1 -1
  473. package/src/state/lockfile.js +64 -10
  474. package/src/state/run-lock.js +1 -1
  475. package/types/node-commonjs.d.ts +106 -7
  476. package/core/.antigravity-plugin/hooks.json +0 -39
  477. package/core/harnesses/gemini/hooks/hooks.json +0 -77
  478. package/core/harnesses/gemini/hooks/lib.sh +0 -302
  479. package/core/harnesses/gemini/hooks/mcp-tool-guard.sh +0 -21
  480. package/core/harnesses/gemini/hooks/post-edit-lint.sh +0 -39
  481. package/core/harnesses/gemini/hooks/pre-bash-guard.sh +0 -21
  482. package/core/harnesses/gemini/hooks/pre-compact.sh +0 -38
  483. package/core/harnesses/gemini/hooks/pre-implement-gate.sh +0 -22
  484. package/core/harnesses/gemini/hooks/session-end.impl.sh +0 -89
  485. package/core/harnesses/gemini/hooks/session-end.sh +0 -5
  486. package/core/harnesses/gemini/hooks/session-start.sh +0 -5
  487. package/core/harnesses/gemini/settings/adapter-defaults.json +0 -4
  488. package/core/harnesses/shared/hooks/adapters/arg-fields.js +0 -19
  489. package/core/harnesses/shared/hooks/adapters/gemini.js +0 -114
  490. package/core/harnesses/shared/hooks/hooks.json +0 -47
  491. package/core/shared/guidance/FLAGS.md +0 -25
  492. package/core/shared/guidance/rules/RULE_01_SAFETY.md +0 -25
  493. package/core/shared/guidance/rules/RULE_02_WORKFLOW.md +0 -23
  494. package/core/shared/guidance/rules/RULE_03_QUALITY.md +0 -14
  495. package/core/shared/guidance/rules/RULE_04_QUESTIONS.md +0 -46
  496. package/src/adapters/gemini/hooks.js +0 -210
  497. package/src/adapters/gemini/index.js +0 -390
@@ -0,0 +1,352 @@
1
+ 'use strict';
2
+
3
+ // Feature 082 A.4 (IC-006, IC-009 phase table, IC-036; DEC-018, DEC-037, DEC-045): the dispatch store.
4
+ //
5
+ // One file per feature, `.doflow/state/dispatch/<slug>.json` under the lifecycle project root,
6
+ // shaped `{ slug, run, dispatches: { <d-id>: Dispatch }, drivers, lease, spawnRequests, steps }`
7
+ // (fields: design/data-model.md §1). Every write is a locked read-decide-write through
8
+ // `updateTaskState`, which stamps `version` and `revision` and owns the only fs write calls (G26 (b),
9
+ // IC-037): this module makes none.
10
+ //
11
+ // Two writes exist. `mutateDispatch` runs `decide(store)` inside the lock; `write: false` leaves the
12
+ // file untouched. `transitionDispatch` is the per-dispatch compare-and-set: it refuses with no write
13
+ // when the dispatch's `phase` is not in `allowedPhases`, otherwise applies and increments `seq`.
14
+ // Both enforce, on the state they are about to write, the rules that make a phase name what exists
15
+ // (DEC-045): a new dispatch starts `admitted`, only an `admitted` slot may be dropped, a phase moves
16
+ // only along IC-009's table, `(task, token)` is unique, and an `ended` dispatch is add-only (only
17
+ // `cleanup`, `removal` and `usageLate` may be set while absent, and `removal.verified` may move from
18
+ // false to true, IC-021). The step log keeps the newest STEP_LIMIT records.
19
+ //
20
+ // A refusal is a returned value, never a throw; a torn or future-version file is a thrown
21
+ // `store-unreadable` (exit 2), as in 080 and 081. The callbacks run inside the lock, so they do no
22
+ // I/O but the in-process 080 calls IC-006 lists and one bounded read of one small file (IC-013):
23
+ // no git call, spawn, signal or wait.
24
+ //
25
+ // Feature 106 A.2 (IC-004): `fallbackOf` and `appendFallbackEvent` keep a run's `fallback` state,
26
+ // pure helpers a locked write's callback calls on the store it holds.
27
+
28
+ const fs = require('node:fs');
29
+ const path = require('node:path');
30
+ const crypto = require('node:crypto');
31
+ const { isDeepStrictEqual } = require('node:util');
32
+ const { updateTaskState, readTaskState } = require('../task-state.js');
33
+ const { isSafeSlug } = require('../task-scope.js');
34
+
35
+ /** IC-009's six dispatch phases, in order. */
36
+ const PHASES = Object.freeze(['admitted', 'claimed', 'launched', 'collected', 'integrating', 'ended']);
37
+ /** The phases each phase may move to in one write, itself included (IC-009, data-model "Dispatch phases"). */
38
+ const NEXT_PHASES = Object.freeze({
39
+ admitted: Object.freeze(['admitted', 'claimed']),
40
+ claimed: Object.freeze(['claimed', 'launched', 'ended']),
41
+ launched: Object.freeze(['launched', 'collected', 'ended']),
42
+ collected: Object.freeze(['collected', 'integrating', 'ended']),
43
+ integrating: Object.freeze(['integrating', 'collected', 'ended']),
44
+ ended: Object.freeze(['ended']),
45
+ });
46
+ /** Fields an ended dispatch accepts while absent (IC-006). */
47
+ const ADD_ONLY_FIELDS = Object.freeze(['cleanup', 'removal', 'usageLate']);
48
+ const STEP_LIMIT = 500;
49
+ const DISPATCH_RUN_ID = /^p-[0-9a-f]{12}$/;
50
+ const DISPATCH_ID = /^d-[0-9a-f]{12}$/;
51
+ /** 106 IC-004: the newest fallback events a run keeps. */
52
+ const FALLBACK_EVENTS_MAX = 200;
53
+
54
+ /** A failure that is the caller's or the environment's, not a refusal: exit 2. */
55
+ class DispatchStoreError extends Error {
56
+
57
+ constructor(code , message ) {
58
+ super(message);
59
+ this.name = 'DispatchStoreError';
60
+ this.code = code;
61
+ }
62
+ }
63
+
64
+ function storeFileFor(root , slug ) {
65
+ if (!isSafeSlug(slug)) throw new DispatchStoreError('usage', `'${slug}' is not a valid feature slug`);
66
+ return path.join(root, '.doflow', 'state', 'dispatch', `${slug}.json`);
67
+ }
68
+
69
+ /** `p-` plus 12 lowercase hex characters from 6 random bytes (DEC-037). */
70
+ function newDispatchRunId() {
71
+ return `p-${crypto.randomBytes(6).toString('hex')}`;
72
+ }
73
+
74
+ /** `d-` plus 12 lowercase hex characters from 6 random bytes (DEC-037). */
75
+ function newDispatchId() {
76
+ return `d-${crypto.randomBytes(6).toString('hex')}`;
77
+ }
78
+
79
+ function unreadable(file , why ) {
80
+ return new DispatchStoreError('store-unreadable', `Dispatch store '${file}' cannot be read: ${why}`);
81
+ }
82
+
83
+ function usage(message ) {
84
+ return new DispatchStoreError('usage', message);
85
+ }
86
+
87
+ function isObject(value ) {
88
+ return !!value && typeof value === 'object' && !Array.isArray(value);
89
+ }
90
+
91
+ /** The parsed store without `version` and `revision`, or an empty one when the file does not exist. */
92
+ function normalizeStore(data , file ) {
93
+ const empty = { slug: path.basename(file, '.json'), run: null, dispatches: {}, drivers: [], lease: null, spawnRequests: {}, steps: [] };
94
+ if (data === null) return empty;
95
+ if (!isObject(data)) throw unreadable(file, 'it is not a JSON object.');
96
+ const { version, revision, ...rest } = data;
97
+ const store = { ...empty, ...rest };
98
+ if (store.run !== null && !isObject(store.run)) throw unreadable(file, "its 'run' is not an object or null.");
99
+ if (store.lease !== null && !isObject(store.lease)) throw unreadable(file, "its 'lease' is not an object or null.");
100
+ if (!isObject(store.dispatches)) throw unreadable(file, "its 'dispatches' is not an object.");
101
+ if (!isObject(store.spawnRequests)) throw unreadable(file, "its 'spawnRequests' is not an object.");
102
+ if (!Array.isArray(store.drivers)) throw unreadable(file, "its 'drivers' is not a list.");
103
+ if (!Array.isArray(store.steps)) throw unreadable(file, "its 'steps' is not a list.");
104
+ for (const [id, dispatch] of Object.entries(store.dispatches) ) {
105
+ if (!isObject(dispatch)) throw unreadable(file, `dispatch '${id}' is not an object.`);
106
+ if (!PHASES.includes(dispatch.phase)) throw unreadable(file, `dispatch '${id}' has phase '${dispatch.phase}', not one of ${PHASES.join(', ')}.`);
107
+ if (!Number.isInteger(dispatch.seq) || dispatch.seq < 0) throw unreadable(file, `dispatch '${id}' has seq '${dispatch.seq}', not a non-negative integer.`);
108
+ }
109
+ return store;
110
+ }
111
+
112
+ function rethrow(error , file ) {
113
+ if (error instanceof DispatchStoreError) throw error;
114
+ if (error && error.code === 'store-unreadable') throw unreadable(file, error.message);
115
+ throw error;
116
+ }
117
+
118
+ /** The first field an ended dispatch's write changes against IC-006's add-only rule, or null. */
119
+ function endedViolation(before , after ) {
120
+ for (const key of new Set([...Object.keys(before), ...Object.keys(after)])) {
121
+ if (key === 'seq' || isDeepStrictEqual(before[key], after[key])) continue;
122
+ if (ADD_ONLY_FIELDS.includes(key) && before[key] == null && after[key] != null) continue;
123
+ if (key === 'removal' && isObject(before.removal) && before.removal.verified === false
124
+ && isObject(after.removal) && after.removal.verified === true) continue;
125
+ return key;
126
+ }
127
+ return null;
128
+ }
129
+
130
+ /**
131
+ * Checks the store about to be written against the one read. Returns `{ field, dispatchId }` for an
132
+ * ended dispatch's add-only violation; throws `usage` for every other broken rule, a caller defect.
133
+ */
134
+ function checkWrite(before , after ) {
135
+ if (!isObject(after) || !isObject(after.dispatches)) throw usage("a dispatch store write needs a 'dispatches' object");
136
+ if (!Array.isArray(after.steps)) throw usage("a dispatch store write needs a 'steps' list");
137
+ for (const [id, old] of Object.entries(before.dispatches) ) {
138
+ if (!(id in after.dispatches) && old.phase !== 'admitted') throw usage(`dispatch '${id}' is '${old.phase}'; only an admitted slot may be dropped`);
139
+ }
140
+ const identities = new Map();
141
+ for (const [id, next] of Object.entries(after.dispatches) ) {
142
+ if (!DISPATCH_ID.test(id)) throw usage(`dispatch id '${id}' is not 'd-' plus 12 hex characters`);
143
+ if (!isObject(next) || next.dispatchId !== id) throw usage(`dispatch '${id}' must be an object whose dispatchId is '${id}'`);
144
+ if (!PHASES.includes(next.phase)) throw usage(`dispatch '${id}' has phase '${next.phase}', not one of ${PHASES.join(', ')}`);
145
+ const old = before.dispatches[id];
146
+ if (!old && next.phase !== 'admitted') throw usage(`new dispatch '${id}' must be admitted, not '${next.phase}'`);
147
+ if (old && !NEXT_PHASES[old.phase].includes(next.phase)) throw usage(`dispatch '${id}' cannot move from '${old.phase}' to '${next.phase}'`);
148
+ if (old && next.task !== old.task) throw usage(`dispatch '${id}' cannot change its task`);
149
+ if (next.token !== undefined && next.token !== null) {
150
+ const identity = `${next.task}\u0000${next.token}`;
151
+ if (identities.has(identity)) throw usage(`dispatches '${identities.get(identity)}' and '${id}' share task '${next.task}' token ${next.token}`);
152
+ identities.set(identity, id);
153
+ }
154
+ if (old && old.phase === 'ended') {
155
+ const field = endedViolation(old, next);
156
+ if (field) return { dispatchId: id, field };
157
+ }
158
+ }
159
+ return null;
160
+ }
161
+
162
+ /** Every dispatch's `seq`: 0 when new, one higher when changed or forced, else as stored. */
163
+ function stampSeq(before , after , forced ) {
164
+ for (const [id, next] of Object.entries(after.dispatches) ) {
165
+ const old = before.dispatches[id];
166
+ if (!old) { next.seq = 0; continue; }
167
+ const changed = id === forced || !isDeepStrictEqual({ ...old, seq: 0 }, { ...next, seq: 0 });
168
+ next.seq = changed ? old.seq + 1 : old.seq;
169
+ }
170
+ }
171
+
172
+ /** Run states only their owner may move (IC-009, IC-026, IC-030, IC-032): a cancel marks and ends a
173
+ * run, a resume clears `resume-required`, and `completed` is final. */
174
+ const HELD_RUN_STATES = Object.freeze(['cancelling', 'cancelled', 'completed', 'resume-required']);
175
+
176
+ /**
177
+ * The one run-state setter, used inside a locked write by every writer of `run.state`. It moves
178
+ * `store.run.state` to `next` and returns true, unless the run holds a state another writer owns:
179
+ * `cancelling`, `cancelled`, `completed` or `resume-required`, which only `writer: 'cancel'` (IC-030
180
+ * steps 3 and 9: to `cancelling` or `cancelled`, never over `completed` or `cancelled`) or
181
+ * `writer: 'resume'` (IC-032 step 8: `resume-required` to `running` or `blocked`) may move. A held
182
+ * state is returned as false with nothing changed, so a pass that read the run before a cancel, a
183
+ * lost driver or a completion never overwrites it.
184
+ */
185
+ function setRunState(store , next , writer = null) {
186
+ const run = store && store.run;
187
+ if (!run) return false;
188
+ const current = run.state;
189
+ if (current === next) return true;
190
+ if (HELD_RUN_STATES.includes(current)) {
191
+ const cancelling = writer === 'cancel' && ['cancelling', 'cancelled'].includes(next) && !['completed', 'cancelled'].includes(current);
192
+ const resuming = writer === 'resume' && current === 'resume-required' && ['running', 'blocked'].includes(next);
193
+ if (!cancelling && !resuming) return false;
194
+ }
195
+ run.state = next;
196
+ return true;
197
+ }
198
+
199
+ /**
200
+ * 106 IC-004: `run.fallback`, created in place with `mode` when absent (a run started before 106).
201
+ * Shape: `{ mode, ladder: string[] | null, inSession?: boolean, exhausted: string[], holds: { [task]: Hold }, events, eventsDropped }`.
202
+ * IC-010: older runs and unset starts omit `inSession`; creating fallback never adds it.
203
+ */
204
+ function fallbackOf(run , mode ) {
205
+ if (!isObject(run.fallback)) run.fallback = { mode, ladder: null, exhausted: [], holds: {}, events: [], eventsDropped: 0 };
206
+ return run.fallback;
207
+ }
208
+
209
+ /** IC-010: only an explicitly stored false disables in-session work; older runs read true. */
210
+ function inSessionOf(run ) {
211
+ return !(isObject(run.fallback) && run.fallback.inSession === false);
212
+ }
213
+
214
+ /** IC-013: read model fields independently; legacy records remain unchanged on disk. */
215
+ function modelRecordOf(dispatch ) {
216
+ const model = isObject(dispatch?.model) ? dispatch.model : {};
217
+ return {
218
+ tier: ['light', 'standard', 'frontier'].includes(model.tier) ? model.tier : null,
219
+ requested: typeof model.requested === 'string' ? model.requested : null,
220
+ source: ['binding', 'harness-default'].includes(model.source) ? model.source : null,
221
+ served: typeof model.served === 'string' ? model.served : null,
222
+ };
223
+ }
224
+
225
+ /** IC-010: the stored admission snapshot, or null for a record without one. */
226
+ function primaryOf(dispatch ) {
227
+ return isObject(dispatch?.primary) ? dispatch.primary : null;
228
+ }
229
+
230
+ /** 106 IC-004: appends `event` to `run.fallback.events`, keeping the newest 200 and counting the dropped. */
231
+ function appendFallbackEvent(run , event , mode ) {
232
+ const f = fallbackOf(run, mode);
233
+ if (!Array.isArray(f.events)) f.events = [];
234
+ f.events.push(event);
235
+ const over = f.events.length - FALLBACK_EVENTS_MAX;
236
+ if (over > 0) {
237
+ f.events.splice(0, over);
238
+ f.eventsDropped = (Number.isInteger(f.eventsDropped) ? f.eventsDropped : 0) + over;
239
+ }
240
+ return event;
241
+ }
242
+
243
+ /** The whole store, `dispatches` keyed by id; the empty store when the file does not exist. Takes no lock. */
244
+ function readDispatch(file , fsImpl = fs) {
245
+ try {
246
+ return normalizeStore(readTaskState(fsImpl, file), file);
247
+ } catch (error ) {
248
+ return rethrow(error, file);
249
+ }
250
+ }
251
+
252
+ /**
253
+ * The one locked write. `decide(store)` changes the store in place and returns `{ write, result }`.
254
+ * Returns `{ outcome, violation, before, written }`, the last two the store as read and as written
255
+ * inside the lock; a violation of the ended add-only rule writes nothing.
256
+ */
257
+ function writeLocked(file , fsImpl , decide , forced ) {
258
+ let outcome = null;
259
+ let violation = null;
260
+ let before = null;
261
+ let written = null;
262
+ try {
263
+ updateTaskState({
264
+ fsImpl,
265
+ file,
266
+ build: (disk ) => {
267
+ const store = normalizeStore(disk, file);
268
+ before = structuredClone(store);
269
+ outcome = decide(store);
270
+ if (!outcome || typeof outcome !== 'object' || typeof outcome.write !== 'boolean') throw usage('decide must return { write, result }');
271
+ if (!outcome.write) return null;
272
+ violation = checkWrite(before, store);
273
+ if (violation) return null;
274
+ stampSeq(before, store, forced);
275
+ if (store.steps.length > STEP_LIMIT) store.steps = store.steps.slice(-STEP_LIMIT);
276
+ written = structuredClone(store);
277
+ return store;
278
+ },
279
+ });
280
+ } catch (error ) {
281
+ rethrow(error, file);
282
+ }
283
+ return { outcome, violation, before, written };
284
+ }
285
+
286
+ /**
287
+ * One locked read-decide-write. `decide(store)` runs inside the lock over the parsed store, changes it
288
+ * in place and returns `{ write, result }`; with `write` false the file's bytes stay as they were.
289
+ * Returns `result`. A write that breaks an ended dispatch's add-only rule is a caller defect: thrown
290
+ * `usage`, nothing written.
291
+ */
292
+ function mutateDispatch(file , decide , fsImpl = fs) {
293
+ const { outcome, violation } = writeLocked(file, fsImpl, decide, null);
294
+ if (violation) throw usage(`ended dispatch '${violation.dispatchId}' cannot change '${violation.field}' (IC-006 add-only)`);
295
+ return outcome.result;
296
+ }
297
+
298
+ /**
299
+ * The per-dispatch compare-and-set. Inside the lock: refuse with no write when the dispatch is absent
300
+ * (`unknown-dispatch`) or its `phase` is not in `allowedPhases` (`phase-not-allowed`, carrying the
301
+ * current record); otherwise `apply(copy)` changes the copy, in place or by returning a replacement,
302
+ * and the dispatch is written with `seq` one higher. On an ended dispatch a change outside the
303
+ * add-only rule is refused `ended-add-only`, naming the field. `apply` runs under the lock.
304
+ * @returns {{ applied: true, dispatch: Object } | { applied: false, reason: string, dispatchId: string, phase?: string, field?: string, dispatch?: Object }}
305
+ */
306
+ function transitionDispatch(file , dispatchId , allowedPhases , apply , fsImpl = fs) {
307
+ if (typeof dispatchId !== 'string' || !DISPATCH_ID.test(dispatchId)) throw usage(`dispatch id '${dispatchId}' is not 'd-' plus 12 hex characters`);
308
+ if (!Array.isArray(allowedPhases) || allowedPhases.length === 0 || allowedPhases.some((p) => !PHASES.includes(p))) {
309
+ throw usage(`allowed phases must be a non-empty list of ${PHASES.join(', ')}`);
310
+ }
311
+ const { outcome, violation, before, written } = writeLocked(file, fsImpl, (store) => {
312
+ const current = store.dispatches[dispatchId];
313
+ if (!current) return { write: false, result: { applied: false, reason: 'unknown-dispatch', dispatchId } };
314
+ if (!allowedPhases.includes(current.phase)) {
315
+ return { write: false, result: { applied: false, reason: 'phase-not-allowed', dispatchId, phase: current.phase, dispatch: structuredClone(current) } };
316
+ }
317
+ const draft = structuredClone(current);
318
+ const returned = apply(draft);
319
+ const next = returned && typeof returned === 'object' ? returned : draft;
320
+ store.dispatches[dispatchId] = { ...next, dispatchId };
321
+ return { write: true, result: null };
322
+ }, dispatchId);
323
+ if (violation) {
324
+ return { applied: false, reason: 'ended-add-only', dispatchId, phase: 'ended', field: violation.field, dispatch: structuredClone(before.dispatches[dispatchId]) };
325
+ }
326
+ if (outcome.result) return outcome.result;
327
+ return { applied: true, dispatch: structuredClone(written.dispatches[dispatchId]) };
328
+ }
329
+
330
+ module.exports = {
331
+ storeFileFor,
332
+ newDispatchRunId,
333
+ newDispatchId,
334
+ readDispatch,
335
+ mutateDispatch,
336
+ transitionDispatch,
337
+ setRunState,
338
+ fallbackOf,
339
+ inSessionOf,
340
+ modelRecordOf,
341
+ primaryOf,
342
+ appendFallbackEvent,
343
+ FALLBACK_EVENTS_MAX,
344
+ HELD_RUN_STATES,
345
+ DispatchStoreError,
346
+ PHASES,
347
+ NEXT_PHASES,
348
+ ADD_ONLY_FIELDS,
349
+ STEP_LIMIT,
350
+ DISPATCH_RUN_ID,
351
+ DISPATCH_ID,
352
+ };
@@ -0,0 +1,201 @@
1
+ 'use strict';
2
+
3
+ // Feature 082 A.6 (IC-019, IC-032 endVerification; DEC-006, DEC-024, DEC-047): the verification
4
+ // runner.
5
+ //
6
+ // The run's declared `verify.argv` is spawned with no shell in the integration worktree, as its own
7
+ // process group off win32, with the caller's environment less the variables that would point git at
8
+ // another repository. `onSpawn` receives `{ pid, pgid, start }` right after the spawn so the caller
9
+ // records it as the run's `verifying` by a locked write (IC-019). Output goes through 081's masked,
10
+ // bounded `createOutputSink` under a fresh `w-` output id; at the bound the group is ended by 081's
11
+ // `escalate` against the recorded leader start. One `test-result` evidence item is saved through
12
+ // `EvidenceLedger`; it names the argv by its sha256 and count only, never an element (R14).
13
+ //
14
+ // This is the one module under `src/runtime/dispatch/` that calls `escalate` (DEC-047, G42.5), and
15
+ // it signals nothing directly: the group it spawned is the only one dispatch code ends. Writes go
16
+ // through `createOutputSink` and `EvidenceLedger` only.
17
+
18
+ const path = require('node:path');
19
+ const crypto = require('node:crypto');
20
+ const { spawn, execFileSync } = require('node:child_process');
21
+ const { createOutputSink } = require('../worker/supervisor.js');
22
+ const { newRunId } = require('../worker/run-store.js');
23
+ const processTree = require('../worker/process-tree.js');
24
+ const { EvidenceLedger } = require('../evidence-ledger.js');
25
+ const { emitVerifyTelemetry } = require('../verification/telemetry-emit.js');
26
+
27
+ /** The same names as 081's `REPO_ENV` (`src/runtime/worker/worktree-check.js:30`), which that
28
+ * module does not export: inherited variables that would point git at another repository. */
29
+ const REPO_ENV = Object.freeze(['GIT_DIR', 'GIT_WORK_TREE', 'GIT_INDEX_FILE', 'GIT_COMMON_DIR', 'GIT_OBJECT_DIRECTORY']);
30
+ /** How long the streams may stay open after the leader's exit (a grandchild may hold them). */
31
+ const DRAIN_MS = 500;
32
+ /** Bound on the `git rev-parse HEAD` read of the verified head. */
33
+ const GIT_TIMEOUT_MS = 2000;
34
+ const SOURCE = Object.freeze({ provider: 'doflow-dispatch', capability: 'verify' });
35
+
36
+
37
+
38
+
39
+
40
+
41
+
42
+
43
+
44
+
45
+ /** sha256 hex over the canonical JSON of an argv (an array of strings has one JSON form). */
46
+ function argvDigest(argv ) {
47
+ return crypto.createHash('sha256').update(JSON.stringify(argv)).digest('hex');
48
+ }
49
+
50
+ /** `env` without `REPO_ENV`'s names. */
51
+ function childEnv(env ) {
52
+ const out = { ...env };
53
+ for (const key of REPO_ENV) delete out[key];
54
+ return out;
55
+ }
56
+
57
+ /** The worktree's HEAD commit, or null when git could not read it. */
58
+ function headOf(worktree , env ) {
59
+ try {
60
+ const out = execFileSync('git', ['-C', worktree, 'rev-parse', '--verify', '-q', 'HEAD'], {
61
+ encoding: 'utf8', env, timeout: GIT_TIMEOUT_MS, stdio: ['ignore', 'pipe', 'ignore'],
62
+ }).trim();
63
+ return /^[0-9a-f]{40,64}$/.test(out) ? out : null;
64
+ } catch {
65
+ return null;
66
+ }
67
+ }
68
+
69
+ function drained(stream ) {
70
+ if (!stream || stream.readableEnded || stream.destroyed) return Promise.resolve();
71
+ return new Promise((resolve) => {
72
+ stream.once('end', resolve);
73
+ stream.once('close', resolve);
74
+ });
75
+ }
76
+
77
+ function boundedWait(promise , ms ) {
78
+ let timer ;
79
+ const bound = new Promise((resolve) => { timer = setTimeout(resolve, ms); });
80
+ return Promise.race([promise, bound]).finally(() => clearTimeout(timer));
81
+ }
82
+
83
+ /**
84
+ * IC-019: runs `argv` in `worktree` and records one `test-result` evidence item for `taskId` (the
85
+ * task for a per-integration run, the slug for the final run) under `root`'s evidence store for
86
+ * `slug`. `seconds` is the bound. `escalate` and `procStart` default to 081's and exist for tests.
87
+ * Resolves a `VerificationRun`; a pass is `exitCode === 0 && !timedOut`.
88
+ */
89
+ async function runVerification({
90
+ root, slug, taskId, worktree, argv, argvSha256 = null, seconds, env = process.env, platform = process.platform,
91
+ onSpawn = null, escalate = processTree.escalate, procStart = processTree.procStart, dispatchRunId = null,
92
+ } ) {
93
+ if (!Array.isArray(argv) || argv.length === 0 || !argv.every((part ) => typeof part === 'string' && part.length > 0)) {
94
+ throw new TypeError('runVerification: argv must be a non-empty array of non-empty strings');
95
+ }
96
+ if (!Number.isInteger(seconds) || seconds <= 0) throw new TypeError(`runVerification: seconds must be a positive whole number: ${seconds}`);
97
+ const digest = typeof argvSha256 === 'string' && argvSha256 ? argvSha256 : argvDigest(argv);
98
+ const stateRoot = path.join(root, '.doflow', 'state');
99
+ const outputId = newRunId();
100
+ const envForChild = childEnv(env);
101
+ const head = headOf(worktree, envForChild);
102
+ const out = createOutputSink({ stateRoot, runId: outputId, stream: 'stdout' });
103
+ const err = createOutputSink({ stateRoot, runId: outputId, stream: 'stderr' });
104
+ const startedAt = Date.now();
105
+
106
+ let exitCode = null;
107
+ let error = null;
108
+ let timedOut = false;
109
+ let cleanup = null;
110
+
111
+ let child = null;
112
+ try {
113
+ child = spawn(argv[0], argv.slice(1), {
114
+ cwd: worktree, env: envForChild, detached: platform !== 'win32', shell: false, stdio: ['ignore', 'pipe', 'pipe'],
115
+ });
116
+ } catch (spawnError ) {
117
+ error = typeof spawnError?.code === 'string' && spawnError.code ? spawnError.code : 'spawn-failed';
118
+ }
119
+
120
+ if (child) {
121
+ const settled = new Promise((resolve) => {
122
+ child.once('error', (e ) => resolve({ code: null, spawnError: typeof e?.code === 'string' && e.code ? e.code : 'spawn-failed' }));
123
+ child.once('exit', (code ) => resolve({ code, spawnError: null }));
124
+ });
125
+ child.stdout.on('data', (chunk ) => out.write(chunk));
126
+ child.stderr.on('data', (chunk ) => err.write(chunk));
127
+ const streamsDone = Promise.all([drained(child.stdout), drained(child.stderr)]);
128
+
129
+ let ending = Promise.resolve();
130
+ let timer = null;
131
+ if (Number.isInteger(child.pid) && child.pid > 0) {
132
+ const proc = { pid: child.pid, pgid: child.pid, start: platform === 'win32' ? null : procStart(child.pid) };
133
+ timer = setTimeout(() => {
134
+ timedOut = true;
135
+ ending = Promise.resolve(escalate({ proc, platform })).then(
136
+ (outcome ) => { cleanup = { status: outcome?.status ?? 'unknown', reason: outcome?.reason ?? null }; },
137
+ () => { cleanup = { status: 'unknown', reason: 'escalate-failed' }; },
138
+ );
139
+ }, seconds * 1000);
140
+ if (onSpawn) {
141
+ try {
142
+ onSpawn({ ...proc });
143
+ } catch (recordError) {
144
+ // An unrecorded group could never be found by resume: end it now, then report the failure.
145
+ clearTimeout(timer);
146
+ child.stdout.resume();
147
+ child.stderr.resume();
148
+ await escalate({ proc, platform });
149
+ await settled;
150
+ child.stdout.destroy();
151
+ child.stderr.destroy();
152
+ await out.end();
153
+ await err.end();
154
+ throw recordError;
155
+ }
156
+ }
157
+ }
158
+ const result = await settled;
159
+ clearTimeout(timer);
160
+ await ending;
161
+ if (result.spawnError) error = result.spawnError;
162
+ else exitCode = result.code;
163
+ await boundedWait(streamsDone, DRAIN_MS);
164
+ child.stdout.destroy();
165
+ child.stderr.destroy();
166
+ }
167
+ const stdout = await out.end();
168
+ await err.end();
169
+ const durationMs = Date.now() - startedAt;
170
+ // 088 C.1 (IC-006): one verify.run line, `source: dispatch`; it never throws and changes nothing here.
171
+ emitVerifyTelemetry({
172
+ report: { status: exitCode === 0 && !timedOut ? 'PASS' : 'FAIL', startedAt: new Date(startedAt).toISOString(), finishedAt: new Date(startedAt + durationMs).toISOString(), tiers: [] },
173
+ ctx: { source: 'dispatch', taskId, risk: null, exitCode, dispatchRun: dispatchRunId }, env, start: root,
174
+ });
175
+
176
+ const ledger = new EvidenceLedger({ repoRoot: root, slug });
177
+ const evidenceId = ledger.addEvidence({
178
+ kind: 'test-result',
179
+ provenance: 'extracted',
180
+ source: { ...SOURCE },
181
+ locator: { path: stdout.path },
182
+ taskId,
183
+ observation: { command: `verify sha256:${digest} argc:${argv.length}`, exitCode },
184
+ establishes: [],
185
+ freshness: { gitCommit: head },
186
+ });
187
+ ledger.save(taskId);
188
+
189
+ return { outputId, argvSha256: digest, head, exitCode, timedOut, error, durationMs, evidenceId, cleanup };
190
+ }
191
+
192
+ /**
193
+ * IC-032 lease rule: ends a recorded verification group `{ pid, pgid, start }` through 081's
194
+ * `escalate`, with the recorded leader start, so a null start or a failed `ps` signals no group
195
+ * (081 IC-021 step 3). Resolves `escalate`'s `{ status, reason, survivors, zombies }`.
196
+ */
197
+ function endVerification(proc , { platform = process.platform, escalate = processTree.escalate } = {}) {
198
+ return escalate({ proc: { pid: proc?.pid, pgid: proc?.pgid, start: proc?.start ?? null }, platform });
199
+ }
200
+
201
+ module.exports = { REPO_ENV, argvDigest, runVerification, endVerification };
@@ -0,0 +1,39 @@
1
+ 'use strict';
2
+
3
+ // TypeScript migration (agent-docs/BRIEF_GOAL_DOFLOW.md): run directly by Node native type-stripping.
4
+
5
+ /** The names a retrieval score arrives under, in camelCase (`--relevance-score` normalises here).
6
+ * Exported because `bin/doflow.js` refuses the same names on the command line, and because
7
+ * `retrieval-plan.ts` and `outcome.ts` refuse them on the option object a declared plan and a
8
+ * terminal record are built from: one definition, three input surfaces (argv, a `--batch` JSON
9
+ * file, and a verb's own options), each with exactly one enforcement point. A second copy of
10
+ * this list is how one of them drifts. */
11
+ const EVIDENCE_SCORE_FIELDS = new Set([
12
+ 'confidence', 'score', 'relevance', 'relevanceScore', 'similarity',
13
+ 'certainty', 'probability', 'rank', 'distance', 'weight',
14
+ ]);
15
+
16
+ /** The one refusal message for a score offered as a property of a fact. */
17
+ function scoreFieldRefusal(name ) {
18
+ return `'${name}' is a retrieval score and this boundary records none (FR-007). Relevance is a `
19
+ + 'property of a search, not of a fact: stored beside the fact it is read as confidence by the '
20
+ + 'next worker, which is the specific interpretation the requirement forbids. Record the '
21
+ + 'locator and the content instead — a reader who can see where a fact came from does not need '
22
+ + 'to be told how sure to be.';
23
+ }
24
+
25
+ /** Refuses a score-shaped key anywhere in an item, including nested under source or locator. */
26
+ function assertNoScoreFields(value , prefix ) {
27
+ if (!value || typeof value !== 'object' || Array.isArray(value)) return;
28
+ for (const [key, inner] of Object.entries(value )) {
29
+ const dotted = prefix ? `${prefix}.${key}` : key;
30
+ if (EVIDENCE_SCORE_FIELDS.has(key)) throw new Error(scoreFieldRefusal(dotted));
31
+ assertNoScoreFields(inner, dotted);
32
+ }
33
+ }
34
+
35
+ module.exports = {
36
+ EVIDENCE_SCORE_FIELDS,
37
+ scoreFieldRefusal,
38
+ assertNoScoreFields,
39
+ };