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
@@ -18,17 +18,28 @@ export const meta = {
18
18
  { title: 'Converge', detail: 'A deterministic static sweep runs first each round; then code-review, bug-audit, and self-review run in parallel on the same HEAD; one clean-coder applies all fixes; the loop repeats until every lens is clean on a stable HEAD' },
19
19
  { title: 'Bugbot gate', detail: 'A terminal confirmation gate that runs once the internal lenses are clean; when Bugbot is disabled or unavailable it spawns no agent and passes, otherwise it fetches Bugbot\'s verdict and routes any finding back into Converge' },
20
20
  { title: 'Copilot gate', detail: 'When the quota pre-check reports Copilot out of premium-request quota or unavailable, skip the gate with no agent spawned; otherwise request a Copilot review and poll up to the configured cap, then route findings by tier — self-healing findings flow back into Converge, and each code-concern finding goes to its own verifier agent that must execute a check against HEAD: a confirmed finding (defect reproduced by a run command) joins the fix round carrying its repro, a refuted finding (correct behavior demonstrated by a run command) is replied-to and resolved with the check evidence, and only inconclusive findings return blocker user-review for the orchestrator to gate on rather than auto-fixing or marking the PR ready; when Copilot is down or out of quota log a notice and proceed with the gate bypassed' },
21
- { title: 'Codex gate', detail: 'A conditional-required terminal gate that runs after Bugbot and Copilot: when CLAUDE_REVIEWS_DISABLED lists codex, when weekly usage is at or below the shared probe threshold (or null), or when the wrapper classifies codex_down, the gate skips without blocking; when usage is above the threshold it runs the codex-review wrapper against the PR base branch, routes findings into the fix path, and on a clean pass records the codex-clean HEAD for the convergence check' },
21
+ { title: 'Codex gate', detail: 'A conditional-required terminal gate that runs after Bugbot and Copilot: when CLAUDE_REVIEWS_DISABLED lists codex, when weekly usage is at or below the shared probe threshold (or null), or when the wrapper classifies codex_down, the gate skips without blocking (a codex_down skip is logged as a note so the bypass stays visible); when usage is above the threshold it runs the codex-review wrapper against the PR base branch, routes findings into the fix path, and on a clean pass records the codex-clean HEAD for the convergence check' },
22
22
  { title: 'Finalize', detail: 'Run check_convergence.py; mark draft=false on a full pass' },
23
23
  ],
24
24
  }
25
25
 
26
- const homeDirectory = (process.env.HOME || process.env.USERPROFILE || '').replace(/\\/g, '/')
26
+ const homeDirectoryPayload = normalizeRunInput(args)
27
+ const homeDirectoryFromEnv = typeof process !== 'undefined' && process.env ? process.env.HOME || process.env.USERPROFILE || '' : ''
28
+ const homeDirectory = (
29
+ (homeDirectoryPayload && typeof homeDirectoryPayload.homeDirectory === 'string'
30
+ ? homeDirectoryPayload.homeDirectory
31
+ : '') ||
32
+ homeDirectoryFromEnv ||
33
+ ''
34
+ ).replace(/\\/g, '/')
27
35
 
