claude-dev-env 2.3.0 → 2.5.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 (290) hide show
  1. package/CLAUDE.md +53 -48
  2. package/_shared/pr-loop/scripts/_claude_permissions_common.py +84 -0
  3. package/_shared/pr-loop/scripts/code_rules_gate.py +4 -2
  4. package/_shared/pr-loop/scripts/grant_project_claude_permissions.py +306 -306
  5. package/_shared/pr-loop/scripts/pr_loop_shared_constants/claude_permissions_constants.py +44 -0
  6. package/_shared/pr-loop/scripts/pr_loop_shared_constants/copilot_quota_constants.py +24 -24
  7. package/_shared/pr-loop/scripts/pr_loop_shared_constants/stale_worktree_rule_sweep_constants.py +107 -107
  8. package/_shared/pr-loop/scripts/revoke_project_claude_permissions.py +290 -48
  9. package/_shared/pr-loop/scripts/tests/test_claude_permissions_common.py +42 -2
  10. package/_shared/pr-loop/scripts/tests/test_claude_permissions_constants.py +36 -0
  11. package/_shared/pr-loop/scripts/tests/test_code_rules_gate.py +100 -1
  12. package/_shared/pr-loop/scripts/tests/test_fix_hookspath.py +497 -497
  13. package/_shared/pr-loop/scripts/tests/test_revoke_project_claude_permissions.py +311 -2
  14. package/_shared/pr-loop/scripts/tests/test_stale_worktree_rule_sweep.py +301 -301
  15. package/_shared/pr-loop/scripts/tests/test_stale_worktree_rule_sweep_constants.py +85 -85
  16. package/_shared/pr-loop/worker-spawn.md +1 -1
  17. package/agents/CLAUDE.md +3 -1
  18. package/agents/caveman.md +0 -1
  19. package/agents/clasp-deployment-orchestrator.md +0 -1
  20. package/agents/clean-coder.md +0 -1
  21. package/agents/code-advisor.md +0 -1
  22. package/agents/code-quality-agent.md +1 -2
  23. package/agents/code-verifier.md +3 -4
  24. package/agents/deep-research.md +0 -1
  25. package/agents/docs-agent.md +0 -1
  26. package/agents/git-commit-crafter.md +0 -1
  27. package/agents/issue-tracker.md +42 -0
  28. package/agents/plan-packet-validator.md +0 -1
  29. package/agents/pr-description-writer.md +0 -1
  30. package/agents/skill-writer-agent.md +84 -0
  31. package/agents/test_agent_frontmatter.py +67 -18
  32. package/audit-rubrics/category_rubrics/category-o-docstring-vs-impl-drift.md +105 -3
  33. package/audit-rubrics/prompts/category-o-docstring-vs-impl-drift.md +29 -13
  34. package/bin/CLAUDE.md +68 -5
  35. package/bin/ever-shipped-skills.mjs +1 -0
  36. package/bin/install-constants.mjs +88 -0
  37. package/bin/install.mjs +1138 -114
  38. package/bin/install.prune.test.mjs +869 -19
  39. package/bin/install.test.mjs +906 -2
  40. package/commands/implement.md +1 -1
  41. package/commands/right-size.md +1 -1
  42. package/docs/CLAUDE.md +2 -0
  43. package/docs/host-pool-health-monitor.md +102 -0
  44. package/docs/references/CLAUDE.md +5 -2
  45. package/docs/references/advisor-tool.md +13 -0
  46. package/docs/references/code-review-enforcement.md +107 -0
  47. package/docs/references/team-advisor-skill.md +14 -0
  48. package/docs/wsl-docker-cowork-starter-matrix.md +89 -0
  49. package/hooks/blocking/CLAUDE.md +9 -1
  50. package/hooks/blocking/code_review_enforcement_config_bootstrap.py +53 -0
  51. package/hooks/blocking/code_review_gate_deny.py +74 -0
  52. package/hooks/blocking/code_review_pr_create_gate.py +198 -0
  53. package/hooks/blocking/code_review_push_gate.py +145 -0
  54. package/hooks/blocking/code_review_stamp_directory_write_blocker.py +348 -0
  55. package/hooks/blocking/code_review_stamp_store.py +233 -0
  56. package/hooks/blocking/code_review_stamp_write_blocker_parts/__init__.py +7 -0
  57. package/hooks/blocking/code_review_stamp_write_blocker_parts/conftest.py +15 -0
  58. package/hooks/blocking/code_review_stamp_write_blocker_parts/obfuscated_stamp_path_reference.py +212 -0
  59. package/hooks/blocking/code_review_stamp_write_blocker_parts/split_directory_change_into_stamp.py +138 -0
  60. package/hooks/blocking/code_review_stamp_write_blocker_parts/test_obfuscated_stamp_path_reference.py +49 -0
  61. package/hooks/blocking/code_review_stamp_write_blocker_parts/test_split_directory_change_into_stamp.py +38 -0
  62. package/hooks/blocking/code_verifier_spawn_preflight_gate.py +39 -27
  63. package/hooks/blocking/config/__init__.py +5 -5
  64. package/hooks/blocking/config/code_review_enforcement_constants.py +113 -0
  65. package/hooks/blocking/config/test_code_review_enforcement_constants.py +113 -0
  66. package/hooks/blocking/config/verified_commit_constants.py +160 -155
  67. package/hooks/blocking/conftest.py +2 -0
  68. package/hooks/blocking/convergence_gate_blocker.py +112 -23
  69. package/hooks/blocking/destructive_command_blocker.py +19 -6
  70. package/hooks/blocking/orchestrator_refresh_reschedule_gate.py +256 -0
  71. package/hooks/blocking/pr_description_proof_of_work.py +52 -34
  72. package/hooks/blocking/pre_tool_use_dispatcher.py +24 -24
  73. package/hooks/blocking/test_bash_pre_tool_use_dispatcher.py +4 -1
  74. package/hooks/blocking/test_code_review_enforcement_config_bootstrap.py +62 -0
  75. package/hooks/blocking/test_code_review_gate_deny.py +54 -0
  76. package/hooks/blocking/test_code_review_pr_create_gate.py +199 -0
  77. package/hooks/blocking/test_code_review_push_gate.py +205 -0
  78. package/hooks/blocking/test_code_review_stamp_directory_write_blocker.py +199 -0
  79. package/hooks/blocking/test_code_review_stamp_store.py +205 -0
  80. package/hooks/blocking/test_code_verifier_spawn_preflight_gate.py +124 -2
  81. package/hooks/blocking/test_convergence_gate_blocker.py +153 -5
  82. package/hooks/blocking/test_destructive_command_blocker.py +1 -1
  83. package/hooks/blocking/test_destructive_command_blocker_deny_mode.py +45 -0
  84. package/hooks/blocking/test_orchestrator_refresh_reschedule_gate.py +231 -0
  85. package/hooks/blocking/test_pr_description_proof_of_work.py +151 -0
  86. package/hooks/blocking/test_pre_tool_use_dispatcher.py +17 -8
  87. package/hooks/blocking/test_verdict_directory_write_blocker.py +808 -808
  88. package/hooks/blocking/test_verification_verdict_store.py +974 -903
  89. package/hooks/blocking/test_verified_commit_gate.py +581 -581
  90. package/hooks/blocking/test_verified_commit_message_accuracy_blocker.py +131 -131
  91. package/hooks/blocking/test_volatile_path_in_post_blocker.py +114 -2
  92. package/hooks/blocking/verdict_directory_write_blocker.py +687 -687
  93. package/hooks/blocking/verification_verdict_store.py +1039 -1014
  94. package/hooks/blocking/verified_commit_gate_parts/gated_invocations.py +29 -17
  95. package/hooks/blocking/verified_commit_gate_parts/tests/test_gated_invocations.py +35 -0
  96. package/hooks/blocking/verified_commit_message_accuracy_blocker.py +167 -167
  97. package/hooks/blocking/verifier_verdict_minter.py +280 -280
  98. package/hooks/blocking/volatile_path_in_post_blocker.py +69 -8
  99. package/hooks/git-hooks/git_hooks_constants/__init__.py +6 -0
  100. package/hooks/git-hooks/pre_push.py +89 -2
  101. package/hooks/git-hooks/test_pre_push.py +128 -0
  102. package/hooks/hooks.json +26 -1
  103. package/hooks/hooks_constants/CLAUDE.md +3 -1
  104. package/hooks/hooks_constants/bash_pre_tool_use_dispatcher_constants.py +8 -0
  105. package/hooks/hooks_constants/code_rules_path_utils_constants.py +1 -0
  106. package/hooks/hooks_constants/code_verifier_spawn_preflight_gate_constants.py +26 -11
  107. package/hooks/hooks_constants/convergence_gate_blocker_constants.py +20 -3
  108. package/hooks/hooks_constants/destructive_command_segment_constants.py +3 -1
  109. package/hooks/hooks_constants/enter_worktree_prefetch_constants.py +18 -18
  110. package/hooks/hooks_constants/orchestrator_refresh_reschedule_gate_constants.py +48 -0
  111. package/hooks/hooks_constants/pr_description_proof_of_work_constants.py +0 -4
  112. package/hooks/hooks_constants/pre_tool_use_dispatcher_constants.py +4 -0
  113. package/hooks/hooks_constants/pyproject_config_discovery_constants.py +16 -0
  114. package/hooks/hooks_constants/ruff_integration_constants.py +16 -0
  115. package/hooks/hooks_constants/test_bash_pre_tool_use_dispatcher_constants.py +24 -0
  116. package/hooks/hooks_constants/test_pre_tool_use_dispatcher_constants.py +6 -0
  117. package/hooks/hooks_constants/volatile_path_in_post_blocker_constants.py +8 -1
  118. package/hooks/lifecycle/enter_worktree_origin_prefetch.py +163 -146
  119. package/hooks/lifecycle/test_enter_worktree_origin_prefetch.py +185 -178
  120. package/hooks/pyproject.toml +1 -0
  121. package/hooks/validators/CLAUDE.md +2 -0
  122. package/hooks/validators/config/__init__.py +0 -0
  123. package/hooks/validators/config/directory_exemption_constants.py +183 -0
  124. package/hooks/validators/config/test_directory_exemption_constants.py +21 -0
  125. package/hooks/validators/conftest.py +4 -0
  126. package/hooks/validators/mypy_integration.py +63 -50
  127. package/hooks/validators/pyproject_config_discovery.py +101 -0
  128. package/hooks/validators/ruff_integration.py +257 -25
  129. package/hooks/validators/run_all_validators.py +223 -19
  130. package/hooks/validators/test_directory_exemption_constants.py +185 -0
  131. package/hooks/validators/test_mypy_integration.py +32 -0
  132. package/hooks/validators/test_pyproject_config_discovery.py +94 -0
  133. package/hooks/validators/test_python_antipattern_checks.py +110 -5
  134. package/hooks/validators/test_ruff_integration.py +160 -2
  135. package/hooks/validators/test_run_all_validators.py +140 -68
  136. package/hooks/validators/test_run_all_validators_config_discovery.py +123 -0
  137. package/hooks/validators/test_run_all_validators_pretooluse.py +159 -1
  138. package/package.json +10 -2
  139. package/rules/CLAUDE.md +1 -0
  140. package/rules/docstring-prose-matches-implementation.md +45 -67
  141. package/rules/durable-post-artifacts.md +7 -0
  142. package/rules/state-what-is.md +25 -0
  143. package/rules/verified-commit-gate-skip.md +1 -1
  144. package/scripts/CLAUDE.md +1 -0
  145. package/scripts/Capture-PoolHealth.ps1 +410 -0
  146. package/scripts/_code_review_test_support.py +404 -0
  147. package/scripts/claude_chain_runner.py +141 -1
  148. package/scripts/codec_forwarding_test_support.py +83 -0
  149. package/scripts/conftest.py +23 -0
  150. package/scripts/dev_env_scripts_constants/CLAUDE.md +2 -2
  151. package/scripts/dev_env_scripts_constants/claude_chain_constants.py +53 -1
  152. package/scripts/dev_env_scripts_constants/code_review_constants.py +129 -12
  153. package/scripts/dev_env_scripts_constants/test_code_review_constants.py +55 -0
  154. package/scripts/invoke_code_review.py +550 -38
  155. package/scripts/resolve_worker_spawn.py +626 -619
  156. package/scripts/spawn_grok_batch.py +672 -672
  157. package/scripts/test_claude_chain_runner.py +131 -0
  158. package/scripts/test_invoke_code_review_chain.py +70 -0
  159. package/scripts/test_invoke_code_review_cli.py +192 -0
  160. package/scripts/test_invoke_code_review_codec.py +77 -0
  161. package/scripts/test_invoke_code_review_contract.py +256 -0
  162. package/scripts/test_invoke_code_review_git.py +123 -0
  163. package/scripts/test_invoke_code_review_mode.py +99 -0
  164. package/scripts/test_resolve_worker_spawn.py +1014 -1014
  165. package/scripts/test_resolve_worker_spawn_codec.py +101 -0
  166. package/skills/CLAUDE.md +2 -0
  167. package/skills/auditing-claude-config/SKILL.md +114 -114
  168. package/skills/autoconverge/SKILL.md +427 -421
  169. package/skills/autoconverge/reference/convergence.md +26 -4
  170. package/skills/autoconverge/reference/multi-pr.md +6 -1
  171. package/skills/autoconverge/reference/stop-conditions.md +16 -10
  172. package/skills/autoconverge/workflow/CLAUDE.md +1 -0
  173. package/skills/autoconverge/workflow/converge.clean-audit.test.mjs +4 -4
  174. package/skills/autoconverge/workflow/converge.codex-gate.test.mjs +175 -3
  175. package/skills/autoconverge/workflow/converge.contract.test.mjs +1263 -1244
  176. package/skills/autoconverge/workflow/converge.mjs +191 -8
  177. package/skills/autoconverge/workflow/converge.p2-advance.test.mjs +202 -0
  178. package/skills/autoconverge/workflow/converge_multi.mjs +7 -3
  179. package/skills/autoconverge/workflow/converge_multi.run-input.test.mjs +5 -0
  180. package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a11d903476b803493.jsonl +2 -2
  181. package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a26213978adeef6fb.jsonl +2 -2
  182. package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a3def0d15ed9d9110.jsonl +2 -2
  183. package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a41f41b1b708ee3b7.jsonl +2 -2
  184. package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a758b880abecc3ff7.jsonl +2 -2
  185. package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a8897b89656b1bd16.jsonl +2 -2
  186. package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-abd463d744a1437bc.jsonl +2 -2
  187. package/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-ad19d027ae8ee1816.jsonl +2 -2
  188. package/skills/autoconverge/workflow/fixtures/wf_run/workflows/wf_881252e6-700.json +265 -265
  189. package/skills/closeout/SKILL.md +33 -50
  190. package/skills/codex-review/scripts/codex_review_scripts_constants/run_constants.py +8 -0
  191. package/skills/codex-review/scripts/run_codex_review.py +233 -1
  192. package/skills/codex-review/scripts/test_run_codex_review.py +189 -0
  193. package/skills/condensing-instructions/SKILL.md +81 -0
  194. package/skills/copilot-review/SKILL.md +119 -119
  195. package/skills/e-code-review/SKILL.md +52 -0
  196. package/skills/e-code-review/reference/fix.md +54 -0
  197. package/skills/e-code-review/reference/loop.md +43 -0
  198. package/skills/e-code-review/reference/low.md +57 -0
  199. package/skills/e-code-review/reference/medium.md +153 -0
  200. package/skills/e-code-review/reference/xhigh.md +182 -0
  201. package/skills/e-simplify/SKILL.md +97 -0
  202. package/skills/fresh-branch/CLAUDE.md +2 -0
  203. package/skills/fresh-branch/SKILL.md +2 -0
  204. package/skills/fresh-branch/scripts/create_fresh_branch.py +78 -180
  205. package/skills/fresh-branch/scripts/fresh_branch_git_commands.py +285 -0
  206. package/skills/fresh-branch/scripts/fresh_branch_scripts_constants/fresh_branch_cli_constants.py +1 -0
  207. package/skills/fresh-branch/scripts/pytest.ini +4 -0
  208. package/skills/fresh-branch/scripts/test_create_fresh_branch.py +98 -0
  209. package/skills/fresh-branch/scripts/test_fresh_branch_git_commands.py +310 -0
  210. package/skills/issue-tracker/SKILL.md +92 -0
  211. package/skills/issue-tracker/reference/epic-and-sub-issue-model.md +55 -0
  212. package/skills/issue-tracker/reference/handoff-schema.md +64 -0
  213. package/skills/issue-tracker/reference/operation-matrix.md +41 -0
  214. package/skills/orchestrator/SKILL.md +162 -21
  215. package/skills/orchestrator/scripts/status_gate.py +625 -0
  216. package/skills/orchestrator/scripts/status_gate_constants/__init__.py +1 -0
  217. package/skills/orchestrator/scripts/status_gate_constants/config/__init__.py +1 -0
  218. package/skills/orchestrator/scripts/status_gate_constants/config/constants.py +47 -0
  219. package/skills/orchestrator/scripts/test_status_gate.py +439 -0
  220. package/skills/orchestrator-refresh/SKILL.md +110 -35
  221. package/skills/plan-to-pr/SKILL.md +155 -0
  222. package/skills/plan-to-pr/reference/final-validation-tasks.md +15 -0
  223. package/skills/plan-to-pr/reference/model-routing.md +36 -0
  224. package/skills/plan-to-pr/reference/packet-contract.md +43 -0
  225. package/skills/plan-to-pr/reference/packet-schema.json +57 -0
  226. package/skills/plan-to-pr/reference/process-inventory.md +22 -0
  227. package/skills/plan-to-pr/reference/review-loop.md +33 -0
  228. package/skills/plan-to-pr/reference/run-record.schema.json +27 -0
  229. package/skills/plan-to-pr/reference/self-audit-tasks.md +15 -0
  230. package/skills/plan-to-pr/reference/task-seeds.md +14 -0
  231. package/skills/plan-to-pr/reference/task-ticket.md +38 -0
  232. package/skills/plan-to-pr/scripts/config/__init__.py +1 -0
  233. package/skills/plan-to-pr/scripts/config/constants.py +193 -0
  234. package/skills/plan-to-pr/scripts/create_packet.py +173 -0
  235. package/skills/plan-to-pr/scripts/test_create_packet.py +102 -0
  236. package/skills/plan-to-pr/scripts/test_validate_packet.py +256 -0
  237. package/skills/plan-to-pr/scripts/test_validate_protocol.py +135 -0
  238. package/skills/plan-to-pr/scripts/test_validate_run.py +158 -0
  239. package/skills/plan-to-pr/scripts/validate_packet.py +655 -0
  240. package/skills/plan-to-pr/scripts/validate_protocol.py +622 -0
  241. package/skills/plan-to-pr/scripts/validate_run.py +173 -0
  242. package/skills/plan-to-pr/test_skill_contract.py +207 -0
  243. package/skills/plan-to-pr/test_task_ticket_contract.py +151 -0
  244. package/skills/pr-converge/SKILL.md +472 -469
  245. package/skills/pr-converge/reference/examples.md +3 -3
  246. package/skills/pr-converge/reference/fix-protocol.md +1 -1
  247. package/skills/pr-converge/reference/ground-rules.md +7 -4
  248. package/skills/pr-converge/reference/multi-pr-orchestration.md +4 -1
  249. package/skills/pr-converge/reference/per-tick.md +5 -5
  250. package/skills/pr-converge/reference/progress-checklist.md +1 -1
  251. package/skills/pr-converge/scripts/check_convergence_gates.py +279 -279
  252. package/skills/pr-converge/scripts/test_check_convergence_codex.py +507 -507
  253. package/skills/pr-converge/scripts/test_check_convergence_gates.py +84 -84
  254. package/skills/pr-converge/test_step5_host_branch.py +1 -1
  255. package/skills/pr-fix-protocol/SKILL.md +1 -1
  256. package/skills/privacy-hygiene/SKILL.md +68 -68
  257. package/skills/prototype/SKILL.md +86 -0
  258. package/skills/prototype/reference/honest-limitations.md +23 -0
  259. package/skills/prototype/reference/promotion-tasks.md +23 -0
  260. package/skills/prototype/scripts/build_sandbox_settings.py +249 -0
  261. package/skills/prototype/scripts/conftest.py +15 -0
  262. package/skills/prototype/scripts/launch_sandbox.py +205 -0
  263. package/skills/prototype/scripts/probe_sandbox_safety.py +311 -0
  264. package/skills/prototype/scripts/prototype_scripts_constants/__init__.py +1 -0
  265. package/skills/prototype/scripts/prototype_scripts_constants/config/__init__.py +0 -0
  266. package/skills/prototype/scripts/prototype_scripts_constants/config/build_sandbox_settings_constants.py +41 -0
  267. package/skills/prototype/scripts/prototype_scripts_constants/config/launch_sandbox_constants.py +23 -0
  268. package/skills/prototype/scripts/prototype_scripts_constants/config/probe_sandbox_safety_constants.py +45 -0
  269. package/skills/prototype/scripts/prototype_scripts_constants/config/prototype_common_constants.py +10 -0
  270. package/skills/prototype/scripts/test_build_sandbox_settings.py +275 -0
  271. package/skills/prototype/scripts/test_launch_sandbox.py +303 -0
  272. package/skills/prototype/scripts/test_probe_sandbox_safety.py +284 -0
  273. package/skills/prototype/workflows/promotion.md +27 -0
  274. package/skills/prototype/workflows/sandbox.md +35 -0
  275. package/skills/release-notes-html/SKILL.md +164 -0
  276. package/skills/skill-builder/CLAUDE.md +3 -3
  277. package/skills/skill-builder/SKILL.md +5 -5
  278. package/skills/skill-builder/references/CLAUDE.md +1 -1
  279. package/skills/skill-builder/references/delegation-map.md +3 -3
  280. package/skills/skill-builder/references/description-field.md +1 -1
  281. package/skills/skill-builder/references/skill-modularity.md +2 -3
  282. package/skills/skill-builder/workflows/CLAUDE.md +1 -1
  283. package/skills/skill-builder/workflows/improve-skill.md +1 -1
  284. package/skills/skill-builder/workflows/new-skill.md +2 -2
  285. package/skills/task-build/CLAUDE.md +8 -7
  286. package/skills/task-build/SKILL.md +16 -8
  287. package/skills/task-build/reference/tool-routing.md +19 -0
  288. package/skills/team-advisor/SKILL.md +2 -2
  289. package/scripts/test_invoke_code_review.py +0 -672
  290. package/skills/closeout/reference/issue-body-templates.md +0 -108
