@khoavu882/doflow 2.0.0-beta.5 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (471) hide show
  1. package/README.md +12 -11
  2. package/bin/doflow.js +21 -11
  3. package/core/.antigravity-plugin/plugin.json +1 -2
  4. package/core/.claude-plugin/plugin.json +1 -1
  5. package/core/.codex-plugin/plugin.json +1 -1
  6. package/core/.plugin/plugin.json +1 -1
  7. package/core/harnesses/claude/hooks/lib.sh +43 -24
  8. package/core/harnesses/claude/hooks/permission-audit.sh +3 -0
  9. package/core/harnesses/claude/hooks/post-compact.sh +35 -32
  10. package/core/harnesses/claude/hooks/post-edit-lint.sh +2 -0
  11. package/core/harnesses/claude/hooks/post-shell-observe.sh +48 -0
  12. package/core/harnesses/claude/hooks/post-tool-failure.sh +3 -0
  13. package/core/harnesses/claude/hooks/pre-compact.sh +23 -27
  14. package/core/harnesses/claude/hooks/session-end.sh +23 -10
  15. package/core/harnesses/claude/hooks/skill-config-audit.sh +3 -0
  16. package/core/harnesses/claude/settings/settings.json +18 -0
  17. package/core/harnesses/claude/statusline/doflow-statusline.sh +1 -0
  18. package/core/harnesses/codex/agents/core-implementer.toml +1 -0
  19. package/core/harnesses/codex/agents/delivery-lead.toml +10 -0
  20. package/core/harnesses/codex/agents/quality-guardian.toml +1 -1
  21. package/core/harnesses/codex/agents/research-writer.toml +1 -1
  22. package/core/harnesses/codex/agents/reviewer.toml +10 -0
  23. package/core/harnesses/codex/agents/spec-analyst.toml +1 -1
  24. package/core/harnesses/codex/agents/system-architect.toml +1 -1
  25. package/core/harnesses/codex/hooks/lib.sh +44 -25
  26. package/core/harnesses/codex/hooks/post-edit-lint.impl.sh +3 -1
  27. package/core/harnesses/codex/hooks/pre-compact.impl.sh +29 -26
  28. package/core/harnesses/codex/hooks/session-end.impl.sh +23 -10
  29. package/core/harnesses/codex/hooks/subagent-audit.sh +1 -0
  30. package/core/harnesses/copilot/hooks/doflow-lib.sh +93 -0
  31. package/core/harnesses/copilot/hooks/doflow-mcp-tool-guard.sh +9 -0
  32. package/core/harnesses/copilot/hooks/doflow-pre-bash-guard.sh +9 -0
  33. package/core/harnesses/copilot/hooks/doflow-pre-implement-gate.sh +9 -0
  34. package/core/harnesses/copilot/hooks/doflow-session-start.sh +9 -0
  35. package/core/harnesses/copilot/hooks/doflow-stop-check.sh +9 -0
  36. package/core/harnesses/copilot/hooks/doflow-subagent-start.sh +9 -0
  37. package/core/harnesses/copilot/hooks/doflow-subagent-stop.sh +9 -0
  38. package/core/harnesses/copilot/hooks/doflow.json +57 -0
  39. package/core/harnesses/kiro/agents/doflow.json +1 -1
  40. package/core/harnesses/kiro/hooks/lib.sh +26 -6
  41. package/core/harnesses/kiro/hooks/mcp-tool-guard.sh +1 -0
  42. package/core/harnesses/kiro/hooks/pre-bash-guard.sh +1 -0
  43. package/core/harnesses/kiro/hooks/pre-implement-gate.sh +1 -0
  44. package/core/harnesses/kiro/hooks/session-start.sh +1 -0
  45. package/core/harnesses/kiro/hooks/stop-check.sh +1 -0
  46. package/core/harnesses/pi/extensions/doflow-guard.js +8 -0
  47. package/core/harnesses/shared/hooks/ENVELOPE_MIGRATION.md +0 -1
  48. package/core/harnesses/shared/hooks/bridge/bridge.sh +102 -9
  49. package/core/harnesses/shared/hooks/bridge/envelope.jq +87 -18
  50. package/core/harnesses/shared/hooks/bridge/observe-model.sh +75 -0
  51. package/core/harnesses/shared/hooks/bridge/respond.sh +10 -2
  52. package/core/harnesses/shared/hooks/policies/blocked-patterns.conf +28 -14
  53. package/core/harnesses/shared/hooks/policies/capture-failure.sh +48 -15
  54. package/core/harnesses/shared/hooks/policies/compact-text.sh +46 -0
  55. package/core/harnesses/shared/hooks/policies/contract.sh +3 -0
  56. package/core/harnesses/shared/hooks/policies/deny-json.sh +2 -11
  57. package/core/harnesses/shared/hooks/policies/emission-rules.sh +37 -0
  58. package/core/harnesses/shared/hooks/policies/emission.jq +209 -0
  59. package/core/harnesses/shared/hooks/policies/emission.sh +383 -0
  60. package/core/harnesses/shared/hooks/policies/identity.sh +177 -0
  61. package/core/harnesses/shared/hooks/policies/lib.sh +63 -40
  62. package/core/harnesses/shared/hooks/policies/mcp-tool-guard.sh +3 -2
  63. package/core/harnesses/shared/hooks/policies/pre-bash-guard.sh +213 -12
  64. package/core/harnesses/shared/hooks/policies/pre-implementation-gate.sh +114 -38
  65. package/core/harnesses/shared/hooks/policies/session-context.sh +30 -1
  66. package/core/harnesses/shared/hooks/policies/stop-check.sh +20 -9
  67. package/core/harnesses/shared/hooks/policies/subagent-audit.sh +5 -2
  68. package/core/harnesses/shared/hooks/policies/summary-store.sh +347 -0
  69. package/core/harnesses/shared/hooks/policies/user-prompt-submit.sh +60 -155
  70. package/core/harnesses/shared/hooks/stream-hook-runner.js +47 -8
  71. package/core/harnesses/shared/locator/doflow-run +2 -3
  72. package/core/registry/advice.json +3 -3
  73. package/core/registry/assets.json +55 -82
  74. package/core/registry/capabilities.json +12 -0
  75. package/core/registry/contracts.json +58 -31
  76. package/core/registry/contribution-points.json +32 -0
  77. package/core/registry/gates.json +32 -0
  78. package/core/registry/guidance-caps.json +40 -0
  79. package/core/registry/harnesses.json +1442 -363
  80. package/core/registry/lifecycle.json +485 -304
  81. package/core/registry/mcp.json +5 -1
  82. package/core/registry/models.json +7 -1
  83. package/core/registry/package-templates/README.md +19 -0
  84. package/core/registry/package-templates/agent/files/agent.md +9 -0
  85. package/core/registry/package-templates/agent/template.json +4 -0
  86. package/core/registry/package-templates/capability-provider/files/provider.json +9 -0
  87. package/core/registry/package-templates/capability-provider/template.json +11 -0
  88. package/core/registry/package-templates/debate-preset/files/preset.json +26 -0
  89. package/core/registry/package-templates/debate-preset/template.json +4 -0
  90. package/core/registry/package-templates/review-preset/files/preset.json +30 -0
  91. package/core/registry/package-templates/review-preset/template.json +4 -0
  92. package/core/registry/package-templates/skill/files/SKILL.md +8 -0
  93. package/core/registry/package-templates/skill/template.json +4 -0
  94. package/core/registry/package-templates/worker/files/worker.json +92 -0
  95. package/core/registry/package-templates/worker/template.json +11 -0
  96. package/core/registry/packages/doflow/doflow-package.json +15 -0
  97. package/core/registry/packages/doflow/presets/debate-3.json +26 -0
  98. package/core/registry/protocols.json +61 -0
  99. package/core/registry/routes.json +17 -0
  100. package/core/registry/rules.json +431 -0
  101. package/core/registry/settings.json +276 -0
  102. package/core/registry/storage.json +679 -31
  103. package/core/registry/telemetry.json +236 -0
  104. package/core/registry/transcripts.json +142 -0
  105. package/core/registry/verification.json +76 -1
  106. package/core/registry/workflows.json +55 -0
  107. package/core/shared/agent-specs/core-implementer.md +3 -0
  108. package/core/shared/agent-specs/delivery-lead.md +68 -0
  109. package/core/shared/agent-specs/quality-guardian.md +5 -1
  110. package/core/shared/agent-specs/research-writer.md +10 -3
  111. package/core/shared/agent-specs/reviewer.md +53 -0
  112. package/core/shared/agent-specs/spec-analyst.md +5 -6
  113. package/core/shared/agent-specs/system-architect.md +5 -1
  114. package/core/shared/guidance/DOFLOW_CORE.md +1 -5
  115. package/core/shared/guidance/modes/MODE_DeepResearch.md +1 -1
  116. package/core/shared/guidance/modes/MODE_Orchestration.md +4 -3
  117. package/core/shared/guidance/modes/MODE_Task_Management.md +14 -14
  118. package/core/shared/guidance/pointers/codex.md +1 -4
  119. package/core/shared/guidance/references/ARTIFACT_FORMAT.md +30 -16
  120. package/core/shared/guidance/references/ARTIFACT_VOICE.md +3 -2
  121. package/core/shared/guidance/references/CONSTITUTION_BASE.md +17 -15
  122. package/core/shared/guidance/references/DIAGRAM_GUIDE.md +1 -1
  123. package/core/shared/guidance/references/DOFLOW_CHAIN.md +17 -19
  124. package/core/shared/guidance/references/EVIDENCE_LEDGER.md +7 -8
  125. package/core/shared/guidance/references/MODEL_SELECTION.md +5 -3
  126. package/core/shared/guidance/references/ORCHESTRATION.md +1 -2
  127. package/core/shared/guidance/rules/RULES.md +45 -0
  128. package/core/shared/scripts/doflow/bash/do-args.sh +37 -0
  129. package/core/shared/scripts/doflow/bash/do-exec-paths.sh +4 -0
  130. package/core/shared/scripts/doflow/bash/do-git-state.sh +15 -4
  131. package/core/shared/scripts/doflow/bash/do-parallel-check.sh +4 -0
  132. package/core/shared/scripts/doflow/bash/do-paths.sh +40 -2
  133. package/core/shared/scripts/doflow/bash/do-prereqs.sh +2 -1
  134. package/core/shared/scripts/doflow/bash/do-review-package.sh +4 -0
  135. package/core/shared/scripts/doflow/bash/do-task-brief.sh +58 -16
  136. package/core/shared/scripts/doflow/bash/render-audit.sh +26 -2
  137. package/core/shared/scripts/doflow/bash/render-diagrams.sh +1 -0
  138. package/core/shared/scripts/doflow/bash/render-puml.sh +2 -1
  139. package/core/shared/scripts/doflow/bash/validate-artifacts.sh +48 -4
  140. package/core/shared/scripts/doflow/bin/doflow-run +149 -34
  141. package/core/shared/skills/do/SKILL.md +12 -10
  142. package/core/shared/skills/do/references/advice_calls.md +26 -0
  143. package/core/shared/skills/do/references/pm_routing.md +1 -2
  144. package/core/shared/skills/do/references/program_mode.md +31 -0
  145. package/core/shared/skills/do-brainstorm/SKILL.md +20 -74
  146. package/core/shared/skills/do-brainstorm/references/handoff_branches.md +35 -0
  147. package/core/shared/skills/do-brainstorm/references/intent_seed.md +21 -0
  148. package/core/shared/skills/do-brainstorm/references/round_log.md +17 -0
  149. package/core/shared/skills/do-code-review/SKILL.md +41 -144
  150. package/core/shared/skills/do-code-review/content-types/markdown.md +24 -2
  151. package/core/shared/skills/do-code-review/references/tools.md +90 -0
  152. package/core/shared/skills/do-code-review/scripts/doc_quality_checker.py +549 -7
  153. package/core/shared/skills/do-design/SKILL.md +18 -17
  154. package/core/shared/skills/do-diagnose/SKILL.md +14 -4
  155. package/core/shared/skills/do-document/SKILL.md +11 -1
  156. package/core/shared/skills/do-document/references/deep_research.md +1 -2
  157. package/core/shared/skills/do-document/references/embedded-research.md +18 -6
  158. package/core/shared/skills/do-document/references/implementation-flow.md +5 -6
  159. package/core/shared/skills/do-execute-plan/SKILL.md +15 -58
  160. package/core/shared/skills/do-execute-plan/references/handoff_record.md +7 -2
  161. package/core/shared/skills/do-execute-plan/references/parallel_dispatch.md +13 -0
  162. package/core/shared/skills/do-execute-plan/references/readiness_gate.md +26 -1
  163. package/core/shared/skills/do-execute-plan/references/run_position.md +13 -1
  164. package/core/shared/skills/do-execute-plan/references/scaffold.md +17 -9
  165. package/core/shared/skills/do-execute-plan/references/state_checkpoint.md +11 -0
  166. package/core/shared/skills/do-execute-plan/references/task_attempts.md +78 -24
  167. package/core/shared/skills/do-flow/SKILL.md +8 -9
  168. package/core/shared/skills/do-flow/references/task_classes.md +2 -2
  169. package/core/shared/skills/do-git/SKILL.md +4 -0
  170. package/core/shared/skills/do-implement/SKILL.md +4 -7
  171. package/core/shared/skills/do-plan/SKILL.md +26 -15
  172. package/core/shared/skills/do-test/SKILL.md +7 -4
  173. package/core/shared/templates/doflow/constitution-template.md +9 -3
  174. package/core/shared/templates/doflow/data-model-template.md +1 -1
  175. package/core/shared/templates/doflow/design-template.md +113 -16
  176. package/core/shared/templates/doflow/plan-template.md +11 -10
  177. package/core/shared/templates/doflow/question-log-template.md +14 -2
  178. package/core/shared/templates/doflow/requirement-template.md +1 -1
  179. package/core/worker-evidence/antigravity/checks.json +14 -0
  180. package/core/worker-evidence/codex/2026-10-18-cli-check.json +107 -0
  181. package/core/worker-evidence/codex/2026-10-18-live-run-edit.json +73 -0
  182. package/core/worker-evidence/codex/2026-10-18-live-run-read-only.json +62 -0
  183. package/core/worker-evidence/codex/2026-10-24-cli-check.json +126 -0
  184. package/core/worker-evidence/codex/checks.json +38 -0
  185. package/package.json +10 -6
  186. package/src/adapters/antigravity/index.js +143 -66
  187. package/src/adapters/claude/index.js +24 -4
  188. package/src/adapters/codex/agents.js +25 -15
  189. package/src/adapters/codex/index.js +48 -13
  190. package/src/adapters/codex/native-projection.js +6 -2
  191. package/src/adapters/copilot/index.js +58 -23
  192. package/src/adapters/copy-tree.js +290 -44
  193. package/src/adapters/guidance-manifest.js +71 -0
  194. package/src/adapters/guidance-pointer.js +19 -0
  195. package/src/adapters/hook-commands.js +6 -10
  196. package/src/adapters/index.js +1 -0
  197. package/src/adapters/kiro/index.js +23 -12
  198. package/src/adapters/opencode/index.js +8 -8
  199. package/src/adapters/pi/hooks.js +2 -1
  200. package/src/adapters/pi/index.js +14 -7
  201. package/src/adapters/resolve-source.js +26 -2
  202. package/src/cli/commands/install.js +2 -1
  203. package/src/cli/commands/reconcile.js +53 -9
  204. package/src/cli/commands/remove.js +2 -2
  205. package/src/cli/commands/rollback.js +6 -3
  206. package/src/cli/commands/self-update.js +18 -9
  207. package/src/cli/commands/status.js +21 -2
  208. package/src/cli/commands/ui.js +99 -0
  209. package/src/cli/commands/update.js +2 -1
  210. package/src/cli/index.js +322 -29
  211. package/src/cli/runtime-commands.js +209 -57
  212. package/src/cli/shared.js +0 -2
  213. package/src/helper/git.js +1 -1
  214. package/src/helper/guardrails.js +1 -1
  215. package/src/helper/marker-merge.js +31 -13
  216. package/src/helper/package-ids.js +37 -0
  217. package/src/helper/semver.js +94 -0
  218. package/src/install/backup.js +105 -15
  219. package/src/install/context.js +1 -1
  220. package/src/install/targets.js +31 -16
  221. package/src/lifecycle/coverage.js +20 -3
  222. package/src/lifecycle/hook-tier.js +160 -0
  223. package/src/lifecycle/index.js +412 -78
  224. package/src/lifecycle/retired-harnesses.js +318 -0
  225. package/src/lifecycle/view.js +35 -15
  226. package/src/registry/index.js +551 -26
  227. package/src/registry/worker-ids.js +45 -0
  228. package/src/release/build-package.js +0 -3
  229. package/src/release/docs-blocks.js +492 -0
  230. package/src/release/preflight.js +425 -0
  231. package/src/release/rehearse.js +401 -0
  232. package/src/release/release-workflow.js +733 -0
  233. package/src/release/sync-plugin-versions.js +43 -7
  234. package/src/runtime/advice/bench-gate.js +25 -0
  235. package/src/runtime/advice/cli.js +59 -16
  236. package/src/runtime/advice/client.js +32 -9
  237. package/src/runtime/advice/config.js +98 -133
  238. package/src/runtime/advice/doctor.js +1 -1
  239. package/src/runtime/advice/elevation.js +97 -0
  240. package/src/runtime/advice/endpoint-guard.js +9 -3
  241. package/src/runtime/advice/mode.js +37 -0
  242. package/src/runtime/advice/record.js +18 -5
  243. package/src/runtime/advice/registry.js +4 -1
  244. package/src/runtime/advice/shadow-config.js +229 -0
  245. package/src/runtime/advice/shadow-dispatcher.js +276 -0
  246. package/src/runtime/brief-rules.js +81 -0
  247. package/src/runtime/brief-tools.js +111 -34
  248. package/src/runtime/candidate.js +20 -11
  249. package/src/runtime/capabilities/executor.js +450 -0
  250. package/src/runtime/capabilities/loopback.js +41 -0
  251. package/src/runtime/capabilities/sanitizer.js +88 -0
  252. package/src/runtime/capability-router.js +135 -3
  253. package/src/runtime/checkouts.js +73 -11
  254. package/src/runtime/claims.js +13 -7
  255. package/src/runtime/cli.js +196 -19
  256. package/src/runtime/config-home.js +42 -0
  257. package/src/runtime/context/arbitrate.js +767 -0
  258. package/src/runtime/context/delivery-store.js +4 -6
  259. package/src/runtime/context/emission-rules.js +118 -0
  260. package/src/runtime/context/identity.js +227 -0
  261. package/src/runtime/context/primary-command.js +117 -0
  262. package/src/runtime/context/primary.js +344 -0
  263. package/src/runtime/context/summary-store.js +655 -0
  264. package/src/runtime/context-pack.js +154 -78
  265. package/src/runtime/decision-register.js +13 -4
  266. package/src/runtime/dispatch/admission.js +392 -0
  267. package/src/runtime/dispatch/attempt-end.js +274 -0
  268. package/src/runtime/dispatch/command.js +628 -0
  269. package/src/runtime/dispatch/controls.js +904 -0
  270. package/src/runtime/dispatch/envelope.js +276 -0
  271. package/src/runtime/dispatch/grants.js +310 -0
  272. package/src/runtime/dispatch/integrate.js +617 -0
  273. package/src/runtime/dispatch/loop.js +1835 -0
  274. package/src/runtime/dispatch/store.js +352 -0
  275. package/src/runtime/dispatch/verify-run.js +201 -0
  276. package/src/runtime/evidence-ledger.js +34 -7
  277. package/src/runtime/failure/capture.js +3 -1
  278. package/src/runtime/failure/cli.js +25 -9
  279. package/src/runtime/failure/home.js +57 -30
  280. package/src/runtime/failure/store.js +7 -2
  281. package/src/runtime/feature-pointer.js +174 -0
  282. package/src/runtime/feature-resolve.js +13 -2
  283. package/src/runtime/fleet/command.js +77 -0
  284. package/src/runtime/fleet/projects.js +178 -0
  285. package/src/runtime/fleet/tree.js +383 -0
  286. package/src/runtime/freshness.js +33 -3
  287. package/src/runtime/git-memo.js +46 -0
  288. package/src/runtime/guidance/ablation.js +427 -0
  289. package/src/runtime/health.js +565 -17
  290. package/src/runtime/history-compactor.js +13 -3
  291. package/src/runtime/implementation-gate.js +277 -22
  292. package/src/runtime/inventory/coverage.js +1 -1
  293. package/src/runtime/inventory/index.js +1 -1
  294. package/src/runtime/inventory/precedence.js +2 -2
  295. package/src/runtime/inventory/siblings.js +6 -4
  296. package/src/runtime/knowledge/code-corpus.js +277 -0
  297. package/src/runtime/knowledge/code-lexical.js +130 -0
  298. package/src/runtime/knowledge/code-retrieval.js +307 -0
  299. package/src/runtime/knowledge/code-validation.js +106 -0
  300. package/src/runtime/knowledge/gp3-gate.js +74 -0
  301. package/src/runtime/knowledge/graph-adapter.js +288 -0
  302. package/src/runtime/knowledge/graph-fallback.js +394 -0
  303. package/src/runtime/knowledge/hybrid.js +136 -8
  304. package/src/runtime/knowledge/retrieval.js +34 -1
  305. package/src/runtime/knowledge/rtk-compress.js +77 -0
  306. package/src/runtime/knowledge/semble-bridge.py +106 -0
  307. package/src/runtime/knowledge/semble-lifecycle.js +114 -0
  308. package/src/runtime/knowledge/semble-provider.js +365 -0
  309. package/src/runtime/lifecycle/cli.js +16 -6
  310. package/src/runtime/lifecycle/event-store.js +8 -6
  311. package/src/runtime/lifecycle/followup.js +6 -1
  312. package/src/runtime/lifecycle/overview.js +24 -1
  313. package/src/runtime/lifecycle/report-store.js +8 -10
  314. package/src/runtime/lifecycle/retention.js +95 -31
  315. package/src/runtime/lifecycle/root.js +9 -1
  316. package/src/runtime/lifecycle/status.js +34 -17
  317. package/src/runtime/lifecycle/store-upkeep.js +56 -7
  318. package/src/runtime/mask.js +134 -7
  319. package/src/runtime/mcp-consultations.js +40 -6
  320. package/src/runtime/model-router.js +79 -7
  321. package/src/runtime/models/catalogue.js +191 -0
  322. package/src/runtime/models/command.js +153 -0
  323. package/src/runtime/off-words.js +12 -0
  324. package/src/runtime/outcome.js +2 -2
  325. package/src/runtime/packages/command.js +1009 -0
  326. package/src/runtime/packages/contributions.js +116 -0
  327. package/src/runtime/packages/digest.js +132 -0
  328. package/src/runtime/packages/fetch.js +182 -0
  329. package/src/runtime/packages/grants.js +192 -0
  330. package/src/runtime/packages/index-file.js +134 -0
  331. package/src/runtime/packages/instruction.js +90 -0
  332. package/src/runtime/packages/manifest.js +331 -0
  333. package/src/runtime/packages/points.js +477 -0
  334. package/src/runtime/packages/programs.js +216 -0
  335. package/src/runtime/packages/scaffold.js +277 -0
  336. package/src/runtime/packages/source.js +90 -0
  337. package/src/runtime/packages/state.js +164 -0
  338. package/src/runtime/packages/store.js +449 -0
  339. package/src/runtime/packages/tar.js +106 -0
  340. package/src/runtime/packages/view.js +406 -0
  341. package/src/runtime/plan-check.js +59 -0
  342. package/src/runtime/plan-content.js +324 -0
  343. package/src/runtime/policy-init.js +118 -0
  344. package/src/runtime/profiles/doctor.js +96 -0
  345. package/src/runtime/profiles/resolve.js +244 -0
  346. package/src/runtime/profiles/rollback.js +254 -0
  347. package/src/runtime/profiles/sampler.js +41 -0
  348. package/src/runtime/readiness-record.js +16 -11
  349. package/src/runtime/readiness.js +46 -5
  350. package/src/runtime/research-request.js +50 -25
  351. package/src/runtime/retrieval-plan.js +10 -30
  352. package/src/runtime/retrieval-record.js +75 -5
  353. package/src/runtime/review/command.js +246 -0
  354. package/src/runtime/review/dominance.js +68 -0
  355. package/src/runtime/review/findings.js +434 -0
  356. package/src/runtime/review/identity.js +255 -0
  357. package/src/runtime/review/protocol-command.js +109 -0
  358. package/src/runtime/review/protocols.js +405 -0
  359. package/src/runtime/review/resolve-protocol.js +302 -0
  360. package/src/runtime/review/start.js +530 -0
  361. package/src/runtime/roadmap/facts.js +479 -0
  362. package/src/runtime/roadmap/gates.js +270 -0
  363. package/src/runtime/roadmap/loop.js +1636 -0
  364. package/src/runtime/roadmap/merge.js +381 -0
  365. package/src/runtime/roadmap/rows.js +1113 -0
  366. package/src/runtime/roadmap/seed.js +951 -0
  367. package/src/runtime/rules-report.js +336 -0
  368. package/src/runtime/scaffold/artifacts.js +60 -9
  369. package/src/runtime/scaffold/generate.js +21 -9
  370. package/src/runtime/settings/autopilot.js +106 -0
  371. package/src/runtime/settings/cli.js +280 -0
  372. package/src/runtime/settings/config-write.js +314 -0
  373. package/src/runtime/settings/files.js +271 -0
  374. package/src/runtime/settings/legacy.js +154 -0
  375. package/src/runtime/settings/lexical.js +56 -0
  376. package/src/runtime/settings/prepared-run.js +269 -0
  377. package/src/runtime/settings/project-layers.js +118 -0
  378. package/src/runtime/settings/refs.js +288 -0
  379. package/src/runtime/settings/registry.js +553 -0
  380. package/src/runtime/settings/resolve.js +713 -0
  381. package/src/runtime/settings/shared.js +37 -0
  382. package/src/runtime/settings/worker-billing.js +107 -0
  383. package/src/runtime/settings/write.js +269 -0
  384. package/src/runtime/slot-command.js +78 -0
  385. package/src/runtime/slots.js +671 -0
  386. package/src/runtime/source-version.js +1 -0
  387. package/src/runtime/state-root.js +217 -0
  388. package/src/runtime/storage/cli.js +12 -5
  389. package/src/runtime/storage/engine.js +9 -2
  390. package/src/runtime/storage/registry.js +14 -1
  391. package/src/runtime/storage/roots.js +66 -16
  392. package/src/runtime/storage/rules.js +82 -10
  393. package/src/runtime/storage/upkeep.js +10 -2
  394. package/src/runtime/storage/work-status.js +245 -26
  395. package/src/runtime/task-attempts.js +589 -69
  396. package/src/runtime/task-classifier.js +47 -11
  397. package/src/runtime/task-graph.js +373 -0
  398. package/src/runtime/task-run.js +778 -0
  399. package/src/runtime/task-scope.js +176 -55
  400. package/src/runtime/task-sources.js +147 -0
  401. package/src/runtime/task-state.js +374 -64
  402. package/src/runtime/team/admission.js +301 -0
  403. package/src/runtime/team/envelope.js +324 -0
  404. package/src/runtime/team/review-guard.js +156 -0
  405. package/src/runtime/trace/analyze.js +427 -0
  406. package/src/runtime/trace/baseline.js +770 -0
  407. package/src/runtime/trace/ledger.js +63 -140
  408. package/src/runtime/trace/otlp-export.js +568 -0
  409. package/src/runtime/trace/otlp-map.js +481 -0
  410. package/src/runtime/trace/spans.js +745 -0
  411. package/src/runtime/trace/telemetry.js +225 -0
  412. package/src/runtime/trace/views.js +1 -0
  413. package/src/runtime/transcripts/command.js +98 -0
  414. package/src/runtime/transcripts/cursor.js +326 -0
  415. package/src/runtime/transcripts/read.js +126 -0
  416. package/src/runtime/transcripts/readers/claude-jsonl.js +74 -0
  417. package/src/runtime/transcripts/readers/codex-rollout.js +70 -0
  418. package/src/runtime/transcripts/readers/copilot-events.js +90 -0
  419. package/src/runtime/transcripts/readers/kiro-jsonl.js +77 -0
  420. package/src/runtime/transcripts/readers/pi-jsonl.js +59 -0
  421. package/src/runtime/transcripts/registry.js +161 -0
  422. package/src/runtime/transcripts/stats.js +161 -0
  423. package/src/runtime/verification/check-key.js +628 -0
  424. package/src/runtime/verification/contract-runner.js +546 -11
  425. package/src/runtime/verification/engine.js +499 -9
  426. package/src/runtime/verification/impact.js +617 -0
  427. package/src/runtime/verification/inflight.js +205 -0
  428. package/src/runtime/verification/registry.js +176 -0
  429. package/src/runtime/verification/reuse.js +184 -0
  430. package/src/runtime/verification/telemetry-emit.js +254 -0
  431. package/src/runtime/verification/test-selection.js +67 -7
  432. package/src/runtime/worker/active.js +90 -0
  433. package/src/runtime/worker/cancel.js +222 -0
  434. package/src/runtime/worker/catalogue.js +122 -0
  435. package/src/runtime/worker/command.js +677 -0
  436. package/src/runtime/worker/evidence.js +288 -0
  437. package/src/runtime/worker/launch.js +288 -0
  438. package/src/runtime/worker/models.js +137 -0
  439. package/src/runtime/worker/parsers.js +218 -0
  440. package/src/runtime/worker/probes/index.js +706 -0
  441. package/src/runtime/worker/process-tree.js +415 -0
  442. package/src/runtime/worker/quota.js +43 -0
  443. package/src/runtime/worker/readiness.js +713 -0
  444. package/src/runtime/worker/run-store.js +166 -0
  445. package/src/runtime/worker/supervisor.js +541 -0
  446. package/src/runtime/worker/worktree-check.js +252 -0
  447. package/src/runtime/workflow-engine.js +5 -1
  448. package/src/runtime/workflow-orchestrator.js +578 -63
  449. package/src/runtime/worktree.js +149 -22
  450. package/src/state/index.js +1 -1
  451. package/src/state/lockfile.js +64 -10
  452. package/src/state/run-lock.js +1 -1
  453. package/types/node-commonjs.d.ts +106 -7
  454. package/core/harnesses/gemini/hooks/hooks.json +0 -77
  455. package/core/harnesses/gemini/hooks/lib.sh +0 -302
  456. package/core/harnesses/gemini/hooks/mcp-tool-guard.sh +0 -10
  457. package/core/harnesses/gemini/hooks/post-edit-lint.sh +0 -39
  458. package/core/harnesses/gemini/hooks/pre-bash-guard.sh +0 -10
  459. package/core/harnesses/gemini/hooks/pre-compact.sh +0 -38
  460. package/core/harnesses/gemini/hooks/pre-implement-gate.sh +0 -10
  461. package/core/harnesses/gemini/hooks/session-end.impl.sh +0 -89
  462. package/core/harnesses/gemini/hooks/session-end.sh +0 -5
  463. package/core/harnesses/gemini/hooks/session-start.sh +0 -7
  464. package/core/harnesses/gemini/settings/adapter-defaults.json +0 -4
  465. package/core/shared/guidance/FLAGS.md +0 -25
  466. package/core/shared/guidance/rules/RULE_01_SAFETY.md +0 -25
  467. package/core/shared/guidance/rules/RULE_02_WORKFLOW.md +0 -23
  468. package/core/shared/guidance/rules/RULE_03_QUALITY.md +0 -14
  469. package/core/shared/guidance/rules/RULE_04_QUESTIONS.md +0 -46
  470. package/src/adapters/gemini/hooks.js +0 -251
  471. package/src/adapters/gemini/index.js +0 -429