28
36
  const CONFIG = {
29
37
  maxIterations: 20,
30
38
  maxConsecutiveNoLensRounds: 3,
31
39
  copilotMaxPolls: 8,
40
+ codexReviewTimeoutSeconds: 2400,
41
+ codexReviewPollIntervalSeconds: 120,
42
+ codexReviewMaxPolls: 21,
32
43
  sharedScripts: '$HOME/.claude/skills/pr-converge/scripts',
33
44
  prLoopScripts: '$HOME/.claude/_shared/pr-loop/scripts',
34
45
  codexScripts: `${homeDirectory}/.claude/skills/codex-review/scripts`,
@@ -49,7 +60,7 @@ const HEADLESS_EDIT_PREAMBLE =
49
60
  '- Never place a destructive-command literal inside a Bash command — not in echo, not in a heredoc, and not as an argument to python -c, node -e, or awk. To exercise or verify destructive_command_blocker (or any hook) behavior, run the committed test suite, e.g. python -m pytest <test_file>, which passes the command strings as in-language data rather than as a shell command.\n' +
50
61
  '- When a commit message, or a PR / issue / review-comment body, must describe destructive-command behavior, write that text to a file and pass it by path (git commit -F <file>, gh ... --body-file <file>); never inline it with git commit -m or gh ... -b, where the literal lands in the Bash command and stalls you.\n' +
51
62
  '- Keep scratch files and cleanup inside the OS temp dir; never target a repository or worktree path.\n' +
52
- '- rm shape rules — the hook grants several rm auto-allow paths. The simplest one accepts a standalone Bash call whose target resolves inside the ephemeral namespace (/tmp, /temp, the OS temp root, or the run worktree); a compound path accepts an rm joined with benign reporting segments when every rm target is an absolute ephemeral path. Both of those paths fail closed on $(...) command substitution and on backtick subshells. The compound path additionally fails closed on any $ in the target — including $CLAUDE_JOB_DIR. The standalone path declines a $-bearing target only when the literal path is not already under an ephemeral root, so it does not by itself stop a $VAR that expands inside an ephemeral root. A third, broad path matches only when the command itself declares an ephemeral working directory (it cds into one, or runs under one): that cwd-scoped path resolves the target against the declared cwd, fails closed on $(...) , backticks, and unknown variables, and resolves the known temporary variables TEMP, TMP, TMPDIR, and CLAUDE_JOB_DIR to the OS temp root, so under that declared ephemeral cwd a bare $CLAUDE_JOB_DIR/tmp/<name> target and a relative target after a cd are auto-allowed. Even so, prefer a Python helper for any cleanup whose path is variable-built or whose setup/teardown spans multiple steps: author the helper file and run it as python <file>.py, which keeps every destructive literal out of a Bash command string entirely and never depends on which auto-allow path matches.\n' +
63
+ '- rm shape rules — the hook grants several rm auto-allow paths. The simplest one accepts a standalone Bash call whose target resolves inside the ephemeral namespace (/tmp, /temp, the OS temp root, or the run worktree); a compound path accepts an rm joined with benign reporting segments when every rm target is an absolute ephemeral path. Both of those paths fail closed on $(...) command substitution and on backtick subshells. The compound path additionally fails closed on any $ in the target — including $CLAUDE_JOB_DIR. The standalone path declines a $-bearing target only when the literal path is not already under an ephemeral root, so it does not by itself stop a $VAR that expands inside an ephemeral root. A third, broad path matches only when the command itself declares an ephemeral working directory (it cds into one, or runs under one): that cwd-scoped path resolves the target against the declared cwd, fails closed on $(...) , backticks, and unknown variables, and resolves the known temporary variables TEMP, TMP, TMPDIR, and CLAUDE_JOB_DIR to the OS temp root, so under that declared ephemeral cwd a bare $CLAUDE_JOB_DIR/tmp/<name> target and a relative target after a cd are auto-allowed. Outside those ephemeral paths, run no bash rm at all: an rm on a repo or worktree path — most often a build cache like .mypy_cache, .pytest_cache, or __pycache__ left by ruff, mypy, or pytest — is not auto-allowed and stalls you on a prompt no human can answer. A gitignored build cache is harmless, so never delete it — leave it in place. For any genuine delete outside the ephemeral namespace, use the PowerShell tool (Remove-Item -Recurse -Force -Confirm:$false <absolute path>), which the blocker never sees because it watches only the Bash tool. For any cleanup whose path is variable-built or whose setup/teardown spans multiple steps, author a Python helper and run it as python <file>.py, which keeps every destructive literal out of a Bash command string entirely.\n' +
53
64
  '- If a step appears to require a real destructive command, use a non-destructive equivalent or report it as a blocker instead of running it.\n\n' +
54
65
  'WAITS AND POLLS — foreground sleep is blocked in this headless harness: a bare Bash "sleep N" or a PowerShell "Start-Sleep" is denied, and a wait you move to a background process — then end your turn to await it — never resumes, because a schema-bearing agent runs for a single turn. Therefore:\n' +
55
66
  '- Perform every required delay or poll-interval wait inside this same turn by pairing the Monitor tool with a bounded until-loop: the Monitor tool streams its events to you while you keep working, and the until-loop re-runs the step condition on the interval the step names, up to the attempt budget the step names, exiting the moment the condition holds or the budget is spent — consume the Monitor notifications as they arrive rather than ending your turn to await them. Never end your turn to wait for something to finish.\n' +
@@ -561,7 +572,7 @@ function runConvergenceCheck(context) {
561
572
  if (context.copilotDown) reviewerOptOutTokens.push('copilot')
562
573
  if (context.codexDown) reviewerOptOutTokens.push('codex')
563
574
  if (context.bugteamPostBlocked) reviewerOptOutTokens.push('bugteam')
564
- const jobDirName = process.env.CLAUDE_JOB_DIR
575
+ const jobDirName = typeof process !== 'undefined' && process.env ? process.env.CLAUDE_JOB_DIR : undefined
565
576
  const needsCodexCleanExportFallback = Boolean(context.codexCleanAt) && !jobDirName
566
577
  if (needsCodexCleanExportFallback && !reviewerOptOutTokens.includes('codex')) {
567
578
  reviewerOptOutTokens.push('codex')
@@ -692,7 +703,7 @@ const CODEX_SCHEMA = {
692
703
  properties: {
693
704
  sha: { type: 'string', description: 'PR HEAD SHA this gate evaluated' },
694
705
  clean: { type: 'boolean', description: 'true when Codex reported no findings on sha, or when the gate skipped without requiring a review' },
695
- down: { type: 'boolean', description: 'true when Codex is down (wrapper classifies codex_down) or opted out via the codex token — the gate is bypassed' },
706
+ down: { type: 'boolean', description: 'true when the wrapper classifies codex_down (the gate is skipped and bypassed, recorded as a note) or when Codex is opted out via the codex token — the gate is bypassed' },
696
707
  skipped: { type: 'boolean', description: 'true when the gate did not run the review wrapper (opt-out token or usage at/below the shared threshold)' },
697
708
  skipReason: {
698
709
  type: 'string',
@@ -2072,9 +2083,11 @@ function runCodexGate(head) {
2072
2083
  `3. Resolve the PR base branch (the review target is HEAD vs base, never an invented commit range). Run exactly:\n` +
2073
2084
  ` gh api repos/${input.owner}/${input.repo}/pulls/${input.prNumber} --jq .base.ref\n` +
2074
2085
  ` Capture the printed base branch name.\n\n` +
2075
- `4. Run the codex-review wrapper against that base. From the PR worktree root, run a short Python driver that imports the skill scripts (add "${CONFIG.codexScripts}" to sys.path) and:\n` +
2086
+ `4. Run the codex-review wrapper against that base. A real Codex review routinely runs about 11-12 minutes — longer than the default 2-minute tool timeout and longer than the 10-minute foreground tool ceiling — so it MUST run in the background, or the tool timeout kills it mid-review and it is misreported as codex_down. From the PR worktree root, write a short Python driver to a temp file that imports the skill scripts (add "${CONFIG.codexScripts}" to sys.path) and:\n` +
2076
2087
  ` - creates a temp run-state directory under the OS temp dir\n` +
2077
- ` - calls run_codex_review.run_codex_review(repository_directory=<repo root Path>, run_state_directory=<temp Path>, base_branch=<base ref from step 3>)\n` +
2088
+ ` - calls run_codex_review.run_codex_review(repository_directory=<repo root Path>, run_state_directory=<temp Path>, base_branch=<base ref from step 3>, timeout_seconds=${CONFIG.codexReviewTimeoutSeconds})\n` +
2089
+ ` - writes the outcome (outcome_class, exit_code, and the agent_message when completed) as JSON to a result file under the OS temp dir, then exits\n` +
2090
+ ` Run that driver with the Bash tool in the background (run_in_background: true), never in the foreground — the foreground tool timeout caps below the review runtime and would kill a healthy review. Do NOT end this turn to await the background run — a schema-bearing turn that ends never resumes, so the result would never be collected. Wait for the background run to finish inside this same turn per the WAITS AND POLLS rule: pair the Monitor tool with a bounded until-loop that re-checks whether the result file has been written, on a ${CONFIG.codexReviewPollIntervalSeconds}-second interval up to ${CONFIG.codexReviewMaxPolls} attempts (interval x attempts covers the ${CONFIG.codexReviewTimeoutSeconds}-second review budget), consuming the Monitor notifications as they arrive; arm the Monitor with persistent:true (or a timeout_ms of at least interval x attempts) so it is not timed out before the review lands. If the full poll budget is spent with no result file written, return {sha:${'`'}${head}${'`'}, clean:false, down:true, skipped:false, skipReason:'', findings:[]} and stop. When the background run finishes, read the result file and map its outcome:\n` +
2078
2091
  ` - when outcome_class is codex_down (or classify_codex_run on a non-zero exit reports codex_down) -> return {sha:${'`'}${head}${'`'}, clean:false, down:true, skipped:false, skipReason:'', findings:[]} and stop\n` +
2079
2092
  ` - when outcome_class is completed: parse findings with parse_codex_findings.parse_codex_findings(agent_message)\n` +
2080
2093
  ` - empty findings -> return {sha:${'`'}${head}${'`'}, clean:true, down:false, skipped:false, skipReason:'', findings:[]}\n` +
@@ -2263,6 +2276,101 @@ function isStandardsOnlyRound(findings) {
2263
2276
  return findings.length > 0 && findings.every((each) => each.category === 'code-standard')
2264
2277
  }
2265
2278
 
2279
+ /**
2280
+ * Decide whether a review phase surfaced ONLY P2 findings — pure low-severity
2281
+ * items with no P0/P1 mixed in. Such a phase still applies the fixes once, then
2282
+ * treats the fixed HEAD as clean for that phase and advances forward rather than
2283
+ * re-converging the same phase.
2284
+ * @param {Array<object>} findings already-deduped findings for the phase
2285
+ * @returns {boolean} true when every finding is severity P2
2286
+ */
2287
+ function isP2OnlyFindings(findings) {
2288
+ return findings.length > 0 && findings.every((each) => each.severity === 'P2')
2289
+ }
2290
+
2291
+ /**
2292
+ * Map the phase that just finished a P2-only fix onto the next phase to enter
2293
+ * on the fixed HEAD. Pure P2 never returns to CONVERGE; each gate advances
2294
+ * forward only.
2295
+ *
2296
+ * ::
2297
+ *
2298
+ * nextPhaseAfterP2OnlyFix('CONVERGE') -> 'BUGBOT'
2299
+ * nextPhaseAfterP2OnlyFix('BUGBOT') -> 'COPILOT'
2300
+ * nextPhaseAfterP2OnlyFix('COPILOT') -> 'CODEX'
2301
+ * nextPhaseAfterP2OnlyFix('CODEX') -> 'FINALIZE'
2302
+ *
2303
+ * @param {string} currentPhase the phase that just applied the P2-only fixes
2304
+ * @returns {string} the next phase name
2305
+ */
2306
+ function nextPhaseAfterP2OnlyFix(currentPhase) {
2307
+ const nextPhaseByCurrent = {
2308
+ CONVERGE: 'BUGBOT',
2309
+ BUGBOT: 'COPILOT',
2310
+ COPILOT: 'CODEX',
2311
+ }
2312
+ return nextPhaseByCurrent[currentPhase] || 'FINALIZE'
2313
+ }
2314
+
2315
+ /**
2316
+ * Apply P2-only findings once and report whether the fixed HEAD advanced.
2317
+ * Shared by the four phase branches that fix pure-P2 findings then advance
2318
+ * forward without re-converging.
2319
+ *
2320
+ * ::
2321
+ *
2322
+ * await applyP2OnlyFix(head, findings, 'bugbot', 'bugbot gate', 'bugbot fix lens')
2323
+ * -> { progressed: true, newSha: '<fixed>', blocker: null }
2324
+ *
2325
+ * @param {string} head PR HEAD SHA the findings were raised against
2326
+ * @param {Array<object>} findings P2-only findings for this phase
2327
+ * @param {string} sourceLabel short label passed to applyFixes
2328
+ * @param {string} stallSubject noun phrase for the resolvedWithoutCommit stall message
2329
+ * @param {string} noPushSubject subject for the no-push stall message
2330
+ * @returns {Promise<{progressed: boolean, newSha: string, blocker: string|null}>} fix outcome
2331
+ */
2332
+ async function applyP2OnlyFix(head, findings, sourceLabel, stallSubject, noPushSubject) {
2333
+ const fixResult = await applyFixes(head, findings, sourceLabel)
2334
+ const hadThreadBearingFinding = findings.some((each) => collectFindingThreadIds(each).length > 0)
2335
+ const fixProgress = detectFixProgress(fixResult, head, hadThreadBearingFinding)
2336
+ if (fixProgress.progressed) {
2337
+ return { progressed: true, newSha: fixProgress.newSha, blocker: null }
2338
+ }
2339
+ const blocker =
2340
+ fixResult?.resolvedWithoutCommit === true && !hadThreadBearingFinding
2341
+ ? `fix stalled: ${stallSubject} raised ${findings.length} in-memory finding(s) with no GitHub thread, the fix judged them all stale (resolvedWithoutCommit) and moved no code on HEAD ${head} — re-raising would loop to the iteration cap`
2342
+ : `${noPushSubject} landed no push for ${findings.length} finding(s) on HEAD ${head}`
2343
+ return { progressed: false, newSha: head, blocker }
2344
+ }
2345
+
2346
+ /**
2347
+ * Route after a successful P2-only fix. HEAD-bound CLEAN/review stamps are
2348
+ * SHA-specific: a push that moves HEAD must re-enter CONVERGE so those stamps
2349
+ * rebuild. A no-push progress (resolvedWithoutCommit) advances to the next
2350
+ * phase on the same HEAD.
2351
+ *
2352
+ * ::
2353
+ *
2354
+ * routeAfterP2OnlyFix('aaa', {newSha: 'bbb'}, 'COPILOT')
2355
+ * -> {fixedHead: 'bbb', nextPhase: 'CONVERGE', didMoveHead: true}
2356
+ * routeAfterP2OnlyFix('aaa', {newSha: 'aaa'}, 'COPILOT')
2357
+ * -> {fixedHead: 'aaa', nextPhase: 'COPILOT', didMoveHead: false}
2358
+ *
2359
+ * @param {string} priorHead HEAD SHA before the P2-only fix
2360
+ * @param {{newSha: string}} p2Fix successful applyP2OnlyFix result
2361
+ * @param {string} advancePhase phase to enter when HEAD did not move
2362
+ * @returns {{fixedHead: string, nextPhase: string, didMoveHead: boolean}} routing
2363
+ */
2364
+ function routeAfterP2OnlyFix(priorHead, p2Fix, advancePhase) {
2365
+ const fixedHead = p2Fix.newSha
2366
+ const didMoveHead =
2367
+ normalizeShaForComparison(fixedHead) !== normalizeShaForComparison(priorHead)
2368
+ if (didMoveHead) {
2369
+ return { fixedHead, nextPhase: 'CONVERGE', didMoveHead: true }
2370
+ }
2371
+ return { fixedHead, nextPhase: advancePhase, didMoveHead: false }
2372
+ }
2373
+
2266
2374
  /**
2267
2375
  * Decide whether a standards-only round should file the follow-up fix issue and
2268
2376
  * open the environment-hardening PR. Standards findings are deferred rather than
@@ -2536,6 +2644,7 @@ let copilotDown = input.copilotDisabled || false
2536
2644
  let codexDown = false
2537
2645
  let codexCleanAt = null
2538
2646
  let copilotNote = null
2647
+ let codexNote = null
2539
2648
  let standardsNote = null
2540
2649
  let cleanAuditNote = null
2541
2650
  let hasStandardsFollowUpFiled = false
@@ -2549,6 +2658,7 @@ const assembleResult = (outcomeFields) => ({
2549
2658
  ...outcomeFields,
2550
2659
  standardsNote,
2551
2660
  copilotNote,
2661
+ codexNote,
2552
2662
  cleanAuditNote,
2553
2663
  reuseNote,
2554
2664
  deferredPrs,
@@ -2645,6 +2755,23 @@ while (iterations < CONFIG.maxIterations) {
2645
2755
  phase = 'BUGBOT'
2646
2756
  continue
2647
2757
  }
2758
+ if (isP2OnlyFindings(findings)) {
2759
+ resetNoLensRounds()
2760
+ const advancePhase = nextPhaseAfterP2OnlyFix('CONVERGE')
2761
+ log(`Round ${rounds}: ${findings.length} P2-only finding(s) — fixing once then routing from CONVERGE`)
2762
+ const p2Fix = await applyP2OnlyFix(head, findings, 'converge-round', 'converge round', 'fix lens')
2763
+ if (!p2Fix.progressed) {
2764
+ blocker = p2Fix.blocker
2765
+ break
2766
+ }
2767
+ const p2Route = routeAfterP2OnlyFix(head, p2Fix, advancePhase)
2768
+ head = p2Route.fixedHead
2769
+ phase = p2Route.nextPhase
2770
+ if (p2Route.didMoveHead) {
2771
+ log(`Round ${rounds}: P2-only fix moved HEAD — re-converging so HEAD-bound stamps rebuild`)
2772
+ }
2773
+ continue
2774
+ }
2648
2775
  if (findings.length > 0) {
2649
2776
  resetNoLensRounds()
2650
2777
  log(`Round ${rounds}: ${findings.length} finding(s) — applying fixes`)
@@ -2717,6 +2844,23 @@ while (iterations < CONFIG.maxIterations) {
2717
2844
  phase = 'COPILOT'
2718
2845
  continue
2719
2846
  }
2847
+ if (isP2OnlyFindings(bugbotOutcome.findings)) {
2848
+ const advancePhase = nextPhaseAfterP2OnlyFix('BUGBOT')
2849
+ log(`Bugbot raised ${bugbotOutcome.findings.length} P2-only finding(s) — fixing once then routing from BUGBOT`)
2850
+ const p2Fix = await applyP2OnlyFix(head, bugbotOutcome.findings, 'bugbot', 'bugbot gate', 'bugbot fix lens')
2851
+ if (!p2Fix.progressed) {
2852
+ blocker = p2Fix.blocker
2853
+ break
2854
+ }
2855
+ const p2Route = routeAfterP2OnlyFix(head, p2Fix, advancePhase)
2856
+ head = p2Route.fixedHead
2857
+ bugbotDown = false
2858
+ phase = p2Route.nextPhase
2859
+ if (p2Route.didMoveHead) {
2860
+ log('Bugbot P2-only fix moved HEAD — re-converging so HEAD-bound stamps rebuild')
2861
+ }
2862
+ continue
2863
+ }
2720
2864
  log(`Bugbot raised ${bugbotOutcome.findings.length} finding(s) — fixing and re-converging`)
2721
2865
  const fixResult = await applyFixes(head, bugbotOutcome.findings, 'bugbot')
2722
2866
  const hadThreadBearingFinding = bugbotOutcome.findings.some((each) => collectFindingThreadIds(each).length > 0)
@@ -2798,6 +2942,22 @@ while (iterations < CONFIG.maxIterations) {
2798
2942
  phase = 'CODEX'
2799
2943
  continue
2800
2944
  }
2945
+ if (isP2OnlyFindings(roundFindings)) {
2946
+ const advancePhase = nextPhaseAfterP2OnlyFix('COPILOT')
2947
+ log(`Copilot raised ${roundFindings.length} P2-only finding(s) — fixing once then routing from COPILOT`)
2948
+ const p2Fix = await applyP2OnlyFix(head, roundFindings, 'copilot', 'copilot round', 'copilot fix lens')
2949
+ if (!p2Fix.progressed) {
2950
+ blocker = p2Fix.blocker
2951
+ break
2952
+ }
2953
+ const p2Route = routeAfterP2OnlyFix(head, p2Fix, advancePhase)
2954
+ head = p2Route.fixedHead
2955
+ phase = p2Route.nextPhase
2956
+ if (p2Route.didMoveHead) {
2957
+ log('Copilot P2-only fix moved HEAD — re-converging so HEAD-bound stamps rebuild')
2958
+ }
2959
+ continue
2960
+ }
2801
2961
  log(`Copilot raised ${roundFindings.length} finding(s) — fixing and re-converging`)
2802
2962
  const fixResult = await applyFixes(head, roundFindings, 'copilot')
2803
2963
  const hadThreadBearingFinding = roundFindings.some((each) => collectFindingThreadIds(each).length > 0)
@@ -2840,7 +3000,8 @@ while (iterations < CONFIG.maxIterations) {
2840
3000
  continue
2841
3001
  }
2842
3002
  if (codexOutcome.kind === 'down') {
2843
- log('Codex gate: Codex classified codex_down — bypassing the terminal Codex gate and proceeding to mark-ready with the gate bypassed.')
3003
+ codexNote = `Codex was unreachable this run (the codex-review wrapper classified codex_down — a Codex CLI error, or a review that ran past its timeout budget). The terminal Codex gate was skipped with the gate bypassed so a Codex outage does not hinder convergence; Codex runs again on the next convergence.`
3004
+ log(`Codex gate: ${codexNote}`)
2844
3005
  codexDown = true
2845
3006
  codexCleanAt = null
2846
3007
  phase = 'FINALIZE'
@@ -2857,6 +3018,28 @@ while (iterations < CONFIG.maxIterations) {
2857
3018
  phase = 'FINALIZE'
2858
3019
  continue
2859
3020
  }
3021
+ if (isP2OnlyFindings(codexOutcome.findings)) {
3022
+ const advancePhase = nextPhaseAfterP2OnlyFix('CODEX')
3023
+ log(`Codex raised ${codexOutcome.findings.length} P2-only finding(s) — fixing once then routing from CODEX`)
3024
+ const p2Fix = await applyP2OnlyFix(head, codexOutcome.findings, 'codex', 'codex gate', 'codex fix lens')
3025
+ if (!p2Fix.progressed) {
3026
+ blocker = p2Fix.blocker
3027
+ break
3028
+ }
3029
+ const p2Route = routeAfterP2OnlyFix(head, p2Fix, advancePhase)
3030
+ head = p2Route.fixedHead
3031
+ if (p2Route.didMoveHead) {
3032
+ log('Codex P2-only fix moved HEAD — re-converging so HEAD-bound stamps rebuild')
3033
+ codexDown = false
3034
+ codexCleanAt = null
3035
+ phase = p2Route.nextPhase
3036
+ continue
3037
+ }
3038
+ codexDown = false
3039
+ codexCleanAt = head
3040
+ phase = p2Route.nextPhase
3041
+ continue
3042
+ }
2860
3043
  log(`Codex raised ${codexOutcome.findings.length} finding(s) — fixing and re-converging`)
2861
3044
  const fixResult = await applyFixes(head, codexOutcome.findings, 'codex')
2862
3045
  const hadThreadBearingFinding = codexOutcome.findings.some((each) => collectFindingThreadIds(each).length > 0)
@@ -0,0 +1,202 @@
1
+ import { test } from 'node:test';
2
+ import { strict as assert } from 'node:assert';
3
+ import { readFileSync } from 'node:fs';
4
+ import { fileURLToPath } from 'node:url';
5
+ import { dirname, join } from 'node:path';
6
+
7
+ const workflowDirectory = dirname(fileURLToPath(import.meta.url));
8
+ const convergeSource = readFileSync(join(workflowDirectory, 'converge.mjs'), 'utf8');
9
+
10
+ function functionBody(functionName) {
11
+ const functionStart = convergeSource.indexOf(`function ${functionName}(`);
12
+ assert.notEqual(functionStart, -1, `expected ${functionName} to exist`);
13
+ const nextFunctionStart = convergeSource.indexOf('\nfunction ', functionStart + 1);
14
+ const functionEnd = nextFunctionStart === -1 ? convergeSource.length : nextFunctionStart;
15
+ return convergeSource.slice(functionStart, functionEnd);
16
+ }
17
+
18
+ const productionModule = new Function(
19
+ `const SHA_COMPARISON_PREFIX_LENGTH = 7;\n` +
20
+ `${functionBody('normalizeShaForComparison')}\n` +
21
+ `${functionBody('isP2OnlyFindings')}\n` +
22
+ `${functionBody('nextPhaseAfterP2OnlyFix')}\n` +
23
+ `${functionBody('routeAfterP2OnlyFix')}\n` +
24
+ 'return { isP2OnlyFindings, nextPhaseAfterP2OnlyFix, routeAfterP2OnlyFix };',
25
+ )();
26
+
27
+ const { isP2OnlyFindings, nextPhaseAfterP2OnlyFix, routeAfterP2OnlyFix } = productionModule;
28
+
29
+ function finding(overrides) {
30
+ return {
31
+ file: 'a.py',
32
+ line: 1,
33
+ severity: 'P2',
34
+ category: 'bug',
35
+ title: 't',
36
+ detail: 'd',
37
+ replyToCommentId: null,
38
+ ...overrides,
39
+ };
40
+ }
41
+
42
+ function phaseBranch(phaseMarker) {
43
+ const phaseStart = convergeSource.indexOf(phaseMarker);
44
+ assert.notEqual(phaseStart, -1, `expected ${phaseMarker} to exist`);
45
+ const nextPhaseStart = convergeSource.indexOf('\n if (phase === ', phaseStart + 1);
46
+ const phaseEnd = nextPhaseStart === -1 ? convergeSource.length : nextPhaseStart;
47
+ return convergeSource.slice(phaseStart, phaseEnd);
48
+ }
49
+
50
+ test('isP2OnlyFindings is false for an empty findings list', () => {
51
+ assert.equal(isP2OnlyFindings([]), false);
52
+ });
53
+
54
+ test('isP2OnlyFindings is true when every finding is severity P2', () => {
55
+ assert.equal(isP2OnlyFindings([finding(), finding({ file: 'b.py', line: 2 })]), true);
56
+ });
57
+
58
+ test('isP2OnlyFindings is false when a P1 is mixed in with P2 findings', () => {
59
+ assert.equal(isP2OnlyFindings([finding(), finding({ severity: 'P1' })]), false);
60
+ });
61
+
62
+ test('isP2OnlyFindings is false when every finding is severity P1', () => {
63
+ assert.equal(isP2OnlyFindings([finding({ severity: 'P1' })]), false);
64
+ });
65
+
66
+ test('isP2OnlyFindings is false when a finding is missing severity', () => {
67
+ assert.equal(isP2OnlyFindings([{ file: 'a.py', line: 1, category: 'bug' }]), false);
68
+ });
69
+
70
+ test('nextPhaseAfterP2OnlyFix advances CONVERGE to BUGBOT', () => {
71
+ assert.equal(nextPhaseAfterP2OnlyFix('CONVERGE'), 'BUGBOT');
72
+ });
73
+
74
+ test('nextPhaseAfterP2OnlyFix advances BUGBOT to COPILOT', () => {
75
+ assert.equal(nextPhaseAfterP2OnlyFix('BUGBOT'), 'COPILOT');
76
+ });
77
+
78
+ test('nextPhaseAfterP2OnlyFix advances COPILOT to CODEX', () => {
79
+ assert.equal(nextPhaseAfterP2OnlyFix('COPILOT'), 'CODEX');
80
+ });
81
+
82
+ test('nextPhaseAfterP2OnlyFix advances CODEX to FINALIZE', () => {
83
+ assert.equal(nextPhaseAfterP2OnlyFix('CODEX'), 'FINALIZE');
84
+ });
85
+
86
+ test('routeAfterP2OnlyFix re-converges when the fix moved HEAD', () => {
87
+ const route = routeAfterP2OnlyFix('aaa111', { newSha: 'bbb222' }, 'COPILOT');
88
+ assert.equal(route.didMoveHead, true);
89
+ assert.equal(route.fixedHead, 'bbb222');
90
+ assert.equal(route.nextPhase, 'CONVERGE');
91
+ });
92
+
93
+ test('routeAfterP2OnlyFix advances when HEAD is unchanged', () => {
94
+ const route = routeAfterP2OnlyFix('aaa111', { newSha: 'aaa111' }, 'COPILOT');
95
+ assert.equal(route.didMoveHead, false);
96
+ assert.equal(route.fixedHead, 'aaa111');
97
+ assert.equal(route.nextPhase, 'COPILOT');
98
+ });
99
+
100
+ test('nextPhaseAfterP2OnlyFix uses an in-function lookup map', () => {
101
+ const body = functionBody('nextPhaseAfterP2OnlyFix');
102
+ assert.match(body, /nextPhaseByCurrent/);
103
+ assert.match(body, /CONVERGE:\s*'BUGBOT'/);
104
+ assert.match(body, /BUGBOT:\s*'COPILOT'/);
105
+ assert.match(body, /COPILOT:\s*'CODEX'/);
106
+ });
107
+
108
+ test('applyP2OnlyFix runs applyFixes then detectFixProgress in order', () => {
109
+ const body = functionBody('applyP2OnlyFix');
110
+ const applyIndex = body.indexOf('applyFixes(head, findings, sourceLabel)');
111
+ const detectIndex = body.indexOf('detectFixProgress(fixResult, head, hadThreadBearingFinding)');
112
+ assert.notEqual(applyIndex, -1, 'expected applyFixes in applyP2OnlyFix');
113
+ assert.notEqual(detectIndex, -1, 'expected detectFixProgress in applyP2OnlyFix');
114
+ assert.ok(applyIndex < detectIndex, 'applyFixes must precede detectFixProgress');
115
+ assert.match(body, /collectFindingThreadIds/);
116
+ });
117
+
118
+ test('CONVERGE places the P2-only branch between standards-only and generic fix', () => {
119
+ const convergeBranch = phaseBranch("if (phase === 'CONVERGE')");
120
+ const standardsIndex = convergeBranch.indexOf('isStandardsOnlyRound(findings)');
121
+ const p2OnlyIndex = convergeBranch.indexOf('isP2OnlyFindings(findings)');
122
+ const genericFixIndex = convergeBranch.indexOf("finding(s) — applying fixes");
123
+ assert.notEqual(standardsIndex, -1, 'expected the standards-only branch');
124
+ assert.notEqual(p2OnlyIndex, -1, 'expected the P2-only branch');
125
+ assert.notEqual(genericFixIndex, -1, 'expected the generic fix branch');
126
+ assert.ok(standardsIndex < p2OnlyIndex, 'standards-only must precede P2-only');
127
+ assert.ok(p2OnlyIndex < genericFixIndex, 'P2-only must precede generic fix');
128
+ });
129
+
130
+ test('CONVERGE P2-only branch fixes once and routes via routeAfterP2OnlyFix', () => {
131
+ const convergeBranch = phaseBranch("if (phase === 'CONVERGE')");
132
+ const p2Start = convergeBranch.indexOf('isP2OnlyFindings(findings)');
133
+ const genericFixStart = convergeBranch.indexOf("finding(s) — applying fixes");
134
+ const p2Branch = convergeBranch.slice(p2Start, genericFixStart);
135
+ assert.match(p2Branch, /P2-only/);
136
+ assert.match(p2Branch, /applyP2OnlyFix\(head, findings, 'converge-round'/);
137
+ assert.match(p2Branch, /routeAfterP2OnlyFix\(head, p2Fix, advancePhase\)/);
138
+ assert.match(p2Branch, /nextPhaseAfterP2OnlyFix\('CONVERGE'\)/);
139
+ assert.match(p2Branch, /head = p2Route\.fixedHead/);
140
+ assert.match(p2Branch, /phase = p2Route\.nextPhase/);
141
+ assert.doesNotMatch(p2Branch, /head = null/);
142
+ });
143
+
144
+ test('BUGBOT P2-only branch routes through routeAfterP2OnlyFix', () => {
145
+ const bugbotBranch = phaseBranch("if (phase === 'BUGBOT')");
146
+ const standardsIndex = bugbotBranch.indexOf('isStandardsOnlyRound(bugbotOutcome.findings)');
147
+ const p2OnlyIndex = bugbotBranch.indexOf('isP2OnlyFindings(bugbotOutcome.findings)');
148
+ const genericFixIndex = bugbotBranch.indexOf('fixing and re-converging');
149
+ assert.ok(standardsIndex < p2OnlyIndex, 'standards-only must precede P2-only on BUGBOT');
150
+ assert.ok(p2OnlyIndex < genericFixIndex, 'P2-only must precede generic re-converge on BUGBOT');
151
+ const p2Branch = bugbotBranch.slice(p2OnlyIndex, genericFixIndex);
152
+ assert.match(p2Branch, /P2-only/);
153
+ assert.match(p2Branch, /nextPhaseAfterP2OnlyFix\('BUGBOT'\)/);
154
+ assert.match(p2Branch, /applyP2OnlyFix\(head, bugbotOutcome\.findings, 'bugbot'/);
155
+ assert.match(p2Branch, /routeAfterP2OnlyFix\(head, p2Fix, advancePhase\)/);
156
+ assert.match(p2Branch, /phase = p2Route\.nextPhase/);
157
+ });
158
+
159
+ test('COPILOT P2-only branch routes through routeAfterP2OnlyFix', () => {
160
+ const copilotBranch = phaseBranch("if (phase === 'COPILOT')");
161
+ const standardsIndex = copilotBranch.indexOf('isStandardsOnlyRound(roundFindings)');
162
+ const p2OnlyIndex = copilotBranch.indexOf('isP2OnlyFindings(roundFindings)');
163
+ const genericFixIndex = copilotBranch.indexOf('fixing and re-converging');
164
+ assert.ok(standardsIndex < p2OnlyIndex, 'standards-only must precede P2-only on COPILOT');
165
+ assert.ok(p2OnlyIndex < genericFixIndex, 'P2-only must precede generic re-converge on COPILOT');
166
+ const p2Branch = copilotBranch.slice(p2OnlyIndex, genericFixIndex);
167
+ assert.match(p2Branch, /P2-only/);
168
+ assert.match(p2Branch, /nextPhaseAfterP2OnlyFix\('COPILOT'\)/);
169
+ assert.match(p2Branch, /applyP2OnlyFix\(head, roundFindings, 'copilot'/);
170
+ assert.match(p2Branch, /routeAfterP2OnlyFix\(head, p2Fix, advancePhase\)/);
171
+ assert.match(p2Branch, /phase = p2Route\.nextPhase/);
172
+ });
173
+
174
+ test('CODEX P2-only branch routes through routeAfterP2OnlyFix and stamps codexCleanAt only when HEAD is stable', () => {
175
+ const codexBranch = phaseBranch("if (phase === 'CODEX')");
176
+ const standardsIndex = codexBranch.indexOf('isStandardsOnlyRound(codexOutcome.findings)');
177
+ const p2OnlyIndex = codexBranch.indexOf('isP2OnlyFindings(codexOutcome.findings)');
178
+ const genericFixIndex = codexBranch.indexOf('fixing and re-converging');
179
+ assert.ok(standardsIndex < p2OnlyIndex, 'standards-only must precede P2-only on CODEX');
180
+ assert.ok(p2OnlyIndex < genericFixIndex, 'P2-only must precede generic re-converge on CODEX');
181
+ const p2Branch = codexBranch.slice(p2OnlyIndex, genericFixIndex);
182
+ assert.match(p2Branch, /P2-only/);
183
+ assert.match(p2Branch, /nextPhaseAfterP2OnlyFix\('CODEX'\)/);
184
+ assert.match(p2Branch, /applyP2OnlyFix\(head, codexOutcome\.findings, 'codex'/);
185
+ assert.match(p2Branch, /routeAfterP2OnlyFix\(head, p2Fix, advancePhase\)/);
186
+ assert.match(p2Branch, /codexCleanAt = head/);
187
+ assert.match(p2Branch, /didMoveHead/);
188
+ });
189
+
190
+ test('generic fix branches still re-converge for mixed or P0/P1 findings', () => {
191
+ const bugbotBranch = phaseBranch("if (phase === 'BUGBOT')");
192
+ const genericStart = bugbotBranch.indexOf('fixing and re-converging');
193
+ const genericBranch = bugbotBranch.slice(genericStart);
194
+ assert.match(genericBranch, /phase = 'CONVERGE'/);
195
+ assert.match(genericBranch, /head = null/);
196
+
197
+ const copilotBranch = phaseBranch("if (phase === 'COPILOT')");
198
+ const copilotGenericStart = copilotBranch.indexOf('fixing and re-converging');
199
+ const copilotGeneric = copilotBranch.slice(copilotGenericStart);
200
+ assert.match(copilotGeneric, /phase = 'CONVERGE'/);
201
+ assert.match(copilotGeneric, /head = null/);
202
+ });
@@ -109,11 +109,14 @@ function classifyMultiInput(rawArgs) {
109
109
  * fan-out honors it: bugbotDisabled skips the Bugbot phase, and copilotDisabled
110
110
  * short-circuits the Copilot quota gate when the account is out of premium
111
111
  * requests. An entry that omits an opt-out defaults it to false so the child
112
- * runs that phase.
112
+ * runs that phase. homeDirectory is a run-level value forwarded to every child
113
+ * so the child can build its codex-review scripts path: the workflow sandbox
114
+ * exposes no process global, so the child reads home from its args, not env.
113
115
  * @param {object} prEntry one validated element of the args.prs array
116
+ * @param {string} homeDirectory the run-level home directory for every child
114
117
  * @returns {object} the args object passed to the converge.mjs child run
115
118
  */
116
- function childRunInput(prEntry) {
119
+ function childRunInput(prEntry, homeDirectory) {
117
120
  return {
118
121
  owner: prEntry.owner,
119
122
  repo: prEntry.repo,
@@ -121,6 +124,7 @@ function childRunInput(prEntry) {
121
124
  repoPath: prEntry.repoPath,
122
125
  bugbotDisabled: Boolean(prEntry.bugbotDisabled),
123
126
  copilotDisabled: Boolean(prEntry.copilotDisabled),
127
+ homeDirectory,
124
128
  }
125
129
  }
126
130
 
@@ -137,7 +141,7 @@ const childResults = await parallel(
137
141
  input.prs.map((eachPr) => async () => {
138
142
  const childOutcome = await workflow(
139
143
  { scriptPath: input.convergeScriptPath },
140
- childRunInput(eachPr),
144
+ childRunInput(eachPr, input.homeDirectory),
141
145
  )
142
146
  return {
143
147
  owner: eachPr.owner,
@@ -138,6 +138,11 @@ test('childRunInput defaults bugbotDisabled to false when the entry omits it', (
138
138
  assert.equal(childRunInput(validEntry(101)).bugbotDisabled, false);
139
139
  });
140
140
 
141
+ test('childRunInput forwards the run-level home directory to the child run', () => {
142
+ const childArgs = childRunInput(validEntry(101), '/home/dev');
143
+ assert.equal(childArgs.homeDirectory, '/home/dev');
144
+ });
145
+
141
146
  test('the malformed-input blocker return carries an empty allDeferredPrs list', async () => {
142
147
  const blockerOutcome = await runMultiWorkflowBody(
143
148
  'not json at all',
@@ -1,2 +1,2 @@
1
- {"type": "user", "uuid": "a11d903476b803493-u", "message": {"role": "user", "content": "Return the structured result."}}
2
- {"type": "assistant", "uuid": "a11d903476b803493-a", "message": {"role": "assistant", "content": [{"type": "tool_use", "id": "toolu_a11d903476b803493", "name": "StructuredOutput", "input": {"sha": "7c2f420c4d5b7c83aa47f93d99a0f1420e3373c4", "clean": false, "findings": [{"file": "src/exports/core/export_runner.py", "line": 96, "severity": "P2", "title": "New inline log string literal flagged by Magic-values rule", "detail": "Copilot (CodingGuidelineID 1000000, \"Magic values & configuration\"): the new log message at this line introduces a new string literal in production code. Prefer reusing an existing configured UI message (e.g. web_editor_config.ui_messages.msg_detected_version) instead of adding a new inline literal.", "replyToCommentId": 3407912224}, {"file": "src/web/services/os_generation_ssot.py", "line": 265, "severity": "P2", "title": "New inline warning string literal flagged by Magic-values rule", "detail": "Copilot (CodingGuidelineID 1000000, \"Magic values & configuration\"): read_current_version_name_or_none() adds a new inline warning string in production code. Per repo convention, extract this message into a shared config constant (in src/shared/web_automation/config/) so future callers reuse it consistently.", "replyToCommentId": 3407912234}], "blocker": null}}]}}
1
+ {"type": "user", "uuid": "a11d903476b803493-u", "message": {"role": "user", "content": "Return the structured result."}}
2
+ {"type": "assistant", "uuid": "a11d903476b803493-a", "message": {"role": "assistant", "content": [{"type": "tool_use", "id": "toolu_a11d903476b803493", "name": "StructuredOutput", "input": {"sha": "7c2f420c4d5b7c83aa47f93d99a0f1420e3373c4", "clean": false, "findings": [{"file": "src/exports/core/export_runner.py", "line": 96, "severity": "P2", "title": "New inline log string literal flagged by Magic-values rule", "detail": "Copilot (CodingGuidelineID 1000000, \"Magic values & configuration\"): the new log message at this line introduces a new string literal in production code. Prefer reusing an existing configured UI message (e.g. web_editor_config.ui_messages.msg_detected_version) instead of adding a new inline literal.", "replyToCommentId": 3407912224}, {"file": "src/web/services/os_generation_ssot.py", "line": 265, "severity": "P2", "title": "New inline warning string literal flagged by Magic-values rule", "detail": "Copilot (CodingGuidelineID 1000000, \"Magic values & configuration\"): read_current_version_name_or_none() adds a new inline warning string in production code. Per repo convention, extract this message into a shared config constant (in src/shared/web_automation/config/) so future callers reuse it consistently.", "replyToCommentId": 3407912234}], "blocker": null}}]}}
@@ -1,2 +1,2 @@
1
- {"type": "user", "uuid": "a26213978adeef6fb-u", "message": {"role": "user", "content": "Return the structured result."}}
2
- {"type": "assistant", "uuid": "a26213978adeef6fb-a", "message": {"role": "assistant", "content": [{"type": "tool_use", "id": "toolu_a26213978adeef6fb", "name": "StructuredOutput", "input": {"newSha": "620382f766c4731ba92eefeb30bdbb9d0bcd4c90", "pushed": true, "resolvedWithoutCommit": false, "summary": "Fixed all 11 Copilot findings in one commit (620382f7): renamed banned `result` to `skip_path` in test_resume_skip_export.py and added `-> None` return annotations to the test functions across test_resume_skip_export.py, test_stp_path_selection.py, test_version_export_guards.py, and test_import_isolation.py; all 9 affected tests pass; pushed to fix/example-feature, posted 11 inline replies, and all 11 review threads are resolved."}}]}}
1
+ {"type": "user", "uuid": "a26213978adeef6fb-u", "message": {"role": "user", "content": "Return the structured result."}}
2
+ {"type": "assistant", "uuid": "a26213978adeef6fb-a", "message": {"role": "assistant", "content": [{"type": "tool_use", "id": "toolu_a26213978adeef6fb", "name": "StructuredOutput", "input": {"newSha": "620382f766c4731ba92eefeb30bdbb9d0bcd4c90", "pushed": true, "resolvedWithoutCommit": false, "summary": "Fixed all 11 Copilot findings in one commit (620382f7): renamed banned `result` to `skip_path` in test_resume_skip_export.py and added `-> None` return annotations to the test functions across test_resume_skip_export.py, test_stp_path_selection.py, test_version_export_guards.py, and test_import_isolation.py; all 9 affected tests pass; pushed to fix/example-feature, posted 11 inline replies, and all 11 review threads are resolved."}}]}}
@@ -1,2 +1,2 @@
1
- {"type": "user", "uuid": "a3def0d15ed9d9110-u", "message": {"role": "user", "content": "Return the structured result."}}
2
- {"type": "assistant", "uuid": "a3def0d15ed9d9110-a", "message": {"role": "assistant", "content": [{"type": "tool_use", "id": "toolu_a3def0d15ed9d9110", "name": "StructuredOutput", "input": {"sha": "6a5169ef635ddb841e6f0465a309691ff77f033e", "clean": false, "blocker": null, "findings": [{"file": "src/exports/tests/test_resume_skip_export.py", "line": 35, "severity": "P2", "title": "Banned identifier `result` in test", "detail": "Naming guideline (CodingGuidelineID 1000000): newly introduced identifier `result` is banned; rename to a descriptive name such as `existing_apk_path` or `skip_path`.", "replyToCommentId": 3407883689}, {"file": "src/exports/tests/test_resume_skip_export.py", "line": 47, "severity": "P2", "title": "Banned identifier `result` in test", "detail": "Naming guideline (CodingGuidelineID 1000000): newly introduced identifier `result` is banned; rename to a descriptive name such as `existing_apk_path` or `skip_path`.", "replyToCommentId": 3407883707}, {"file": "src/logging/tests/test_import_isolation.py", "line": 33, "severity": "P2", "title": "Missing return type annotation on test function", "detail": "Repository guideline (CodingGuidelineID 1000000 \u2014 Types): newly added test function is missing an explicit return type annotation. Add `-> None`.", "replyToCommentId": 3407883713}, {"file": "src/logging/tests/test_import_isolation.py", "line": 43, "severity": "P2", "title": "Missing return type annotation on test function", "detail": "Repository guideline (CodingGuidelineID 1000000 \u2014 Types): newly added test function is missing an explicit return type annotation. Add `-> None`.", "replyToCommentId": 3407883717}, {"file": "src/exports/tests/test_version_export_guards.py", "line": 37, "severity": "P2", "title": "Missing return type annotation on async test", "detail": "Repository guideline (CodingGuidelineID 1000000 \u2014 Types): newly added async test is missing an explicit return type annotation. Add `-> None`.", "replyToCommentId": 3407883723}, {"file": "src/exports/tests/test_version_export_guards.py", "line": 74, "severity": "P2", "title": "Missing return type annotation on async test", "detail": "Repository guideline (CodingGuidelineID 1000000 \u2014 Types): newly added async test is missing an explicit return type annotation. Add `-> None`.", "replyToCommentId": 3407883727}, {"file": "src/exports/tests/test_stp_path_selection.py", "line": 19, "severity": "P2", "title": "Missing return type annotation on test function", "detail": "Repository guideline (CodingGuidelineID 1000000 \u2014 Types): newly added test function is missing an explicit return type annotation. Add `-> None`.", "replyToCommentId": 3407883731}, {"file": "src/exports/tests/test_stp_path_selection.py", "line": 31, "severity": "P2", "title": "Missing return type annotation on test function", "detail": "Repository guideline (CodingGuidelineID 1000000 \u2014 Types): newly added test function is missing an explicit return type annotation. Add `-> None`.", "replyToCommentId": 3407883736}, {"file": "src/exports/tests/test_stp_path_selection.py", "line": 43, "severity": "P2", "title": "Missing return type annotation on test function", "detail": "Repository guideline (CodingGuidelineID 1000000 \u2014 Types): newly added test function is missing an explicit return type annotation. Add `-> None`.", "replyToCommentId": 3407883740}, {"file": "src/exports/tests/test_resume_skip_export.py", "line": 21, "severity": "P2", "title": "Missing return type annotation on test function", "detail": "Repository guideline (CodingGuidelineID 1000000 \u2014 Types): newly added test function is missing an explicit return type annotation. Add `-> None`.", "replyToCommentId": 3407883749}, {"file": "src/exports/tests/test_resume_skip_export.py", "line": 38, "severity": "P2", "title": "Missing return type annotation on test function", "detail": "Repository guideline (CodingGuidelineID 1000000 \u2014 Types): newly added test function is missing an explicit return type annotation. Add `-> None`.", "replyToCommentId": 3407883755}]}}]}}
1
+ {"type": "user", "uuid": "a3def0d15ed9d9110-u", "message": {"role": "user", "content": "Return the structured result."}}
2
+ {"type": "assistant", "uuid": "a3def0d15ed9d9110-a", "message": {"role": "assistant", "content": [{"type": "tool_use", "id": "toolu_a3def0d15ed9d9110", "name": "StructuredOutput", "input": {"sha": "6a5169ef635ddb841e6f0465a309691ff77f033e", "clean": false, "blocker": null, "findings": [{"file": "src/exports/tests/test_resume_skip_export.py", "line": 35, "severity": "P2", "title": "Banned identifier `result` in test", "detail": "Naming guideline (CodingGuidelineID 1000000): newly introduced identifier `result` is banned; rename to a descriptive name such as `existing_apk_path` or `skip_path`.", "replyToCommentId": 3407883689}, {"file": "src/exports/tests/test_resume_skip_export.py", "line": 47, "severity": "P2", "title": "Banned identifier `result` in test", "detail": "Naming guideline (CodingGuidelineID 1000000): newly introduced identifier `result` is banned; rename to a descriptive name such as `existing_apk_path` or `skip_path`.", "replyToCommentId": 3407883707}, {"file": "src/logging/tests/test_import_isolation.py", "line": 33, "severity": "P2", "title": "Missing return type annotation on test function", "detail": "Repository guideline (CodingGuidelineID 1000000 \u2014 Types): newly added test function is missing an explicit return type annotation. Add `-> None`.", "replyToCommentId": 3407883713}, {"file": "src/logging/tests/test_import_isolation.py", "line": 43, "severity": "P2", "title": "Missing return type annotation on test function", "detail": "Repository guideline (CodingGuidelineID 1000000 \u2014 Types): newly added test function is missing an explicit return type annotation. Add `-> None`.", "replyToCommentId": 3407883717}, {"file": "src/exports/tests/test_version_export_guards.py", "line": 37, "severity": "P2", "title": "Missing return type annotation on async test", "detail": "Repository guideline (CodingGuidelineID 1000000 \u2014 Types): newly added async test is missing an explicit return type annotation. Add `-> None`.", "replyToCommentId": 3407883723}, {"file": "src/exports/tests/test_version_export_guards.py", "line": 74, "severity": "P2", "title": "Missing return type annotation on async test", "detail": "Repository guideline (CodingGuidelineID 1000000 \u2014 Types): newly added async test is missing an explicit return type annotation. Add `-> None`.", "replyToCommentId": 3407883727}, {"file": "src/exports/tests/test_stp_path_selection.py", "line": 19, "severity": "P2", "title": "Missing return type annotation on test function", "detail": "Repository guideline (CodingGuidelineID 1000000 \u2014 Types): newly added test function is missing an explicit return type annotation. Add `-> None`.", "replyToCommentId": 3407883731}, {"file": "src/exports/tests/test_stp_path_selection.py", "line": 31, "severity": "P2", "title": "Missing return type annotation on test function", "detail": "Repository guideline (CodingGuidelineID 1000000 \u2014 Types): newly added test function is missing an explicit return type annotation. Add `-> None`.", "replyToCommentId": 3407883736}, {"file": "src/exports/tests/test_stp_path_selection.py", "line": 43, "severity": "P2", "title": "Missing return type annotation on test function", "detail": "Repository guideline (CodingGuidelineID 1000000 \u2014 Types): newly added test function is missing an explicit return type annotation. Add `-> None`.", "replyToCommentId": 3407883740}, {"file": "src/exports/tests/test_resume_skip_export.py", "line": 21, "severity": "P2", "title": "Missing return type annotation on test function", "detail": "Repository guideline (CodingGuidelineID 1000000 \u2014 Types): newly added test function is missing an explicit return type annotation. Add `-> None`.", "replyToCommentId": 3407883749}, {"file": "src/exports/tests/test_resume_skip_export.py", "line": 38, "severity": "P2", "title": "Missing return type annotation on test function", "detail": "Repository guideline (CodingGuidelineID 1000000 \u2014 Types): newly added test function is missing an explicit return type annotation. Add `-> None`.", "replyToCommentId": 3407883755}]}}]}}
@@ -1,2 +1,2 @@
1
- {"type": "user", "uuid": "a41f41b1b708ee3b7-u", "message": {"role": "user", "content": "Return the structured result."}}
2
- {"type": "assistant", "uuid": "a41f41b1b708ee3b7-a", "message": {"role": "assistant", "content": [{"type": "tool_use", "id": "toolu_a41f41b1b708ee3b7", "name": "StructuredOutput", "input": {"sha": "7c2f420c4d5b7c83aa47f93d99a0f1420e3373c4", "clean": true, "findings": [], "blocker": null}}]}}
1
+ {"type": "user", "uuid": "a41f41b1b708ee3b7-u", "message": {"role": "user", "content": "Return the structured result."}}
2
+ {"type": "assistant", "uuid": "a41f41b1b708ee3b7-a", "message": {"role": "assistant", "content": [{"type": "tool_use", "id": "toolu_a41f41b1b708ee3b7", "name": "StructuredOutput", "input": {"sha": "7c2f420c4d5b7c83aa47f93d99a0f1420e3373c4", "clean": true, "findings": [], "blocker": null}}]}}
@@ -1,2 +1,2 @@
1
- {"type": "user", "uuid": "a758b880abecc3ff7-u", "message": {"role": "user", "content": "Return the structured result."}}
2
- {"type": "assistant", "uuid": "a758b880abecc3ff7-a", "message": {"role": "assistant", "content": [{"type": "tool_use", "id": "toolu_a758b880abecc3ff7", "name": "StructuredOutput", "input": {"newSha": "7c2f420c4d5b7c83aa47f93d99a0f1420e3373c4", "pushed": false, "resolvedWithoutCommit": true, "summary": "Both Copilot magic-value findings were already fixed at HEAD 7c2f420c (export_runner.py uses web_editor_config.ui_messages.msg_detected_version; os_generation_ssot.py uses os_generation_ssot_constants.unavailable_warning_template); posted inline replies and resolved both review threads (PRRT_kwDORiaSZc6JUqSw, PRRT_kwDORiaSZc6JUqS6)."}}]}}
1
+ {"type": "user", "uuid": "a758b880abecc3ff7-u", "message": {"role": "user", "content": "Return the structured result."}}
2
+ {"type": "assistant", "uuid": "a758b880abecc3ff7-a", "message": {"role": "assistant", "content": [{"type": "tool_use", "id": "toolu_a758b880abecc3ff7", "name": "StructuredOutput", "input": {"newSha": "7c2f420c4d5b7c83aa47f93d99a0f1420e3373c4", "pushed": false, "resolvedWithoutCommit": true, "summary": "Both Copilot magic-value findings were already fixed at HEAD 7c2f420c (export_runner.py uses web_editor_config.ui_messages.msg_detected_version; os_generation_ssot.py uses os_generation_ssot_constants.unavailable_warning_template); posted inline replies and resolved both review threads (PRRT_kwDORiaSZc6JUqSw, PRRT_kwDORiaSZc6JUqS6)."}}]}}
@@ -1,2 +1,2 @@
1
- {"type": "user", "uuid": "a8897b89656b1bd16-u", "message": {"role": "user", "content": "Return the structured result."}}
2
- {"type": "assistant", "uuid": "a8897b89656b1bd16-a", "message": {"role": "assistant", "content": [{"type": "tool_use", "id": "toolu_a8897b89656b1bd16", "name": "StructuredOutput", "input": {"newSha": "7c2f420c4d5b7c83aa47f93d99a0f1420e3373c4", "pushed": true, "resolvedWithoutCommit": false, "summary": "Fixed both Copilot magic-value findings on PR #211: export_runner.py now reuses web_editor_config.ui_messages.msg_detected_version, and os_generation_ssot.py references a new os_generation_ssot_constants.unavailable_warning_template. Added a behavioral TDD test, committed (7c2f420c) and pushed; posted inline replies to both comments (3407912224, 3407912234) and resolved both review threads (PRRT_kwDORiaSZc6JUqSw, PRRT_kwDORiaSZc6JUqS6)."}}]}}
1
+ {"type": "user", "uuid": "a8897b89656b1bd16-u", "message": {"role": "user", "content": "Return the structured result."}}
2
+ {"type": "assistant", "uuid": "a8897b89656b1bd16-a", "message": {"role": "assistant", "content": [{"type": "tool_use", "id": "toolu_a8897b89656b1bd16", "name": "StructuredOutput", "input": {"newSha": "7c2f420c4d5b7c83aa47f93d99a0f1420e3373c4", "pushed": true, "resolvedWithoutCommit": false, "summary": "Fixed both Copilot magic-value findings on PR #211: export_runner.py now reuses web_editor_config.ui_messages.msg_detected_version, and os_generation_ssot.py references a new os_generation_ssot_constants.unavailable_warning_template. Added a behavioral TDD test, committed (7c2f420c) and pushed; posted inline replies to both comments (3407912224, 3407912234) and resolved both review threads (PRRT_kwDORiaSZc6JUqSw, PRRT_kwDORiaSZc6JUqS6)."}}]}}