@@ -106,6 +106,18 @@ follow-up issue listing the findings, opens a draft environment-hardening PR
106
106
  (hooks/rules that block those violation classes at Write/Edit time), resolves
107
107
  any bot threads with a deferral note, and reports the deferral in
108
108
  `standardsNote`. The hardening PRs land in the run result's `deferredPrs` list.
109
+ Standards-only deferral still takes precedence when every finding is category
110
+ `code-standard`.
111
+
112
+ When the already-deduped findings for a phase are all severity P2 (and not
113
+ standards-only), the phase still applies the fixes once. If the fix does **not**
114
+ move HEAD (for example `resolvedWithoutCommit`), the phase treats that HEAD as
115
+ clean and advances forward. If the fix **does** move HEAD, the run re-enters
116
+ CONVERGE on the fixed SHA so HEAD-bound CLEAN/review stamps rebuild before
117
+ FINALIZE — those stamps are SHA-specific. The three CONVERGE reading lenses stay
118
+ parallel; the P2-only decision is a routing branch on the deduped set after
119
+ `resolveRoundOutcome` / gate classification. Mixed P0/P1 findings keep the
120
+ existing fix + re-converge path.
109
121
 
110
122
  **BUGBOT** gate (terminal external confirmation):
111
123
 
@@ -115,7 +127,10 @@ any bot threads with a deferral note, and reports the deferral in
115
127
  to the Copilot gate with `bugbotDown` set.