@@ -28,6 +28,12 @@
28
28
  const { spawnSync } = require('node:child_process');
29
29
  const fs = require('node:fs');
30
30
  const path = require('node:path');
31
+ const { telemetryOff } = require('../trace/telemetry.js');
32
+ const { checkKeyV2, headCommit, treeState } = require('./check-key.js');
33
+ const { runInflight } = require('./inflight.js');
34
+ const { levelOf, reuseApplies, v0Applies } = require('./registry.js');
35
+ const { evictReuse, lookupReuse, reuseDir, storeReuse } = require('./reuse.js');
36
+ const { resolveTestsImportingHelper } = require('./impact.js');
31
37
 
32
38
  /** A failing check whose name contains one of these aborts the remaining checks: once the build is
33
39
  * broken, every later result describes the broken build rather than the change. */
@@ -88,6 +94,216 @@ function extractFailedTests(text ) {
88
94
  return [...seen];
89
95
  }
90
96
 
97
+ /** The monotonic clock in nanoseconds, or null when it cannot be read. */
98
+ function monotonicNs() {
99
+ try {
100
+ return process.hrtime.bigint();
101
+ } catch {
102
+ return null;
103
+ }
104
+ }
105
+
106
+ /**
107
+ * Test output counts parser (088 E.2, IC-007).
108
+ * Parses full stdout+stderr for TAP (# tests N / # fail N) or spec (ℹ tests N / ℹ fail N).
109
+ * Pytest and unparseable outputs return { tests: null, testsFailed: null }.
110
+ * @param {unknown} text
111
+ * @returns {{ tests: number | null, testsFailed: number | null }}
112
+ */
113
+ function parseTestCounts(text ) {
114
+ if (typeof text !== 'string') return { tests: null, testsFailed: null };
115
+ const tapTests = text.match(/^# tests (\d+)/m);
116
+ if (tapTests) {
117
+ const tapFail = text.match(/^# fail (\d+)/m);
118
+ return {
119
+ tests: parseInt(tapTests[1], 10),
120
+ testsFailed: tapFail ? parseInt(tapFail[1], 10) : 0,
121
+ };
122
+ }
123
+ const specTests = text.match(/^ℹ tests (\d+)/m);
124
+ if (specTests) {
125
+ const specFail = text.match(/^ℹ fail (\d+)/m);
126
+ return {
127
+ tests: parseInt(specTests[1], 10),
128
+ testsFailed: specFail ? parseInt(specFail[1], 10) : 0,
129
+ };
130
+ }
131
+ return { tests: null, testsFailed: null };
132
+ }
133
+
134
+ /**
135
+ * Reads child cpu ticks from /proc/self/stat on Linux (088 E.2, IC-008).
136
+ * Returns cutime + cstime as a number, or null on non-Linux or read fault.
137
+ * @param {Object} [fsImpl=fs]
138
+ * @param {string} [platform=process.platform]
139
+ * @returns {number | null}
140
+ */
141
+ function childCpuTicks(fsImpl = fs, platform = process.platform) {
142
+ if (platform !== 'linux') return null;
143
+ try {
144
+ const statText = fsImpl.readFileSync('/proc/self/stat', 'utf8');
145
+ const lastParen = statText.lastIndexOf(')');
146
+ if (lastParen === -1) return null;
147
+ const rest = statText.slice(lastParen + 1).trim();
148
+ const tokens = rest.split(/\s+/);
149
+ // After the last ')', field 3 (state) is index 0.
150
+ // field 16 (cutime) is index 13, field 17 (cstime) is index 14.
151
+ const cutime = parseInt(tokens[13], 10);
152
+ const cstime = parseInt(tokens[14], 10);
153
+ if (!Number.isFinite(cutime) || !Number.isFinite(cstime)) return null;
154
+ return cutime + cstime;
155
+ } catch {
156
+ return null;
157
+ }
158
+ }
159
+
160
+ /**
161
+ * Test output failing files parser (088 G.3, IC-007).
162
+ * Extracts sorted unique repo-relative test file paths from TAP, spec, or pytest output.
163
+ * Drops non-test files, non-regular files (directories), files outside root, or parse faults.
164
+ * @param {unknown} text
165
+ * @param {Object} options
166
+ * @param {string} options.root project root
167
+ * @param {Object} options.convention convention object with isTest(path)
168
+ * @param {Object} [options.fsImpl=fs]
169
+ * @returns {Array<string>} sorted unique repo-relative paths
170
+ */
171
+ function extractFailedFiles(
172
+ text ,
173
+ {
174
+ root,
175
+ convention,
176
+ found,
177
+ fsImpl = fs,
178
+ }
179
+
180
+
181
+
182
+
183
+ = {},
184
+ ) {
185
+ if (typeof text !== 'string' || !root || !convention || typeof convention.isTest !== 'function') {
186
+ return [];
187
+ }
188
+
189
+ let realRoot ;
190
+ try {
191
+ realRoot = fsImpl.realpathSync(root);
192
+ } catch {
193
+ realRoot = path.resolve(root);
194
+ }
195
+
196
+ const rawPaths = new Set ();
197
+
198
+ // 1. TAP: in a YAML block that follows a `not ok` line, ^\s*location:\s*'(.+):\d+:\d+'\s*$ (absolute, made relative to realpath(root))
199
+ const lines = text.split('\n');
200
+ let inNotOk = false;
201
+ let inYaml = false;
202
+
203
+ for (let i = 0; i < lines.length; i++) {
204
+ const line = lines[i];
205
+ if (/^\s*not ok\b/.test(line)) {
206
+ inNotOk = true;
207
+ inYaml = false;
208
+ } else if (/^\s*ok\b/.test(line)) {
209
+ inNotOk = false;
210
+ inYaml = false;
211
+ } else if (inNotOk && /^\s*---\s*$/.test(line)) {
212
+ inYaml = true;
213
+ } else if (inYaml && /^\s*\.\.\.\s*$/.test(line)) {
214
+ inYaml = false;
215
+ inNotOk = false;
216
+ } else if (inNotOk && inYaml) {
217
+ const match = line.match(/^\s*location:\s*['"]?(.+?):\d+:\d+['"]?\s*$/);
218
+ if (match) {
219
+ rawPaths.add(match[1]);
220
+ }
221
+ }
222
+ }
223
+
224
+ // 2. Spec: after the `✖ failing tests:` line, ^\s*test at (.+):\d+:\d+\s*$ (relative to root)
225
+ let afterFailingTests = false;
226
+ for (let i = 0; i < lines.length; i++) {
227
+ const line = lines[i];
228
+ if (/✖ failing tests:/.test(line)) {
229
+ afterFailingTests = true;
230
+ continue;
231
+ }
232
+ if (afterFailingTests) {
233
+ const match = line.match(/^\s*test at (.+?):\d+:\d+\s*$/);
234
+ if (match) {
235
+ rawPaths.add(match[1]);
236
+ }
237
+ }
238
+ }
239
+
240
+ // 3. Pytest: ^FAILED (\S+?)::
241
+ for (let i = 0; i < lines.length; i++) {
242
+ const line = lines[i];
243
+ const match = line.match(/^FAILED (\S+?)::/);
244
+ if (match) {
245
+ rawPaths.add(match[1]);
246
+ }
247
+ }
248
+
249
+ const validFiles = new Set ();
250
+
251
+ for (const raw of rawPaths) {
252
+ let relPath ;
253
+ if (path.isAbsolute(raw)) {
254
+ relPath = path.relative(realRoot, raw);
255
+ } else {
256
+ relPath = path.normalize(raw);
257
+ }
258
+ if (path.isAbsolute(relPath) || relPath.startsWith('..')) {
259
+ continue;
260
+ }
261
+ const normalized = relPath.replace(/\\/g, '/');
262
+ const fullPath = path.join(realRoot, normalized);
263
+
264
+ let st ;
265
+ try {
266
+ st = fsImpl.statSync(fullPath);
267
+ } catch {
268
+ continue;
269
+ }
270
+ if (!st || !st.isFile() || st.isDirectory()) {
271
+ continue;
272
+ }
273
+ if (convention.isTest(normalized)) {
274
+ validFiles.add(normalized);
275
+ continue;
276
+ }
277
+ // IC-004: when location is a non-test helper under convention.roots, map to importing .test.* files
278
+ const underRoots = Array.isArray(convention.roots)
279
+ ? convention.roots.some((r ) => normalized === r || normalized.startsWith(`${r}/`))
280
+ : normalized.startsWith('test/');
281
+ if (underRoots) {
282
+ const targetFound = found || convention.found || { source: 'package.json', testDirs: convention.roots || ['test'] };
283
+ const importing = resolveTestsImportingHelper({
284
+ root: realRoot,
285
+ found: targetFound,
286
+ helperFiles: [normalized],
287
+ fsImpl,
288
+ });
289
+ for (const t of importing) {
290
+ validFiles.add(t);
291
+ }
292
+ }
293
+ }
294
+
295
+ return [...validFiles].sort();
296
+ }
297
+
298
+ /** What one status read says (089 IC-001); `ignored`, `gitRoot` and `links` are null when it faulted. */
299
+
300
+
301
+
302
+
303
+
304
+
305
+
306
+
91
307
  class VerificationContractRunner {
92
308
  cwd ;
93
309
  defaultTimeoutMs ;
@@ -95,6 +311,20 @@ class VerificationContractRunner {
95
311
  fsImpl ;
96
312
  logDir ;
97
313
  logWriteWarned ;
314
+ platform ;
315
+ env ;
316
+ convention ;
317
+ configDir ;
318
+ inflightContext ;
319
+ /** 089 IC-005: lookup of recorded PASSes, set only at the levels `policy.reuse.levels` lists. */
320
+ reuseContext ;
321
+ /** 089 IC-005: where an executed PASS is stored while reuse is on, at every level (L3 store-only). */
322
+ reuseStore ;
323
+ /** One `git status` per contract (089 IC-001): `ignored` and `gitRoot` are null when it faulted. */
324
+ _treeContext ;
325
+ /** Check result → `{ startedAt, startedMonoMs, durationMs, tests, testsFailed, cpuMs, resourceBasis, checkKey, shared, failedFirst }`
326
+ * for every executed or reused check; an in-run duplicate and a check with no command have no entry. */
327
+ metrics ;
98
328
 
99
329
  /**
100
330
  * @param {Object|string} [options] a string is accepted for parity with the Python's positional
@@ -102,6 +332,10 @@ class VerificationContractRunner {
102
332
  * @param {string} [options.cwd] directory the checks run in; defaults to the current one
103
333
  * @param {number} [options.defaultTimeoutMs=60000]
104
334
  * @param {Function} [options.exec] injection seam for tests; same contract as `spawnSync`
335
+ * @param {string} [options.logDir] where full check logs go; the verify verb passes its resolved
336
+ * store's (engine.ts `verifyStateRoot`), so `<cwd>/.doflow` is only the unconfigured default
337
+ * @param {string} [options.platform=process.platform]
338
+ * @param {Object} [options.env=process.env]
105
339
  */
106
340
  constructor(options = {}) {
107
341
  const opts = typeof options === 'string' ? { cwd: options } : options;
@@ -110,6 +344,155 @@ class VerificationContractRunner {
110
344
  this.exec = opts.exec || spawnSync;
111
345
  this.fsImpl = opts.fsImpl || fs;
112
346
  this.logDir = opts.logDir || path.join(this.cwd, '.doflow', 'state', 'verification', 'logs');
347
+ this.platform = opts.platform || process.platform;
348
+ this.env = opts.env || process.env;
349
+ this.convention = opts.convention || null;
350
+ this._treeContext = undefined;
351
+ this.metrics = new WeakMap();
352
+ if (opts.context) this.beginContract(opts.context);
353
+ }
354
+
355
+ /** Explicit engine seam: call once per active V0 or reuse evaluation, including retries.
356
+ * No execution policy is inferred from the environment or leaked into compiled contracts.
357
+ * 089 IC-005: `reuse === true` stores every executed PASS at a known level; lookup is enabled only
358
+ * where `reuseApplies` (L1/L2), so an L3 PASS may serve a later L2 run but L3 always executes. */
359
+ beginContract({ v0, reuse, registry, riskLevel, configDir } ) {
360
+ this._treeContext = undefined;
361
+ this.inflightContext = undefined;
362
+ this.reuseContext = undefined;
363
+ this.reuseStore = undefined;
364
+ this.configDir = configDir;
365
+ if (v0 === true && v0Applies(registry, riskLevel) && typeof configDir === 'string') {
366
+ this.inflightContext = { enabled: true, dir: path.join(configDir, 'state', 'verification', 'v0') };
367
+ }
368
+ if (reuse === true && typeof configDir === 'string') {
369
+ const level = levelOf(registry, riskLevel);
370
+ const dir = level ? reuseDir({ configDir, env: this.env, fsImpl: this.fsImpl }) : null;
371
+ if (level && typeof dir === 'string') {
372
+ this.reuseStore = { dir, level };
373
+ const maxAgeMs = registry?.policy?.reuse?.maxAgeMs;
374
+ if (reuseApplies(registry, riskLevel) && Number.isSafeInteger(maxAgeMs) && maxAgeMs > 0) {
375
+ this.reuseContext = { enabled: true, dir, maxAgeMs, level };
376
+ }
377
+ }
378
+ }
379
+ }
380
+
381
+ /**
382
+ * One read of the working tree: HEAD, the status call's tree, ignored set and git root, and (only
383
+ * while reuse is on, where something reads it) the tracked symlinks' out-of-repo targets.
384
+ */
385
+ readTreeContext() {
386
+ const head = headCommit(this.cwd);
387
+ const state = treeState(this.cwd, { fsImpl: this.fsImpl, configDir: this.configDir, links: Boolean(this.reuseStore) });
388
+ return state
389
+ ? { head, tree: state.tree, ignored: state.ignored, gitRoot: state.gitRoot, links: state.links }
390
+ : { head, tree: null, ignored: null, gitRoot: null, links: null };
391
+ }
392
+
393
+ /**
394
+ * 089 IC-004 (review C-1): after a check ran with reuse on, read the tree again. The cached
395
+ * context is replaced when it moved, so later checks key on the tree they actually run on.
396
+ * Returns `unchanged` true only when the check's own payload digest is unchanged, i.e. its inputs
397
+ * did not move during the run and a PASS may be stored under the key it was looked up with, and
398
+ * `key`, the key of the tree the check ran on (null when the read faulted or cannot be keyed).
399
+ * FU-kerery: a FAIL whose pre-run key was null evicts by this post-run key.
400
+ */
401
+ rereadAfterRun(command , timeoutMs , payloadDigest ) {
402
+ const none = { unchanged: false, key: null };
403
+ if (!this.reuseStore) return none;
404
+ try {
405
+ const before = this._treeContext;
406
+ const after = this.readTreeContext();
407
+ if (after.tree === null) {
408
+ // 089 re-review N-a: a faulted read is not cached; the next check reads the tree again
409
+ // instead of keying null (and evicting nothing) for the rest of the contract.
410
+ this._treeContext = undefined;
411
+ return none;
412
+ }
413
+ if (!before || before.head !== after.head || before.tree !== after.tree || before.ignored !== after.ignored
414
+ || before.gitRoot !== after.gitRoot || before.links !== after.links) {
415
+ this._treeContext = after;
416
+ }
417
+ if (typeof after.ignored !== 'string') return none;
418
+ const keyed = checkKeyV2({ command, timeoutMs, cwd: this.cwd, head: after.head, tree: after.tree,
419
+ ignored: after.ignored, gitRoot: after.gitRoot, links: after.links, configDir: this.configDir,
420
+ env: this.env, fsImpl: this.fsImpl });
421
+ return { unchanged: Boolean(payloadDigest) && keyed?.payloadDigest === payloadDigest, key: keyed?.key ?? null };
422
+ } catch {
423
+ this._treeContext = undefined; // unknown state: the next check reads it afresh
424
+ return none;
425
+ }
426
+ }
427
+
428
+ /** 089 IC-003 (review C-2): an executed non-PASS removes the key's stored PASS, at every level. */
429
+ evictFailed(key ) {
430
+ if (!this.reuseStore || !key) return;
431
+ evictReuse({ dir: this.reuseStore.dir, key, fsImpl: this.fsImpl });
432
+ }
433
+
434
+ /**
435
+ * 089 IC-004: the recorded PASS for this key, as a check result labelled `reused`, or null. Its
436
+ * metrics entry says `shared: "reused"` with the record's ref; `durationMs` is the lookup time,
437
+ * and no cpu was spent. Lookup faults are misses (IC-003 never throws).
438
+ */
439
+ lookupReused(checkName , command , key , payloadDigest , startedAt ) {
440
+ if (!this.reuseContext?.enabled || !key || !payloadDigest) return null;
441
+ const t0 = monotonicNs();
442
+ const found = lookupReuse({ dir: this.reuseContext.dir, key, payloadDigest, maxAgeMs: this.reuseContext.maxAgeMs, fsImpl: this.fsImpl });
443
+ if (!found || found.hit !== true) return null;
444
+ const t1 = monotonicNs();
445
+ const record = found.record;
446
+ const result = {
447
+ name: checkName,
448
+ command,
449
+ status: 'PASS',
450
+ exitCode: 0,
451
+ stdout: record.result.stdout,
452
+ stderr: record.result.stderr,
453
+ reused: { ref: record.ref, recordedAt: record.recordedAt },
454
+ };
455
+ this.metrics.set(result, {
456
+ startedAt,
457
+ durationMs: t0 !== null && t1 !== null ? Number(t1 - t0) / 1e6 : null,
458
+ tests: record.result.tests ?? null,
459
+ testsFailed: record.result.testsFailed ?? null,
460
+ cpuMs: null,
461
+ resourceBasis: null,
462
+ checkKey: key,
463
+ shared: 'reused',
464
+ sharedRef: record.ref,
465
+ failedFirst: 'none',
466
+ });
467
+ return result;
468
+ }
469
+
470
+ /**
471
+ * 089 IC-002/IC-004: store an executed PASS while reuse is on. The stored result is the excerpt
472
+ * the report carries, elision marker included (the record bound leaves room for it), so a reused
473
+ * check shows exactly what the executed one showed.
474
+ */
475
+ storeReused(result , key , payloadDigest , res ) {
476
+ if (!this.reuseStore || !key || !payloadDigest || result.status !== 'PASS' || result.exitCode !== 0) return;
477
+ if (typeof result.stdout !== 'string' || typeof result.stderr !== 'string') return;
478
+ const counts = res?.bounded === true ? res : parseTestCounts(`${res?.stdout || ''}\n${res?.stderr || ''}`);
479
+ storeReuse({
480
+ dir: this.reuseStore.dir,
481
+ key,
482
+ payloadDigest,
483
+ level: this.reuseStore.level,
484
+ result: {
485
+ status: 'PASS',
486
+ exitCode: 0,
487
+ stdout: result.stdout,
488
+ stderr: result.stderr,
489
+ truncated: res?.bounded === true ? res.truncated === true
490
+ : (res?.stdout || '').length > MAX_STREAM_CHARS || (res?.stderr || '').length > MAX_STREAM_CHARS,
491
+ tests: counts.tests ?? null,
492
+ testsFailed: counts.testsFailed ?? null,
493
+ },
494
+ fsImpl: this.fsImpl,
495
+ });
113
496
  }
114
497
 
115
498
  /**
@@ -154,11 +537,26 @@ class VerificationContractRunner {
154
537
  * @param {string} [stdout]
155
538
  * @param {string} [stderr]
156
539
  */
157
- decorate(result , command , stdout , stderr ) {
540
+ decorate(result , command , stdout , stderr , execution ) {
541
+ if (execution?.bounded === true) {
542
+ for (const field of ['failedTests', 'failedFiles', 'logPath']) {
543
+ if (Object.hasOwn(execution, field)) result[field] = execution[field];
544
+ }
545
+ return;
546
+ }
158
547
  const full = `${stdout || ''}\n${stderr || ''}`;
159
548
  if (result.status === 'FAIL') {
160
549
  const failedTests = extractFailedTests(full);
161
550
  if (failedTests.length > 0) result.failedTests = failedTests;
551
+ if (this.convention) {
552
+ const failedFiles = extractFailedFiles(full, {
553
+ root: this.cwd,
554
+ convention: this.convention,
555
+ found: (this.convention ).found || (this ).found,
556
+ fsImpl: this.fsImpl,
557
+ });
558
+ if (failedFiles.length > 0) result.failedFiles = failedFiles;
559
+ }
162
560
  }
163
561
  const truncated = (stdout || '').length > MAX_STREAM_CHARS || (stderr || '').length > MAX_STREAM_CHARS;
164
562
  if ((result.status === 'FAIL' || truncated) && full.trim() !== '') {
@@ -166,6 +564,27 @@ class VerificationContractRunner {
166
564
  }
167
565
  }
168
566
 
567
+ /** Parse the full streams and write their log once, before publishing a bounded handoff.
568
+ * Keep the runner's existing head/tail excerpt so a follower reports the same diagnostics. */
569
+ summarizeExecution(res , name , command ) {
570
+ const diagnostics = { name, status: res.error || res.status !== 0 ? 'FAIL' : 'PASS' };
571
+ this.decorate(diagnostics, command, res.stdout, res.stderr);
572
+ const counts = parseTestCounts(`${res.stdout || ''}\n${res.stderr || ''}`);
573
+ return {
574
+ status: res.status,
575
+ signal: res.signal ?? null,
576
+ ...(res.error ? { error: { message: res.error.message, code: res.error.code ?? null } } : {}),
577
+ stdout: truncate(res.stdout),
578
+ stderr: truncate(res.stderr),
579
+ truncated: (res.stdout || '').length > MAX_STREAM_CHARS || (res.stderr || '').length > MAX_STREAM_CHARS,
580
+ bounded: true,
581
+ ...counts,
582
+ ...(diagnostics.failedTests ? { failedTests: diagnostics.failedTests } : {}),
583
+ ...(diagnostics.failedFiles ? { failedFiles: diagnostics.failedFiles } : {}),
584
+ ...(Object.hasOwn(diagnostics, 'logPath') ? { logPath: diagnostics.logPath } : {}),
585
+ };
586
+ }
587
+
169
588
  /**
170
589
  * Executes a single deterministic shell check.
171
590
  * @param {string} name
@@ -196,16 +615,103 @@ class VerificationContractRunner {
196
615
  if (dedupeCache && dedupeCache.has(dedupeKey)) {
197
616
  return { ...dedupeCache.get(dedupeKey), name: checkName, deduplicated: true };
198
617
  }
618
+ // Timing for telemetry (088 C.1, IC-006; 088 E.2, IC-007..IC-009) lives in `this.metrics`,
619
+ // never in the result, so the report is the same with telemetry on or off.
620
+ const startedAt = Date.now();
621
+ const t0 = monotonicNs();
622
+ const t0Cpu = childCpuTicks(this.fsImpl, this.platform);
623
+ let checkKeyVal = null;
624
+ let payloadDigest = null;
625
+ try {
626
+ if (!telemetryOff(this.env) || this.inflightContext?.enabled || this.reuseStore) {
627
+ if (this._treeContext === undefined) {
628
+ // 089 IC-001: one status call per contract yields the tree, the ignored set and the git
629
+ // root; when it faults all three are null and the key is null (never a partial key).
630
+ this._treeContext = this.readTreeContext();
631
+ }
632
+ const keyed = checkKeyV2({
633
+ command,
634
+ timeoutMs,
635
+ cwd: this.cwd,
636
+ head: this._treeContext.head,
637
+ tree: this._treeContext.tree,
638
+ ignored: this._treeContext.ignored,
639
+ gitRoot: this._treeContext.gitRoot,
640
+ links: this._treeContext.links,
641
+ configDir: this.configDir,
642
+ env: this.env,
643
+ fsImpl: this.fsImpl,
644
+ });
645
+ checkKeyVal = keyed?.key ?? null;
646
+ // Reuse trusts only a key whose ignored set came from this contract's own status call.
647
+ payloadDigest = typeof this._treeContext.ignored === 'string' ? keyed?.payloadDigest ?? null : null;
648
+ }
649
+ } catch {
650
+ checkKeyVal = null;
651
+ payloadDigest = null;
652
+ }
653
+
654
+ // 089 IC-004: after the in-run cache and the key, before in-flight sharing or execution.
655
+ const reusedResult = this.lookupReused(checkName, command, checkKeyVal, payloadDigest, startedAt);
656
+ if (reusedResult) {
657
+ if (dedupeCache) dedupeCache.set(dedupeKey, reusedResult);
658
+ return reusedResult;
659
+ }
660
+
661
+ let sharing = { shared: 'none', sharedRef: null };
662
+ let timing = null;
199
663
  const remember = (result ) => {
664
+ if (sharing.shared === 'follower') result.shared = { from: sharing.sharedRef };
665
+ if (timing) {
666
+ if (sharing.shared === 'follower') {
667
+ timing.durationMs = sharing.durationMs;
668
+ timing.cpuMs = null;
669
+ timing.resourceBasis = null;
670
+ }
671
+ this.metrics.set(result, timing);
672
+ }
200
673
  if (dedupeCache) dedupeCache.set(dedupeKey, result);
201
674
  return result;
202
675
  };
676
+ const stop = (res ) => {
677
+ const t1 = monotonicNs();
678
+ const t1Cpu = childCpuTicks(this.fsImpl, this.platform);
679
+ let cpuMs = null;
680
+ let resourceBasis = null;
681
+ if (this.platform !== 'linux') {
682
+ cpuMs = null;
683
+ resourceBasis = 'unreached-platform';
684
+ } else if (t0Cpu === null || t1Cpu === null) {
685
+ cpuMs = null;
686
+ resourceBasis = 'unreached-read';
687
+ } else {
688
+ cpuMs = Math.max(0, (t1Cpu - t0Cpu) * 10);
689
+ resourceBasis = 'proc-children';
690
+ }
691
+ const full = `${res ? res.stdout || '' : ''}\n${res ? res.stderr || '' : ''}`;
692
+ const { tests, testsFailed } = res?.bounded === true ? res : parseTestCounts(full);
693
+ timing = {
694
+ startedAt,
695
+ startedMonoMs: t0 !== null ? Number(t0) / 1e6 : null,
696
+ durationMs: t0 !== null && t1 !== null ? Number(t1 - t0) / 1e6 : null,
697
+ tests,
698
+ testsFailed,
699
+ cpuMs,
700
+ resourceBasis,
701
+ checkKey: checkKeyVal,
702
+ shared: sharing.shared,
703
+ sharedRef: sharing.sharedRef,
704
+ failedFirst: 'none',
705
+ };
706
+ };
203
707
 
204
708
  let res ;
205
709
  try {
206
- res = this.exec(command, {
710
+ const execute = () => this.exec(command, {
207
711
  shell: true,
208
712
  cwd: this.cwd,
713
+ // The key hashed this.env; with sharing or reuse on the command runs in exactly that env.
714
+ ...(this.inflightContext?.enabled || this.reuseStore ? { env: this.env } : {}),
209
715
  encoding: 'utf8',
210
716
  timeout: timeoutMs,
211
717
  killSignal: 'SIGKILL',
@@ -214,7 +720,17 @@ class VerificationContractRunner {
214
720
  // past anything a check legitimately prints. Only the first 2,000 chars are ever kept.
215
721
  maxBuffer: 64 * 1024 * 1024,
216
722
  });
723
+ if (this.inflightContext?.enabled && checkKeyVal) {
724
+ sharing = runInflight({ dir: this.inflightContext.dir, key: checkKeyVal, timeoutMs,
725
+ execute, summarize: (result ) => this.summarizeExecution(result, checkName, command), fsImpl: this.fsImpl });
726
+ res = sharing.result;
727
+ } else {
728
+ res = execute();
729
+ }
217
730
  } catch (error ) {
731
+ stop();
732
+ const reread = this.rereadAfterRun(command, timeoutMs, payloadDigest);
733
+ this.evictFailed(checkKeyVal ?? reread.key);
218
734
  // Mirrors the Python's bare `except Exception` arm: an unspawnable command is a FAIL, not a
219
735
  // thrown error that aborts the whole contract.
220
736
  return remember({
@@ -225,35 +741,46 @@ class VerificationContractRunner {
225
741
  error: error.message,
226
742
  });
227
743
  }
744
+ stop(res);
745
+ // Reuse on: the check may have changed what the key hashed. Read again before anything is stored.
746
+ const reread = this.rereadAfterRun(command, timeoutMs, payloadDigest);
747
+ const unchanged = reread.unchanged;
748
+ // FU-kerery: a FAIL evicts by the key read before the run, or by the key of the tree it ran on
749
+ // when that read faulted (null).
750
+ const failedKey = checkKeyVal ?? reread.key;
751
+ const stdout = res.bounded === true ? res.stdout : truncate(res.stdout);
752
+ const stderr = res.bounded === true ? res.stderr : truncate(res.stderr);
228
753
 
229
754
  // Defect fix (2): the Python's TimeoutExpired arm returned neither stdout nor stderr, so a
230
755
  // check that hung after printing its first failure told you only that it hung. Whatever the
231
756
  // process produced before the kill is exactly what a reader needs.
232
757
  if (res.error && res.error.code === 'ETIMEDOUT') {
758
+ this.evictFailed(failedKey);
233
759
  const result = {
234
760
  name: checkName,
235
761
  command,
236
762
  status: 'FAIL',
237
763
  exitCode: TIMEOUT_EXIT_CODE,
238
764
  error: 'TimeoutExpired',
239
- stdout: truncate(res.stdout),
240
- stderr: truncate(res.stderr),
765
+ stdout,
766
+ stderr,
241
767
  };
242
- this.decorate(result, command, res.stdout, res.stderr);
768
+ this.decorate(result, command, res.stdout, res.stderr, res);
243
769
  return remember(result);
244
770
  }
245
771
 
246
772
  if (res.error) {
773
+ this.evictFailed(failedKey);
247
774
  const result = {
248
775
  name: checkName,
249
776
  command,
250
777
  status: 'FAIL',
251
778
  exitCode: 1,
252
779
  error: res.error.message,
253
- stdout: truncate(res.stdout),
254
- stderr: truncate(res.stderr),
780
+ stdout,
781
+ stderr,
255
782
  };
256
- this.decorate(result, command, res.stdout, res.stderr);
783
+ this.decorate(result, command, res.stdout, res.stderr, res);
257
784
  return remember(result);
258
785
  }
259
786
 
@@ -268,13 +795,18 @@ class VerificationContractRunner {
268
795
  command,
269
796
  status: passed ? 'PASS' : 'FAIL',
270
797
  exitCode,
271
- stdout: truncate(res.stdout),
272
- stderr: truncate(res.stderr),
798
+ stdout,
799
+ stderr,
273
800
  };
274
801
  if (res.signal) {
275
802
  result.error = `Terminated by signal ${res.signal}`;
276
803
  }
277
- this.decorate(result, command, res.stdout, res.stderr);
804
+ this.decorate(result, command, res.stdout, res.stderr, res);
805
+ if (passed) {
806
+ if (unchanged) this.storeReused(result, checkKeyVal, payloadDigest, res);
807
+ } else {
808
+ this.evictFailed(failedKey);
809
+ }
278
810
  return remember(result);
279
811
  }
280
812
 
@@ -338,4 +870,7 @@ module.exports = {
338
870
  MAX_STREAM_CHARS,
339
871
  MAX_FAILED_TESTS,
340
872
  extractFailedTests,
873
+ extractFailedFiles,
874
+ parseTestCounts,
875
+ childCpuTicks,
341
876
  };