@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
@@ -22,10 +22,12 @@
22
22
  */
23
23
 
24
24
  const path = require('node:path');
25
+ const os = require('node:os');
25
26
  const nodeFs = require('node:fs');
26
27
  const { spawnSync } = require('node:child_process');
27
28
  const { detectCommands, applyTargetPattern, TARGET_PATTERN_TOKEN } = require('../command-detect.js');
28
- const { selectChangedTests } = require('./test-selection.js');
29
+ const { selectChangedTests, formatSelectedTestPattern, testFilesFor, conventionFor, quotePath } = require('./test-selection.js');
30
+ const { selectImpactedTests } = require('./impact.js');
29
31
  const { RecoveryManager } = require('../recovery.js');
30
32
  const { finishRuntime, usageError } = require('../cli-result.js');
31
33
  const { REPO_ROOT } = require('../../helper/repo-root.js');
@@ -37,6 +39,9 @@ const { readReadinessRecord, RECORD_VERSION } = require('../readiness-record.js'
37
39
  const { verifyReadinessCheck, featureSlugFor } = require('../implementation-gate.js');
38
40
  const { assertSafeTaskId } = require('../evidence-ledger.js');
39
41
  const { upkeepAfterVerify } = require('../storage/upkeep.js');
42
+ const { emitVerifyTelemetry } = require('./telemetry-emit.js');
43
+ const { resolveRunsLocation, outerProjectRoot } = require('../trace/ledger.js');
44
+ const { projectRoot: lifecycleProjectRoot } = require('../lifecycle/root.js');
40
45
  const {
41
46
  VerificationContractRunner,
42
47
  FATAL_CHECK_MARKERS,
@@ -49,8 +54,30 @@ const {
49
54
  TIER_STATUSES,
50
55
  RECOVERY_OUTCOMES,
51
56
  REGISTRY_FILENAME,
57
+ v0Enabled,
58
+ v0Applies,
59
+ levelOf,
52
60
  } = require('./registry.js');
53
61
 
62
+ const sha16 = (text ) => nodeCrypto.createHash('sha256').update(String(text), 'utf8').digest('hex').slice(0, 16);
63
+
64
+ /**
65
+ * Saves a failed-first state file atomically via temp file + rename with mode 0600.
66
+ * If checks is empty, any existing state file is removed (IC-016).
67
+ */
68
+ function saveFailedFirstFile(stateDir , stateFile , state , fsImpl = nodeFs) {
69
+ try {
70
+ if (!state || !state.checks || Object.keys(state.checks).length === 0) {
71
+ try { fsImpl.unlinkSync(stateFile); } catch {}
72
+ return;
73
+ }
74
+ fsImpl.mkdirSync(stateDir, { recursive: true });
75
+ const tmp = path.join(stateDir, `.failed-first.${nodeCrypto.randomBytes(6).toString('hex')}.tmp`);
76
+ fsImpl.writeFileSync(tmp, JSON.stringify(state, null, 2) + '\n', { mode: 0o600 });
77
+ fsImpl.renameSync(tmp, stateFile);
78
+ } catch {}
79
+ }
80
+
54
81
  // `handleVerifyCommand` (moved from bin/doflow.js) used bin/doflow.js's own REPO_ROOT — that file's
55
82
  // SCRIPT_DIR is bin/, so its repo root is path.dirname(SCRIPT_DIR). This module takes the same root
56
83
  // from src/helper/repo-root.js rather than counting its own depth.
@@ -104,6 +131,11 @@ class VerificationEngine {
104
131
  policy ;
105
132
  runner ;
106
133
  recoveryOverride ;
134
+ v0 ;
135
+ /** 089 IC-005: the `verify.reuse` setting, resolved once by the verb; false unless injected. */
136
+ reuse ;
137
+ platform ;
138
+ configDir ;
107
139
 
108
140
  /**
109
141
  * @param {Object} [options]
@@ -114,12 +146,19 @@ class VerificationEngine {
114
146
  * @param {Function} [options.exec] injection seam for shell execution; `spawnSync` contract
115
147
  * @param {Function} [options.clock] injection seam returning epoch milliseconds
116
148
  * @param {RecoveryManager} [options.recovery] overrides the per-risk-level manager
149
+ * @param {string} [options.logDir] where failing checks' full logs go (`verifyStateRoot`)
150
+ * @param {string} [options.platform] platform used for command execution
151
+ * @param {string} [options.configDir] resolved verification state store
152
+ * @param {boolean} [options.v0] whether V0 behavior is enabled (IC-015)
153
+ * @param {boolean} [options.reuse] whether recorded PASSes are stored and reused (089 IC-005)
117
154
  */
118
155
  constructor(options = {}) {
119
156
  this.cwd = options.cwd || process.cwd();
120
157
  this.fsImpl = options.fsImpl || nodeFs;
121
158
  this.exec = options.exec || spawnSync;
122
159
  this.clock = options.clock || Date.now;
160
+ this.platform = options.platform || process.platform;
161
+ this.configDir = options.configDir || (options.logDir ? path.resolve(options.logDir, '..', '..', '..') : path.join(this.cwd, '.doflow'));
123
162
  this.registry = loadVerificationRegistry({
124
163
  repoRoot: options.repoRoot,
125
164
  registryDir: options.registryDir,
@@ -128,8 +167,16 @@ class VerificationEngine {
128
167
  fsImpl: this.fsImpl,
129
168
  });
130
169
  this.policy = this.registry.policy || {};
131
- this.runner = new VerificationContractRunner({ cwd: this.cwd, exec: this.exec });
170
+ this.runner = new VerificationContractRunner({
171
+ cwd: this.cwd,
172
+ exec: this.exec,
173
+ logDir: options.logDir,
174
+ platform: this.platform,
175
+ fsImpl: this.fsImpl,
176
+ });
132
177
  this.recoveryOverride = options.recovery || null;
178
+ this.v0 = options.v0 !== undefined ? Boolean(options.v0) : true;
179
+ this.reuse = options.reuse === true;
133
180
  }
134
181
 
135
182
  /** @returns {string} ISO timestamp from the injected clock */
@@ -236,7 +283,8 @@ class VerificationEngine {
236
283
  if (!required && !advisory) return base;
237
284
 
238
285
  if (tier.source === 'detected-command') {
239
- const selectorContext = { changedFiles: input.changedFiles, scope, root: detection.projectRoot || input.projectRoot || this.cwd };
286
+ const v0Active = Boolean(this.v0 && v0Applies(this.registry, riskLevel));
287
+ const selectorContext = { changedFiles: input.changedFiles, scope, root: detection.projectRoot || input.projectRoot || this.cwd, v0Active, riskLevel };
240
288
  return Object.assign(base, this.resolveCommandTier(tier, detection, declaredNone, absent, input.targetPattern, selectorContext));
241
289
  }
242
290
  if (tier.source === 'caller-supplied' && tier.id === 'structural-invariants') {
@@ -293,6 +341,7 @@ class VerificationEngine {
293
341
  .filter((t ) => t.required && t.resolution === 'UNRESOLVED')
294
342
  .map((t ) => ({ tier: t.id, reason: t.reason }));
295
343
 
344
+ const v0Active = Boolean(this.v0 && v0Applies(this.registry, riskLevel));
296
345
  return {
297
346
  version: this.registry.version,
298
347
  taskId: input.taskId,
@@ -313,6 +362,7 @@ class VerificationEngine {
313
362
  declaredNone: detection.declaredNone,
314
363
  errors: detection.errors,
315
364
  },
365
+ ...(v0Active ? { testTargeted: detection?.commands?.testTargeted || null } : {}),
316
366
  scope: {
317
367
  maxFiles: Number.isFinite(scope.maxFiles) ? scope.maxFiles : null,
318
368
  allowedPaths: toStringArray(scope.allowedPaths),
@@ -369,6 +419,47 @@ class VerificationEngine {
369
419
  } else {
370
420
  selection = { basis: 'none', selectedCount: null, totalCount: null, fallback: 'not-scopable' };
371
421
  }
422
+
423
+ // Feature 090 (IC-003): when v0 is active and files changed, augment with static impact analysis
424
+ const v0Active = ctx.v0Active !== undefined ? ctx.v0Active : Boolean(this.v0);
425
+ if (v0Active && observation && Array.isArray(observation.files) && observation.files.length > 0) {
426
+ const conv = conventionFor(found);
427
+ if (conv) {
428
+ const hasNonTest = observation.files.some((f ) => !conv.isTest(f));
429
+ const noChangedTests = selected && selected.pattern === null && selected.selection?.fallback === 'no-changed-tests';
430
+ if (hasNonTest || noChangedTests) {
431
+ const impact = selectImpactedTests({
432
+ root: ctx.root || this.cwd,
433
+ found,
434
+ changedFiles: observation.files,
435
+ fsImpl: this.fsImpl,
436
+ });
437
+ if (impact && impact.mode === 'selected' && impact.tests.length > 0 && impact.tests.length < impact.totalCount) {
438
+ const directlyChanged = observation.files.filter((f ) => conv.isTest(f));
439
+ const combined = [...new Set([...directlyChanged, ...impact.tests])].sort();
440
+ pattern = formatSelectedTestPattern({
441
+ selectedTests: combined,
442
+ guardDir: conv.guardDir,
443
+ platform: process.platform,
444
+ });
445
+ selection = {
446
+ basis: 'impacted-tests',
447
+ selectedCount: combined.length,
448
+ totalCount: impact.totalCount,
449
+ fallback: null,
450
+ };
451
+ } else if (impact && impact.mode === 'full') {
452
+ pattern = null;
453
+ selection = {
454
+ basis: 'none',
455
+ selectedCount: null,
456
+ totalCount: impact.totalCount,
457
+ fallback: 'unknown-files',
458
+ };
459
+ }
460
+ }
461
+ }
462
+ }
372
463
  }
373
464
  }
374
465
  const { command, substituted } = applyTargetPattern(found.command, pattern);
@@ -638,11 +729,83 @@ class VerificationEngine {
638
729
  let fatalTier = null;
639
730
  let checksRun = 0;
640
731
  let checksPassed = 0;
732
+ let checksReused = 0;
641
733
  // One evaluation pass, one cache: two tiers declaring the same command (targeted and broad
642
734
  // tests are often both `npm test`) run it once and share the result. A recovery retry calls
643
735
  // runContract again and gets a fresh cache, so retries always observe live behaviour.
644
736
  const dedupeCache = new Map ();
645
737
 
738
+ const v0Active = Boolean(this.v0 && v0Applies(this.registry, contract.riskLevel));
739
+ if (v0Active || this.reuse) {
740
+ // 089 IC-005: the runner decides reuse eligibility per level; a recovery retry comes back here.
741
+ this.runner.beginContract({
742
+ v0: this.v0,
743
+ reuse: this.reuse,
744
+ registry: this.registry,
745
+ riskLevel: contract.riskLevel,
746
+ configDir: this.configDir,
747
+ });
748
+ } else {
749
+ // A reused engine must not carry L1/L2 sharing or reuse into an L3 or flag-off contract.
750
+ this.runner.inflightContext = undefined;
751
+ this.runner.reuseContext = undefined;
752
+ this.runner.reuseStore = undefined;
753
+ }
754
+ let taskIdValid = false;
755
+ if (typeof contract.taskId === 'string') {
756
+ try {
757
+ assertSafeTaskId(contract.taskId);
758
+ taskIdValid = true;
759
+ } catch {
760
+ taskIdValid = false;
761
+ }
762
+ }
763
+
764
+ const stateDir = path.join(this.configDir, 'state', 'verification', 'v0');
765
+ const stateFile = taskIdValid ? path.join(stateDir, `failed-first-${contract.taskId}.json`) : '';
766
+ let realRootSha = '';
767
+ try {
768
+ const real = typeof this.fsImpl.realpathSync === 'function'
769
+ ? this.fsImpl.realpathSync(this.cwd)
770
+ : path.resolve(this.cwd);
771
+ realRootSha = sha16(real);
772
+ } catch {
773
+ realRootSha = sha16(path.resolve(this.cwd));
774
+ }
775
+
776
+ let failedFirstState = null;
777
+ if (v0Active && taskIdValid) {
778
+ try {
779
+ const content = this.fsImpl.readFileSync(stateFile, 'utf8');
780
+ const parsed = JSON.parse(content);
781
+ if (parsed && parsed.v === 1 && parsed.cwd === realRootSha && parsed.checks && typeof parsed.checks === 'object') {
782
+ failedFirstState = parsed;
783
+ }
784
+ } catch {
785
+ failedFirstState = null;
786
+ }
787
+ }
788
+ let stateDirty = false;
789
+
790
+ const hasTargeted = Object.hasOwn(contract, 'testTargeted');
791
+ let foundTargeted = hasTargeted ? contract.testTargeted : contract.detection?.commands?.testTargeted;
792
+ if (!hasTargeted && !foundTargeted && v0Active && taskIdValid) {
793
+ try {
794
+ const det = detectCommands({ projectRoot: this.cwd, fsImpl: this.fsImpl });
795
+ foundTargeted = det?.commands?.testTargeted;
796
+ } catch {}
797
+ }
798
+ const convention = (foundTargeted && typeof foundTargeted.command === 'string' && foundTargeted.command.includes(TARGET_PATTERN_TOKEN))
799
+ ? conventionFor(foundTargeted)
800
+ : null;
801
+
802
+ if (convention && this.runner) {
803
+ this.runner.convention = convention;
804
+ }
805
+
806
+ let failedFirstAttempted = false;
807
+ let failedFirstHalted = false;
808
+
646
809
  for (const tier of contract.tiers) {
647
810
  const entry = {
648
811
  id: tier.id,
@@ -671,6 +834,23 @@ class VerificationEngine {
671
834
  continue;
672
835
  }
673
836
 
837
+ if (failedFirstHalted && tier.kind === 'behavioral') {
838
+ entry.status = 'NOT_RUN';
839
+ entry.reason = 'failed-first-still-failing';
840
+ for (const check of tier.checks) {
841
+ entry.checks.push({
842
+ name: check.name,
843
+ tier: tier.id,
844
+ role: check.role,
845
+ command: check.command,
846
+ status: 'NOT_RUN',
847
+ reason: 'failed-first-still-failing',
848
+ });
849
+ }
850
+ reportTiers.push(entry);
851
+ continue;
852
+ }
853
+
674
854
  if (tier.modelBased) {
675
855
  Object.assign(entry, this.evaluateModelTier(tier, runtimeInput.modelReview));
676
856
  reportTiers.push(entry);
@@ -684,14 +864,147 @@ class VerificationEngine {
684
864
  }
685
865
 
686
866
  for (const check of tier.checks) {
867
+ if (failedFirstHalted && tier.kind === 'behavioral') {
868
+ entry.checks.push({
869
+ name: check.name,
870
+ tier: tier.id,
871
+ role: check.role,
872
+ command: check.command,
873
+ status: 'NOT_RUN',
874
+ reason: 'failed-first-still-failing',
875
+ });
876
+ continue;
877
+ }
878
+
879
+ let runNarrowed = false;
880
+ let matchedFiles = [];
881
+ let narrowedCommand = null;
882
+ let narrowedStrikes = 0;
883
+
884
+ if (v0Active && taskIdValid && convention && !failedFirstAttempted && tier.kind === 'behavioral') {
885
+ try {
886
+ const savedCheck = failedFirstState?.checks?.[check.name]
887
+ || (failedFirstState?.checks ? Object.values(failedFirstState.checks).find((c ) => c && c.cmd === sha16(check.command)) : null);
888
+ const cmdChanged = savedCheck ? savedCheck.cmd !== sha16(check.command) : false;
889
+ const effectiveStrikes = cmdChanged ? 0 : (Number.isSafeInteger(savedCheck?.strikes) && savedCheck.strikes >= 0 ? savedCheck.strikes : 0);
890
+ // A file can fail in isolation while the full suite passes (for example, suite setup).
891
+ // After two narrowed failures, recheck the full scope so saved state cannot pin FAIL.
892
+ if (savedCheck && effectiveStrikes < 2 && Array.isArray(savedCheck.files) && savedCheck.files.length > 0) {
893
+ const universeFiles = testFilesFor(foundTargeted, this.cwd, this.fsImpl);
894
+ matchedFiles = universeFiles.filter((f ) => savedCheck.files.includes(sha16(f)));
895
+ if (matchedFiles.length > 0) {
896
+ const pattern = matchedFiles.map((f ) => quotePath(f, this.platform || process.platform)).join(' ');
897
+ const sub = applyTargetPattern(foundTargeted.command, pattern);
898
+ if (sub.substituted && sub.command) {
899
+ narrowedCommand = sub.command;
900
+ runNarrowed = true;
901
+ narrowedStrikes = effectiveStrikes;
902
+ }
903
+ }
904
+ }
905
+ } catch {
906
+ runNarrowed = false;
907
+ }
908
+ }
909
+
910
+ if (runNarrowed && narrowedCommand) {
911
+ failedFirstAttempted = true;
912
+ const narrowedResult = this.runner.runCheck(check.name, narrowedCommand, check.timeoutMs, null);
913
+ if (narrowedResult.status === 'FAIL') {
914
+ narrowedResult.tier = tier.id;
915
+ narrowedResult.role = check.role;
916
+ if (check.requirementId) narrowedResult.requirementId = check.requirementId;
917
+ narrowedResult.failedFirst = { ran: true, status: 'FAIL', files: matchedFiles.length };
918
+ entry.checks.push(narrowedResult);
919
+ checksRun += 1;
920
+ failedChecks.push(narrowedResult.name);
921
+ failedFirstHalted = true;
922
+
923
+ const metric = this.runner.metrics.get(narrowedResult);
924
+ if (metric) metric.failedFirst = 'fail';
925
+
926
+ if (v0Active && taskIdValid) {
927
+ const filesToSave = narrowedResult.failedFiles && narrowedResult.failedFiles.length > 0
928
+ ? narrowedResult.failedFiles
929
+ : matchedFiles;
930
+ if (filesToSave && filesToSave.length > 0) {
931
+ if (!failedFirstState) failedFirstState = { v: 1, cwd: realRootSha, checks: {} };
932
+ failedFirstState.checks[check.name] = {
933
+ cmd: sha16(check.command),
934
+ files: filesToSave.map(sha16),
935
+ strikes: narrowedStrikes + 1,
936
+ };
937
+ stateDirty = true;
938
+ }
939
+ }
940
+ continue;
941
+ } else {
942
+ // Narrowed PASS: the full check runs and alone decides
943
+ const fullResult = this.runner.runCheck(check.name, check.command, check.timeoutMs, dedupeCache);
944
+ fullResult.tier = tier.id;
945
+ fullResult.role = check.role;
946
+ if (check.requirementId) fullResult.requirementId = check.requirementId;
947
+ fullResult.failedFirst = { ran: true, status: 'PASS', files: matchedFiles.length };
948
+ entry.checks.push(fullResult);
949
+ checksRun += 1;
950
+ if (fullResult.reused) checksReused += 1;
951
+ if (fullResult.status === 'PASS') checksPassed += 1;
952
+ else failedChecks.push(fullResult.name);
953
+
954
+ const metric = this.runner.metrics.get(fullResult);
955
+ if (metric) metric.failedFirst = 'pass';
956
+
957
+ if (v0Active && taskIdValid) {
958
+ if (fullResult.status === 'PASS') {
959
+ if (failedFirstState?.checks?.[check.name]) {
960
+ delete failedFirstState.checks[check.name];
961
+ stateDirty = true;
962
+ }
963
+ } else if (fullResult.status === 'FAIL') {
964
+ if (fullResult.failedFiles && fullResult.failedFiles.length > 0) {
965
+ if (!failedFirstState) failedFirstState = { v: 1, cwd: realRootSha, checks: {} };
966
+ failedFirstState.checks[check.name] = {
967
+ cmd: sha16(check.command),
968
+ files: fullResult.failedFiles.map(sha16),
969
+ };
970
+ stateDirty = true;
971
+ }
972
+ }
973
+ }
974
+ continue;
975
+ }
976
+ }
977
+
687
978
  const result = this.runner.runCheck(check.name, check.command, check.timeoutMs, dedupeCache);
688
979
  result.tier = tier.id;
689
980
  result.role = check.role;
690
981
  if (check.requirementId) result.requirementId = check.requirementId;
691
982
  entry.checks.push(result);
692
983
  checksRun += 1;
984
+ if (result.reused) checksReused += 1;
693
985
  if (result.status === 'PASS') checksPassed += 1;
694
986
  else failedChecks.push(result.name);
987
+
988
+ const metric = this.runner.metrics.get(result);
989
+ if (metric) metric.failedFirst = 'none';
990
+
991
+ if (v0Active && taskIdValid && tier.kind === 'behavioral') {
992
+ if (result.status === 'FAIL') {
993
+ if (result.failedFiles && result.failedFiles.length > 0) {
994
+ if (!failedFirstState) failedFirstState = { v: 1, cwd: realRootSha, checks: {} };
995
+ failedFirstState.checks[check.name] = {
996
+ cmd: sha16(check.command),
997
+ files: result.failedFiles.map(sha16),
998
+ };
999
+ stateDirty = true;
1000
+ }
1001
+ } else if (result.status === 'PASS') {
1002
+ if (failedFirstState?.checks?.[check.name]) {
1003
+ delete failedFirstState.checks[check.name];
1004
+ stateDirty = true;
1005
+ }
1006
+ }
1007
+ }
695
1008
  }
696
1009
 
697
1010
  const anyFailed = entry.checks.some((c ) => c.status === 'FAIL');
@@ -712,6 +1025,10 @@ class VerificationEngine {
712
1025
  reportTiers.push(entry);
713
1026
  }
714
1027
 
1028
+ if (stateDirty && v0Active && taskIdValid) {
1029
+ saveFailedFirstFile(stateDir, stateFile, failedFirstState, this.fsImpl);
1030
+ }
1031
+
715
1032
  const requiredTiers = reportTiers.filter((t) => t.required);
716
1033
  const requiredFailed = requiredTiers.filter((t) => t.status === 'FAIL');
717
1034
  const requiredUnverified = requiredTiers.filter((t) => t.status === 'UNRESOLVED' || t.status === 'NOT_RUN');
@@ -760,6 +1077,7 @@ class VerificationEngine {
760
1077
  checksRun,
761
1078
  checksPassed,
762
1079
  checksFailed: checksRun - checksPassed,
1080
+ ...(this.reuse ? { checksReused } : {}),
763
1081
  tiersRequired: requiredTiers.length,
764
1082
  tiersPassed: requiredTiers.filter((t) => t.status === 'PASS').length,
765
1083
  tiersFailed: requiredFailed.length,
@@ -912,7 +1230,23 @@ class VerificationEngine {
912
1230
  failureSignature(report ) {
913
1231
  const failed = [...(report.failedChecks || [])].sort().join('|');
914
1232
  const unresolved = (report.unresolved || []).map((u ) => u.tier).sort().join('|');
915
- return `${report.status}::${failed}::${unresolved}`;
1233
+ const failedFiles = new Set ();
1234
+ const failedTests = new Set ();
1235
+ for (const tier of (report.tiers || [])) {
1236
+ for (const check of (tier.checks || [])) {
1237
+ if (check.status === 'FAIL') {
1238
+ if (Array.isArray(check.failedFiles)) {
1239
+ for (const f of check.failedFiles) failedFiles.add(f);
1240
+ }
1241
+ if (Array.isArray(check.failedTests)) {
1242
+ for (const t of check.failedTests) failedTests.add(t);
1243
+ }
1244
+ }
1245
+ }
1246
+ }
1247
+ const filesPart = [...failedFiles].sort().join('|');
1248
+ const testsPart = [...failedTests].sort().join('|');
1249
+ return `${report.status}::${failed}::${unresolved}::${filesPart}::${testsPart}`;
916
1250
  }
917
1251
 
918
1252
  /**
@@ -1260,7 +1594,136 @@ function validateVerificationInput({ cwd, verificationInput, fs = nodeFs, crypto
1260
1594
  * @param {string} [options.baselineRef] commit or ref to measure change against (IC-022)
1261
1595
  * @returns {number} exit code
1262
1596
  */
1263
- function handleVerifyCommand({ taskId, action = 'report', risk, planPath, json = false, projectRoot, slug = null, scope = null, verificationInput = null, verificationPhase = null, baselineRef = null }
1597
+ /**
1598
+ * Where verification keeps its own state (check logs, the verify-moment upkeep): the store the
1599
+ * lifecycle owns — `DOFLOW_CONFIG_DIR`, else the nearest existing `.doflow` above the main checkout,
1600
+ * else the install this runtime was copied into, else an existing `~/.doflow`. Only when none
1601
+ * exists (a project never set up) is it the owner's `.doflow`.
1602
+ * A `.doflow` created under the working directory of a dispatch attempt worktree captured every
1603
+ * later record from there and was deleted with the worktree (088 G-O1 D4).
1604
+ * @param {Object} [options]
1605
+ * @param {string} [options.start] the verify's working directory (default: cwd)
1606
+ * @param {Object} [options.env=process.env]
1607
+ * @param {string} [options.homeDir]
1608
+ * @param {string|null} [options.installRoot] this runtime's package root
1609
+ * @param {Object} [options.fsImpl]
1610
+ * @returns {{configDir: string, projectRoot: string|null}} `projectRoot` is the directory holding
1611
+ * `configDir` when it is named `.doflow` (the root the storage upkeep takes), else null
1612
+ */
1613
+ function verifyStateRoot({ start, env = process.env, homeDir = os.homedir(), installRoot = REPO_ROOT, fsImpl = nodeFs } = {}) {
1614
+ // Match worker/lifecycle state ownership before looking for a store. A sibling linked checkout
1615
+ // cannot walk up to the main checkout, and a nested .doflow is never a store root (110/111).
1616
+ const from = lifecycleProjectRoot(outerProjectRoot(path.resolve(start || process.cwd())));
1617
+ const isDir = (dir ) => { try { return fsImpl.statSync(dir).isDirectory(); } catch { return false; } };
1618
+ const at = resolveRunsLocation({ start: from, env, homeDir, fsImpl });
1619
+ let configDir = path.join(from, '.doflow');
1620
+ if (at.origin === 'DOFLOW_CONFIG_DIR' || at.origin === 'project') configDir = at.configDir;
1621
+ else {
1622
+ const marker = `${path.sep}.doflow${path.sep}`;
1623
+ const cut = installRoot ? `${path.resolve(installRoot)}${path.sep}`.indexOf(marker) : -1;
1624
+ const install = cut >= 0 ? path.resolve(installRoot).slice(0, cut) + path.sep + '.doflow' : null;
1625
+ if (install && isDir(install)) configDir = install;
1626
+ else if (isDir(at.configDir)) configDir = at.configDir; // ~/.doflow
1627
+ }
1628
+ return { configDir, projectRoot: path.basename(configDir) === '.doflow' ? path.dirname(configDir) : null };
1629
+ }
1630
+
1631
+ const VERIFY_GIT_TIMEOUT_MS = 2000;
1632
+ const VERIFY_GIT_ENV = ['GIT_DIR', 'GIT_WORK_TREE', 'GIT_INDEX_FILE', 'GIT_COMMON_DIR', 'GIT_OBJECT_DIRECTORY'];
1633
+
1634
+ /** Capture the checkout head and status before verification commands run. A moving head is unbound. */
1635
+ function verificationGitState(cwd , env = process.env) {
1636
+ const gitEnv = { ...env };
1637
+ for (const key of VERIFY_GIT_ENV) delete gitEnv[key];
1638
+ const git = (args ) => spawnSync('git', args, {
1639
+ cwd,
1640
+ env: gitEnv,
1641
+ encoding: 'utf8',
1642
+ timeout: VERIFY_GIT_TIMEOUT_MS,
1643
+ stdio: ['ignore', 'pipe', 'ignore'],
1644
+ });
1645
+ try {
1646
+ const before = git(['rev-parse', '--verify', 'HEAD']);
1647
+ if (before.status !== 0) return null;
1648
+ const head = before.stdout.trim();
1649
+ if (!/^[0-9a-f]{40}$/.test(head)) return null;
1650
+ const status = git(['--no-optional-locks', 'status', '--porcelain=v1', '-z', '--untracked-files=all']);
1651
+ const after = git(['rev-parse', '--verify', 'HEAD']);
1652
+ if (after.status !== 0 || after.stdout.trim() !== head) return null;
1653
+ return {
1654
+ head,
1655
+ clean: status.status === 0 && status.stdout.length === 0,
1656
+ projectRoot: verificationRunsRoot(cwd),
1657
+ };
1658
+ } catch {
1659
+ return null;
1660
+ }
1661
+ }
1662
+
1663
+ /** IC-007: the checkout whose state holds the run records of a verify run in `cwd` (the lifecycle root). */
1664
+ function verificationRunsRoot(cwd ) {
1665
+ return lifecycleProjectRoot(outerProjectRoot(path.resolve(cwd)));
1666
+ }
1667
+
1668
+ /** IC-007: the one file the run records of `taskId` are appended to, under `verificationRunsRoot`. */
1669
+ function verificationRunRecordFile(root , taskId ) {
1670
+ return path.join(root, '.doflow', 'state', 'verification', 'runs', `${taskId}.jsonl`);
1671
+ }
1672
+
1673
+ /** 089 IC-006: how many checks of a report carry the `reused` label. */
1674
+ function reusedCount(report ) {
1675
+ if (typeof report?.counts?.checksReused === 'number') return report.counts.checksReused;
1676
+ let count = 0;
1677
+ for (const tier of Array.isArray(report?.tiers) ? report.tiers : []) {
1678
+ for (const check of Array.isArray(tier?.checks) ? tier.checks : []) {
1679
+ if (check && typeof check === 'object' && check.reused) count += 1;
1680
+ }
1681
+ }
1682
+ return count;
1683
+ }
1684
+
1685
+ /** Persist the already printed report as one JSONL line; state I/O never changes its answer.
1686
+ * 089 IC-006: with reuse on for the run, `reused` (the count) precedes `reportSha256`; with reuse
1687
+ * off the record has no `reused` key. 095b IC-004: `risk` (the report's risk level) follows `verdict`;
1688
+ * readers default a missing one, and `verifyRecordReasons` ignores it. */
1689
+ function appendVerificationRunRecord({ taskId, report, gitState, reuse = false, fsImpl = nodeFs } ) {
1690
+ if (!gitState) return;
1691
+ try {
1692
+ const reportJson = JSON.stringify(report, null, 2);
1693
+ const record = {
1694
+ schema: 1,
1695
+ taskId,
1696
+ head: gitState.head,
1697
+ clean: gitState.clean,
1698
+ verdict: report.status,
1699
+ risk: report.riskLevel,
1700
+ at: new Date().toISOString(),
1701
+ ...(reuse === true ? { reused: reusedCount(report) } : {}),
1702
+ reportSha256: `sha256:${nodeCrypto.createHash('sha256').update(reportJson, 'utf8').digest('hex')}`,
1703
+ };
1704
+ const file = verificationRunRecordFile(gitState.projectRoot, taskId);
1705
+ fsImpl.mkdirSync(path.dirname(file), { recursive: true });
1706
+ fsImpl.appendFileSync(file, `${JSON.stringify(record)}\n`, { encoding: 'utf8', mode: 0o600 });
1707
+ } catch {
1708
+ // Reporting is authoritative. A read-only or unavailable state directory is only a lost record.
1709
+ }
1710
+ }
1711
+
1712
+ /**
1713
+ * 089 IC-005: the `verify.reuse` setting, read once per verify command. Only a valid resolved `on`
1714
+ * enables reuse; an invalid value, a load error or any fault is the key's fail-safe `off`.
1715
+ */
1716
+ function readVerifyReuse(env = process.env) {
1717
+ try {
1718
+ const { loadSettings } = require('../settings/resolve.js');
1719
+ const resolution = loadSettings({ global: true, env: { ...env } }).get('verify.reuse', 'global');
1720
+ return resolution?.state === 'ok' && resolution.value === 'on';
1721
+ } catch {
1722
+ return false;
1723
+ }
1724
+ }
1725
+
1726
+ function handleVerifyCommand({ taskId, action = 'report', risk, planPath, json = false, projectRoot, slug = null, scope = null, verificationInput = null, verificationPhase = null, baselineRef = null, env = process.env, fsImpl = nodeFs }
1264
1727
 
1265
1728
 
1266
1729
 
@@ -1272,6 +1735,8 @@ function handleVerifyCommand({ taskId, action = 'report', risk, planPath, json =
1272
1735
 
1273
1736
 
1274
1737
 
1738
+
1739
+
1275
1740
  = {}) {
1276
1741
  // The task id names state files here, so it obeys the rule every other task verb applies.
1277
1742
  try {
@@ -1334,10 +1799,13 @@ function handleVerifyCommand({ taskId, action = 'report', risk, planPath, json =
1334
1799
  if (!declared) declared = { paths: [...recordedScope], origin: 'readiness-record', record: recordRel };
1335
1800
  }
1336
1801
 
1802
+ const state = verifyStateRoot({ start: cwd, env, fsImpl });
1803
+ const v0 = v0Enabled(env, state.configDir, fsImpl);
1804
+ const reuse = readVerifyReuse(env);
1337
1805
  let engine ;
1338
1806
  let contract ;
1339
1807
  try {
1340
- engine = new VerificationEngine({ cwd, repoRoot: REPO_ROOT });
1808
+ engine = new VerificationEngine({ cwd, repoRoot: REPO_ROOT, logDir: path.join(state.configDir, 'state', 'verification', 'logs'), configDir: state.configDir, v0, reuse, fsImpl });
1341
1809
  // The bound comes from the feature's plan, this checkout's or the main checkout's, and from a
1342
1810
  // declared scope; with neither the change-scope tier is UNRESOLVED and says where it looked.
1343
1811
  const resolved = resolveScopeBound({ projectRoot: cwd, slug, planPath, declared, baselineRef });
@@ -1389,6 +1857,7 @@ function handleVerifyCommand({ taskId, action = 'report', risk, planPath, json =
1389
1857
  return usageError('verify', `unknown --action '${action}'. Valid: report (default), contract`, json);
1390
1858
  }
1391
1859
 
1860
+ const runGitState = action === 'report' ? verificationGitState(cwd, env) : null;
1392
1861
  const report = engine.runContract(contract);
1393
1862
  // A task whose run still has its gated stage pending is not verified while no READY readiness
1394
1863
  // record exists: the checks passing says nothing about whether the work should have started.
@@ -1414,9 +1883,21 @@ function handleVerifyCommand({ taskId, action = 'report', risk, planPath, json =
1414
1883
  if (boundLines.has(tier.id)) console.log(boundLines.get(tier.id));
1415
1884
  if (tier.reason) console.log(` ${tier.reason}`);
1416
1885
  if (tier.selection) console.log(` ${selectionText(tier.selection)}`);
1886
+ if (Array.isArray(tier.checks)) {
1887
+ for (const check of tier.checks) {
1888
+ if (check?.reused) {
1889
+ const atIso = typeof check.reused.recordedAt === 'number'
1890
+ ? new Date(check.reused.recordedAt).toISOString()
1891
+ : String(check.reused.recordedAt);
1892
+ console.log(` reused ${atIso} ref ${check.reused.ref}`);
1893
+ }
1894
+ }
1895
+ }
1417
1896
  }
1418
1897
  console.log('─'.repeat(78));
1419
- console.log(`Checks: ${report.counts.checksPassed}/${report.counts.checksRun} passed`);
1898
+ const reused = report.counts?.checksReused ?? reusedCount(report);
1899
+ const reusedSuffix = reused > 0 ? ` (${reused} reused, not executed)` : '';
1900
+ console.log(`Checks: ${report.counts.checksPassed}/${report.counts.checksRun} passed${reusedSuffix}`);
1420
1901
  if (report.unresolved.length) {
1421
1902
  console.log('Never evaluated (not a pass):');
1422
1903
  for (const u of report.unresolved) console.log(` ${u.tier}${u.required ? ' [required]' : ''} — ${u.reason}`);
@@ -1425,8 +1906,12 @@ function handleVerifyCommand({ taskId, action = 'report', risk, planPath, json =
1425
1906
  }
1426
1907
  // PASS is the only status that answers "verified". FAIL and INCONCLUSIVE are both findings, and
1427
1908
  // collapsing INCONCLUSIVE into success would report a verdict over zero evidence as a pass.
1428
- upkeepAfterVerify({ projectRoot: cwd });
1429
- return finishRuntime(report.status === 'PASS' ? 0 : 1);
1909
+ const exitCode = report.status === 'PASS' ? 0 : 1;
1910
+ if (action === 'report') appendVerificationRunRecord({ taskId, report, gitState: runGitState, reuse, fsImpl });
1911
+ // After the report is printed; never throws and changes nothing printed (088 C.1, IC-006).
1912
+ emitVerifyTelemetry({ report, metrics: engine.runner.metrics, ctx: { source: 'verb', taskId, risk: report.riskLevel, exitCode, v0: v0 ? 'on' : 'off' }, start: state.projectRoot || cwd, env, fsImpl });
1913
+ upkeepAfterVerify({ projectRoot: state.projectRoot });
1914
+ return finishRuntime(exitCode);
1430
1915
  }
1431
1916
 
1432
1917
  /** One line stating what the targeted tier selected, or why it ran the broad command instead. */
@@ -1445,6 +1930,8 @@ function sameSet(a , b ) {
1445
1930
  }
1446
1931
 
1447
1932
  module.exports = {
1933
+ verificationRunsRoot,
1934
+ verificationRunRecordFile,
1448
1935
  VerificationContractRunner,
1449
1936
  VerificationEngine,
1450
1937
  loadVerificationRegistry,
@@ -1457,5 +1944,8 @@ module.exports = {
1457
1944
  MAX_STREAM_CHARS,
1458
1945
  REGISTRY_FILENAME,
1459
1946
  handleVerifyCommand,
1947
+ readVerifyReuse,
1948
+ verifyStateRoot,
1460
1949
  validateVerificationInput,
1950
+ saveFailedFirstFile,
1461
1951
  };