116
128
  - Otherwise drive Cursor Bugbot to a verdict on HEAD (trigger and poll its CI
117
129
  check run when needed).
118
- - Bugbot findings fix them and return to CONVERGE on the new HEAD.
130
+ - Bugbot findings that are not standards-only and not P2-only fix them and
131
+ return to CONVERGE on the new HEAD.
132
+ - Bugbot P2-only findings → fix once; advance to Copilot only when HEAD is
133
+ unchanged, otherwise re-converge on the fixed HEAD.
119
134
  - Bugbot clean or approved → move to the Copilot gate.
120
135
  - Bugbot down after the poll cap → move to the Copilot gate with `bugbotDown` set.
121
136
 
@@ -123,7 +138,10 @@ any bot threads with a deferral note, and reports the deferral in
123
138
 
124
139
  - Request a Copilot review on HEAD (skipping a duplicate request), then poll up
125
140
  to the configured cap, 360 seconds apart.
126
- - Copilot findings fix them and return to CONVERGE on the new HEAD.
141
+ - Copilot findings that are not standards-only and not P2-only fix them and
142
+ return to CONVERGE on the new HEAD.
143
+ - Copilot P2-only findings → fix once; advance to Codex only when HEAD is
144
+ unchanged, otherwise re-converge on the fixed HEAD.
127
145
  - Copilot clean or approved → move to the Codex gate.
128
146
  - Copilot down or out of quota (an out-of-usage notice, or no review after the
129
147
  configured cap) → log a notice and move to the Codex gate with the Copilot gate
@@ -135,11 +153,15 @@ any bot threads with a deferral note, and reports the deferral in
135
153
  the weekly usage probe via `is_codex_review_required` (shared threshold
136
154
  constant — no inline percent), and the wrapper's `codex_down` class.
137
155
  - Opt-out token or `codex_down` → set `codexDown`, no stamp, move to the
138
- convergence check with `--codex-down`.
156
+ convergence check with `--codex-down`. A `codex_down` classification also
157
+ records a `codexNote` so the skip stays visible in the final report.
139
158
  - Usage at/below threshold or null → skip with no stamp; the machine checklist
140
159
  applies the same rule.
141
160
  - Above threshold → run the codex-review wrapper against the PR base branch.
142
- Non–code-standard findings → fix and return to CONVERGE.
161
+ Non–code-standard, non–P2-only findings → fix and return to CONVERGE.
162
+ P2-only findings → fix once; when HEAD is unchanged, stamp `codexCleanAt` and
163
+ move to the convergence check (no re-converge), otherwise re-converge on the
164
+ fixed HEAD.
143
165
  Standards-only findings → defer a follow-up, stamp `codexCleanAt`, and move to the
144
166
  convergence check (no fix push).
145
167
  Clean → stamp `codexCleanAt` and pass `--codex-clean-at` into the convergence check.
@@ -72,6 +72,7 @@ Workflow({
72
72
  scriptPath: "<this skill dir>/workflow/converge_multi.mjs",
73
73
  args: {
74
74
  convergeScriptPath: "<this skill dir>/workflow/converge.mjs",
75
+ homeDirectory: "<HOME>",
75
76
  prs: [
76
77
  { owner: "<O>", repo: "<R>", prNumber: <N1>, repoPath: "<abs worktree 1>", bugbotDisabled: false, copilotDisabled: false },
77
78
  { owner: "<O>", repo: "<R>", prNumber: <N2>, repoPath: "<abs worktree 2>", bugbotDisabled: false, copilotDisabled: false }
@@ -82,7 +83,11 @@ Workflow({
82
83
 
83
84
  `convergeScriptPath` is the absolute path to `workflow/converge.mjs` in this same
84
85
  skill directory; each `repoPath` is the absolute path of the worktree that PR is
85
- checked out in. The workflow runs in the background and notifies the session on
86
+ checked out in. `homeDirectory` is the absolute path to the directory that holds
87
+ `.claude` (resolve it once from `$HOME`, or `$env:USERPROFILE` on Windows, with
88
+ forward slashes); the fan-out forwards it to every child run, which needs it to
89
+ build the codex-review scripts path because the workflow sandbox has no access to
90
+ environment variables. The workflow runs in the background and notifies the session on
86
91
  completion; watch live progress with `/workflows`, where each PR's child run
87
92
  appears under its own group.
88
93
 
@@ -88,16 +88,22 @@ Each reviewer-down condition below skips its own convergence-check gate. The fla
88
88
  `--copilot-down` (the Copilot review gate and the pending-requested-reviews
89
89
  gate bypassed), then mark-ready. `copilotNote` records the bypass for the
90
90
  final report.
91
- - **Terminal Codex gate skipped or down** — the conditional-required Codex gate
92
- runs after Bugbot and Copilot. When `CLAUDE_REVIEWS_DISABLED` lists `codex`
93
- (`reviews_disabled.py --reviewer codex` exit 0), the gate sets `codexDown` and
94
- advances with no review. When the weekly usage probe reports `percent_left`
95
- null or at/below the shared threshold (`is_codex_review_required` false), the
96
- gate skips without a stamp; the convergence check applies the same rule. When
97
- the wrapper classifies `codex_down`, the gate sets `codexDown` and advances.
98
- A clean required run stamps `codexCleanAt` for `--codex-clean-at`. Non-code-standard
99
- findings re-enter CONVERGE through the existing fix path. Standards-only findings
100
- defer a follow-up, stamp `codexCleanAt`, and advance to FINALIZE (no fix push).
91
+ - **Terminal Codex gate skipped (opt-out, usage, or `codex_down`)** — the
92
+ conditional-required Codex gate runs after Bugbot and Copilot. When
93
+ `CLAUDE_REVIEWS_DISABLED` lists `codex` (`reviews_disabled.py --reviewer codex`
94
+ exit 0), the gate sets `codexDown` and advances with no review. When the weekly
95
+ usage probe reports `percent_left` null or at/below the shared threshold
96
+ (`is_codex_review_required` false), the gate skips without a stamp; the
97
+ convergence check applies the same rule. When the codex-review wrapper
98
+ classifies `codex_down` a Codex CLI error, or a review that ran past its
99
+ `CONFIG.codexReviewTimeoutSeconds` budget the gate skips the same way: it sets
100
+ `codexDown`, records a `codexNote` so the skip stays visible in the final
101
+ report, clears `codexCleanAt`, and advances to `FINALIZE` (mark-ready with the
102
+ gate bypassed) so a Codex outage does not block convergence; Codex runs again on
103
+ the next convergence. A clean required run stamps `codexCleanAt` for
104
+ `--codex-clean-at`. Non-code-standard findings re-enter `CONVERGE` through the
105
+ existing fix path. Standards-only findings defer a follow-up, stamp
106
+ `codexCleanAt`, and advance to `FINALIZE` (no fix push).
101
107
  - **A lens agent dies** — when one parallel reading lens returns null (a terminal
102
108
  agent failure), the round proceeds on the surviving lenses. A real defect it
103
109
  would have caught surfaces in a later round or at the convergence check. A dead
@@ -18,6 +18,7 @@ Workflow scripts and report utilities for the `autoconverge` skill.
18
18
  | `converge.precatch.test.mjs` | Tests the pre-catch stage: static-sweep ordering and gate coverage, the adversarial second pass, the self-review lens, the terminal Bugbot gate, the read-only/edit preamble split, and the commit-recovery cap. |
19
19
  | `converge.fix-progress.test.mjs` | Tests fix-progress tracking across rounds. |
20
20
  | `converge.fix-recovery.test.mjs` | Tests recovery when a fix commit fails. |
21
+ | `converge.p2-advance.test.mjs` | Tests P2-only fix-then-advance helpers and phase routing. |
21
22
  | `converge.run-input.test.mjs` | Tests workflow input validation. |
22
23
  | `test_aggregate_runs.py` | Tests for `aggregate_runs.py`. |
23
24
  | `test_convergence_summary.py` | Tests for `convergence_summary.py`. |
@@ -215,7 +215,7 @@ test('every no-lens-reviewed round registers one shared counter and every lens-r
215
215
  const registerSites = loopBody.match(/registerNoLensRound\(/g) || [];
216
216
  assert.equal(registerSites.length, 3, 'expected the preflight-no-SHA, all-lenses-dead, and no-lens-ran rounds to register');
217
217
  const resetSites = loopBody.match(/resetNoLensRounds\(\)/g) || [];
218
- assert.equal(resetSites.length, 5, 'expected a reset on the static-sweep, standards, findings, not-clean, and posted lens-ran rounds');
218
+ assert.equal(resetSites.length, 6, 'expected a reset on the static-sweep, standards, P2-only, findings, not-clean, and posted lens-ran rounds');
219
219
  const deadBranch = convergeSource.slice(
220
220
  convergeSource.indexOf('if (roundOutcome.allLensesDead) {'),
221
221
  convergeSource.indexOf('const findings = roundOutcome.findings'),
@@ -533,7 +533,7 @@ test('CLEAN_AUDIT_SCHEMA requires posted, reviewUrl, and reason', () => {
533
533
  test('the standards-only call site records a clean-audit bypass and continues to BUGBOT when the post does not land', () => {
534
534
  const branch = convergeSource.slice(
535
535
  convergeSource.indexOf('if (isStandardsOnlyRound(findings)) {'),
536
- convergeSource.indexOf('if (findings.length > 0) {'),
536
+ convergeSource.indexOf('if (isP2OnlyFindings(findings)) {'),
537
537
  );
538
538
  assert.match(branch, /runGeneralUtilityTask\(.*'post-clean-audit'/);
539
539
  assert.match(branch, /cleanAuditNote = resolveCleanAuditNote\(auditResult, head, rounds\)/);
@@ -545,7 +545,7 @@ test('the standards-only call site records a clean-audit bypass and continues to
545
545
  test('the standards-only call site relays lens provenance, the deferred standards findings, and the deferral filing state', () => {
546
546
  const branch = convergeSource.slice(
547
547
  convergeSource.indexOf('if (isStandardsOnlyRound(findings)) {'),
548
- convergeSource.indexOf('if (findings.length > 0) {'),
548
+ convergeSource.indexOf('if (isP2OnlyFindings(findings)) {'),
549
549
  );
550
550
  assert.match(branch, /const namedLenses = nameLensResults\(lenses\)/);
551
551
  assert.match(branch, /lensResults: namedLenses/);
@@ -655,7 +655,7 @@ test('the workflow declares cleanAuditNote and assembles it into every result vi
655
655
  assert.match(convergeSource, /let cleanAuditNote = null/);
656
656
  assert.match(
657
657
  convergeSource,
658
- /const assembleResult = \(outcomeFields\) => \(\{\n\s*\.\.\.outcomeFields,\n\s*standardsNote,\n\s*copilotNote,\n\s*cleanAuditNote,\n\s*reuseNote,\n\s*deferredPrs,\n\s*\}\)/,
658
+ /const assembleResult = \(outcomeFields\) => \(\{\n\s*\.\.\.outcomeFields,\n\s*standardsNote,\n\s*copilotNote,\n\s*codexNote,\n\s*cleanAuditNote,\n\s*reuseNote,\n\s*deferredPrs,\n\s*\}\)/,
659
659
  );
660
660
  const assembleCalls = convergeSource.match(/return assembleResult\(/g) || [];
661
661
  assert.ok(
@@ -178,7 +178,118 @@ test('the Codex gate prompt runs the review wrapper against the PR base branch',
178
178
  assert.match(codexPrompt, /parse_codex_findings/, 'expected findings to come from the shared parser');
179
179
  });
180
180
 
181
- test('the CODEX phase stamps codexCleanAt on clean and clears it on skip/down', () => {
181
+ test('the Codex gate prompt passes an explicit generous timeout so a slow-but-healthy review is not cut short', () => {
182
+ const codexPrompt = functionBody('runCodexGate');
183
+ assert.match(
184
+ codexPrompt,
185
+ /timeout_seconds=\$\{CONFIG\.codexReviewTimeoutSeconds\}/,
186
+ 'expected the driver to pass CONFIG.codexReviewTimeoutSeconds, overriding the too-small wrapper default',
187
+ );
188
+ });
189
+
190
+ test('CONFIG.codexReviewTimeoutSeconds exceeds the observed real Codex runtime', () => {
191
+ const match = convergeSource.match(/codexReviewTimeoutSeconds:\s*(\d+)/);
192
+ assert.notEqual(match, null, 'expected CONFIG.codexReviewTimeoutSeconds to be defined');
193
+ const budgetSeconds = Number(match[1]);
194
+ assert.ok(
195
+ budgetSeconds >= 900,
196
+ `expected a budget comfortably above the ~683s measured Codex runtime, got ${budgetSeconds}`,
197
+ );
198
+ });
199
+
200
+ test('the Codex gate prompt runs the review in the background rather than the default foreground timeout', () => {
201
+ const codexPrompt = functionBody('runCodexGate');
202
+ assert.match(
203
+ codexPrompt,
204
+ /run_in_background/i,
205
+ 'expected the review driver to run in the background — the foreground tool timeout caps below the review runtime and would kill a healthy review',
206
+ );
207
+ assert.match(
208
+ codexPrompt,
209
+ /result file/i,
210
+ 'expected the driver to persist its outcome to a result file the agent reads once the background run finishes',
211
+ );
212
+ });
213
+
214
+ test('the Codex gate prompt waits for the background review inside the same turn with a bounded Monitor loop', () => {
215
+ const codexPrompt = functionBody('runCodexGate');
216
+ assert.match(codexPrompt, /Monitor tool/, 'expected a same-turn Monitor wait for the background review');
217
+ assert.match(codexPrompt, /until-loop/, 'expected a bounded until-loop that re-checks the result file');
218
+ assert.match(codexPrompt, /codexReviewPollIntervalSeconds/, 'expected a named poll interval in the wait protocol');
219
+ assert.match(codexPrompt, /codexReviewMaxPolls/, 'expected a named attempt budget in the wait protocol');
220
+ assert.match(
221
+ codexPrompt,
222
+ /Do NOT end this turn/i,
223
+ 'expected an explicit instruction against ending the schema-bearing turn to await the background run',
224
+ );
225
+ });
226
+
227
+ test('the Codex background-review poll budget covers the review timeout', () => {
228
+ const intervalMatch = convergeSource.match(/codexReviewPollIntervalSeconds:\s*(\d+)/);
229
+ const pollsMatch = convergeSource.match(/codexReviewMaxPolls:\s*(\d+)/);
230
+ const timeoutMatch = convergeSource.match(/codexReviewTimeoutSeconds:\s*(\d+)/);
231
+ assert.notEqual(intervalMatch, null, 'expected codexReviewPollIntervalSeconds in CONFIG');
232
+ assert.notEqual(pollsMatch, null, 'expected codexReviewMaxPolls in CONFIG');
233
+ assert.notEqual(timeoutMatch, null, 'expected codexReviewTimeoutSeconds in CONFIG');
234
+ const intervalSeconds = Number(intervalMatch[1]);
235
+ const maxPolls = Number(pollsMatch[1]);
236
+ const timeoutSeconds = Number(timeoutMatch[1]);
237
+ assert.ok(
238
+ intervalSeconds * maxPolls >= timeoutSeconds,
239
+ `expected the poll budget ${intervalSeconds * maxPolls}s to cover the ${timeoutSeconds}s review timeout`,
240
+ );
241
+ });
242
+
243
+ test('the Codex gate prompt returns codex_down when the background poll budget is spent', () => {
244
+ const codexPrompt = functionBody('runCodexGate');
245
+ assert.match(
246
+ codexPrompt,
247
+ /full poll budget is spent with no result file[\s\S]*?down:true/,
248
+ 'expected a spent-budget branch that returns the codex_down schema shape',
249
+ );
250
+ });
251
+
252
+ test('meta Codex gate phase describes codex_down as a non-blocking skip that logs a note', () => {
253
+ const phaseStart = convergeSource.indexOf("title: 'Codex gate'");
254
+ const phaseEnd = convergeSource.indexOf("title: 'Finalize'");
255
+ assert.notEqual(phaseStart, -1, 'expected a Codex gate phase in meta');
256
+ const codexPhaseDetail = convergeSource.slice(phaseStart, phaseEnd);
257
+ assert.match(codexPhaseDetail, /codex_down/, 'expected the phase detail to name codex_down');
258
+ assert.match(
259
+ codexPhaseDetail,
260
+ /codex_down, the gate skips without blocking/,
261
+ 'expected codex_down to be described as a non-blocking skip',
262
+ );
263
+ assert.match(
264
+ codexPhaseDetail,
265
+ /logged as a note/,
266
+ 'expected the codex_down skip to be logged as a note so the bypass stays visible',
267
+ );
268
+ assert.doesNotMatch(
269
+ codexPhaseDetail,
270
+ /holds the PR as a draft with a codex-down blocker/,
271
+ 'codex_down must not be described as holding the PR draft with a blocker',
272
+ );
273
+ });
274
+
275
+ test('CODEX_SCHEMA down.description describes codex_down as a skipped, bypassed gate recorded as a note', () => {
276
+ const schemaStart = convergeSource.indexOf('const CODEX_SCHEMA =');
277
+ const schemaEnd = convergeSource.indexOf('const COPILOT_VERIFY_VERDICTS =');
278
+ assert.notEqual(schemaStart, -1, 'expected CODEX_SCHEMA to exist');
279
+ const schemaSource = convergeSource.slice(schemaStart, schemaEnd);
280
+ const downLineStart = schemaSource.indexOf('down: {');
281
+ const downLine = schemaSource.slice(downLineStart, schemaSource.indexOf('\n', downLineStart));
282
+ assert.match(downLine, /codex_down/, 'expected the down description to name codex_down');
283
+ assert.match(downLine, /skipped and bypassed/, 'expected codex_down to be described as a skipped, bypassed gate');
284
+ assert.match(downLine, /recorded as a note/, 'expected codex_down to be recorded as a note');
285
+ assert.doesNotMatch(
286
+ downLine,
287
+ /holds the PR as a draft with a codex-down blocker/,
288
+ 'the down description must not claim codex_down holds the PR draft as a blocker',
289
+ );
290
+ });
291
+
292
+ test('the CODEX phase stamps codexCleanAt on clean and clears it on an opt-out, usage, or down skip', () => {
182
293
  const codexPhaseStart = convergeSource.indexOf("if (phase === 'CODEX') {");
183
294
  const finalizePhaseStart = convergeSource.indexOf("if (phase === 'FINALIZE') {", codexPhaseStart);
184
295
  assert.notEqual(codexPhaseStart, -1, 'expected a CODEX phase block');
@@ -192,6 +303,36 @@ test('the CODEX phase stamps codexCleanAt on clean and clears it on skip/down',
192
303
  assert.match(codexPhase, /kind === 'down'[\s\S]*codexDown = true[\s\S]*codexCleanAt = null/);
193
304
  });
194
305
 
306
+ test('the CODEX phase skips a genuine codex_down gracefully, recording a note and advancing to mark-ready', () => {
307
+ const codexPhaseStart = convergeSource.indexOf("if (phase === 'CODEX') {");
308
+ const finalizePhaseStart = convergeSource.indexOf("if (phase === 'FINALIZE') {", codexPhaseStart);
309
+ const codexPhase = convergeSource.slice(codexPhaseStart, finalizePhaseStart);
310
+ const downBranchStart = codexPhase.indexOf("codexOutcome.kind === 'down'");
311
+ const downBranchEnd = codexPhase.indexOf("codexOutcome.kind === 'fix'", downBranchStart);
312
+ assert.notEqual(downBranchStart, -1, 'expected a codex_down branch in the CODEX phase');
313
+ assert.notEqual(downBranchEnd, -1, 'expected a fix branch after the down branch');
314
+ const downBranch = codexPhase.slice(downBranchStart, downBranchEnd);
315
+ assert.match(downBranch, /codexNote =/, 'a genuine codex_down records a transparent note');
316
+ assert.match(downBranch, /codexDown = true/, 'the down branch sets the bypass flag so mark-ready skips the required Codex gate');
317
+ assert.match(downBranch, /codexCleanAt = null/, 'the down branch clears any codex-clean stamp');
318
+ assert.match(
319
+ downBranch,
320
+ /phase = 'FINALIZE'/,
321
+ 'a genuine codex_down advances to mark-ready with the gate bypassed rather than blocking the run',
322
+ );
323
+ assert.doesNotMatch(downBranch, /blocker =/, 'a genuine codex_down must not record a blocker');
324
+ assert.doesNotMatch(downBranch, /\bbreak\b/, 'the down branch continues rather than breaking the loop');
325
+ });
326
+
327
+ test('the workflow return object includes codexNote so the final report surfaces the Codex skip', () => {
328
+ const assembleStart = convergeSource.indexOf('const assembleResult =');
329
+ assert.notEqual(assembleStart, -1, 'expected assembleResult to exist');
330
+ const assembleEnd = convergeSource.indexOf('})', assembleStart);
331
+ assert.notEqual(assembleEnd, -1, 'expected the assembleResult object to close');
332
+ const assembleSource = convergeSource.slice(assembleStart, assembleEnd);
333
+ assert.match(assembleSource, /codexNote,/, 'expected codexNote among the returned notes');
334
+ });
335
+
195
336
  test('the CODEX phase routes findings through applyFixes and re-enters CONVERGE', () => {
196
337
  const codexPhaseStart = convergeSource.indexOf("if (phase === 'CODEX') {");
197
338
  const finalizePhaseStart = convergeSource.indexOf("if (phase === 'FINALIZE') {", codexPhaseStart);
@@ -267,8 +408,8 @@ test('runConvergenceCheck tells the finalize agent to persist codex_clean_at for
267
408
  test('CONFIG expands home for the codex-review scripts directory', () => {
268
409
  assert.match(
269
410
  convergeSource,
270
- /const homeDirectory = \(process\.env\.HOME \|\| process\.env\.USERPROFILE/,
271
- 'expected home to be resolved from process.env before CONFIG path use',
411
+ /const homeDirectory = \([\s\S]*?homeDirectoryPayload\.homeDirectory/,
412
+ 'expected home to be sourced from the workflow args payload (the sandbox exposes no process)',
272
413
  );
273
414
  assert.match(
274
415
  convergeSource,
@@ -282,6 +423,25 @@ test('CONFIG expands home for the codex-review scripts directory', () => {
282
423
  );
283
424
  });
284
425
 
426
+ test('no process global is read unguarded (the workflow sandbox exposes no process)', () => {
427
+ const unguardedProcessLines = convergeSource
428
+ .split('\n')
429
+ .filter((sourceLine) => sourceLine.includes('process.') && !sourceLine.includes('typeof process'));
430
+ assert.deepEqual(
431
+ unguardedProcessLines,
432
+ [],
433
+ 'every process.* read must sit on a line guarded by typeof process; the Bun workflow sandbox has no process global',
434
+ );
435
+ });
436
+
437
+ test('the home directory is sourced from the workflow args payload', () => {
438
+ assert.match(
439
+ convergeSource,
440
+ /const homeDirectoryPayload = normalizeRunInput\(args\)/,
441
+ 'expected homeDirectory to read args (the only channel into the process-less sandbox), parsed via normalizeRunInput',
442
+ );
443
+ });
444
+
285
445
  test('meta lists the Codex gate phase between Copilot and Finalize', () => {
286
446
  const copilotPhaseIndex = convergeSource.indexOf("title: 'Copilot gate'");
287
447
  const codexPhaseIndex = convergeSource.indexOf("title: 'Codex gate'");
@@ -294,6 +454,18 @@ test('SKILL.md names Codex among the terminal confirmation gates', () => {
294
454
  assert.match(skillSource, /Codex/i);
295
455
  });
296
456
 
457
+ test('SKILL.md documents codexNote in the workflow return shape', () => {
458
+ const returnShapeMatch = skillSource.match(/`\{ converged, rounds, finalSha, blocker,[^`]*\}`/);
459
+ assert.ok(returnShapeMatch, 'expected the documented return shape literal in SKILL.md');
460
+ assert.match(returnShapeMatch[0], /codexNote/);
461
+ });
462
+
463
+ test('SKILL.md final-report template surfaces the codexNote so a bypassed Codex gate stays visible', () => {
464
+ const reportBlockMatch = skillSource.match(/\/autoconverge exit:[\s\S]*?```/);
465
+ assert.ok(reportBlockMatch, 'expected the final-report template block in SKILL.md');
466
+ assert.match(reportBlockMatch[0], /Codex: <codexNote>/);
467
+ });
468
+
297
469
  test('stop-conditions.md documents the Codex gate skip paths as non-blockers', () => {
298
470
  assert.match(stopConditionsSource, /Codex/i);
299
471
  assert.match(stopConditionsSource, /codex/i);