claude-dev-env 4.2.0 → 7.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (350) hide show
  1. package/.agents/agents/pr-description-writer.md +2 -2
  2. package/.agents/agents/test_agent_frontmatter.py +2 -2
  3. package/.agents/agents/test_pr_description_writer_style.py +1 -3
  4. package/.agents/skills/e-code-review/SKILL.md +12 -12
  5. package/.agents/skills/e-code-review/preflight-proposal.contract.test.mjs +20 -0
  6. package/.agents/skills/e-code-review/reference/fix.md +15 -17
  7. package/.agents/skills/e-code-review/reference/loop.md +35 -39
  8. package/.agents/skills/e-code-review/reference/mode-contract.test.mjs +2 -1
  9. package/.agents/skills/e-code-review/reference/preflight-proposal.md +44 -9
  10. package/.agents/skills/e-code-review/reference/runner-selection.md +13 -14
  11. package/.agents/skills/issue-tracker/SKILL.md +7 -0
  12. package/.agents/skills/pull-request/SKILL.md +216 -0
  13. package/.agents/skills/pull-request/reference/publication-tasks.md +18 -0
  14. package/.agents/skills/pull-request/scripts/github_pr_command_constants/__init__.py +1 -0
  15. package/.agents/skills/pull-request/scripts/github_pr_command_constants/config/__init__.py +1 -0
  16. package/.agents/skills/pull-request/scripts/github_pr_command_constants/config/constants.py +69 -0
  17. package/.agents/skills/pull-request/scripts/pull_request.py +265 -0
  18. package/.agents/skills/pull-request/scripts/recover_legacy_author.py +251 -0
  19. package/.agents/skills/pull-request/scripts/test_github_pr_command_constants.py +58 -0
  20. package/.agents/skills/pull-request/scripts/test_pull_request.py +396 -0
  21. package/.agents/skills/pull-request/scripts/test_recover_legacy_author.py +221 -0
  22. package/.agents/skills/skill-builder/SKILL.md +3 -196
  23. package/.agents/skills/test-runner/SKILL.md +69 -0
  24. package/.agents/skills/test-runner/scripts/preflight_checks.py +365 -0
  25. package/.agents/skills/test-runner/scripts/run_tests.py +316 -0
  26. package/.agents/skills/test-runner/scripts/test_preflight_checks.py +185 -0
  27. package/.agents/skills/test-runner/scripts/test_run_tests.py +281 -0
  28. package/.agents/skills/test-runner/scripts/test_runner_constants/__init__.py +1 -0
  29. package/.agents/skills/test-runner/scripts/test_runner_constants/config/__init__.py +1 -0
  30. package/.agents/skills/test-runner/scripts/test_runner_constants/config/constants.py +96 -0
  31. package/.agents/skills-archived/pr-loop-cloud-transport/reference/identity-and-hooks.md +1 -1
  32. package/_shared/pr-loop/scripts/pr_loop_shared_constants/terminology_sweep_constants.py +6 -0
  33. package/_shared/pr-loop/scripts/terminology_sweep.py +64 -19
  34. package/_shared/pr-loop/scripts/tests/test_terminology_sweep.py +59 -0
  35. package/bin/ever-shipped-skills.mjs +2 -0
  36. package/bin/ever-shipped-skills.test.mjs +8 -0
  37. package/bin/install.agents-home.test.mjs +15 -13
  38. package/bin/install.mjs +37 -1
  39. package/bin/install.policy-lint.test.mjs +78 -0
  40. package/bin/install.prune.test.mjs +149 -20
  41. package/bin/install.pstack-plugin.test.mjs +114 -0
  42. package/bin/install.test.mjs +61 -7
  43. package/bin/refresh-pstack-plugin-skills.test.mjs +211 -0
  44. package/hooks/advisory/pr_done_reminder.py +342 -0
  45. package/hooks/advisory/test_pr_done_reminder.py +249 -0
  46. package/hooks/blocking/bash_post_call_dispatcher.py +53 -6
  47. package/hooks/blocking/code_rules_paired_test.py +22 -15
  48. package/hooks/blocking/pii_prevention_blocker.py +32 -51
  49. package/hooks/blocking/test_bash_post_call_dispatcher.py +55 -17
  50. package/hooks/blocking/test_bash_pre_tool_use_dispatcher.py +1 -4
  51. package/hooks/blocking/test_code_rules_command_dispatch.py +2 -2
  52. package/hooks/blocking/test_code_rules_enforcer_paired_test.py +36 -29
  53. package/hooks/blocking/test_code_rules_js_resume_task_enumeration.py +4 -4
  54. package/hooks/blocking/test_code_rules_js_returns_object_schemaless.py +5 -5
  55. package/hooks/blocking/test_pii_prevention_blocker.py +37 -89
  56. package/hooks/blocking/test_pre_tool_use_dispatcher.py +68 -133
  57. package/hooks/blocking/test_pre_tool_use_dispatcher_native.py +20 -51
  58. package/hooks/blocking/test_precommit_code_rules_gate_native_owner.py +18 -18
  59. package/hooks/gh_post_body_texts.py +195 -0
  60. package/hooks/git-hooks/git_hooks_constants/staged_policy_lint.py +12 -0
  61. package/hooks/git-hooks/pre_commit.py +52 -11
  62. package/hooks/git-hooks/test_installed_policy_lint.py +159 -0
  63. package/hooks/git-hooks/test_native_policy_commit.py +87 -0
  64. package/hooks/git-hooks/test_pre_commit.py +246 -0
  65. package/hooks/hooks.json +1 -16
  66. package/hooks/hooks_constants/bash_post_call_dispatcher_constants.py +14 -2
  67. package/hooks/hooks_constants/bash_pre_tool_use_dispatcher_constants.py +0 -8
  68. package/hooks/hooks_constants/gh_post_body_texts_constants.py +12 -0
  69. package/hooks/hooks_constants/pr_done_reminder_constants.py +98 -0
  70. package/hooks/hooks_constants/pre_tool_use_dispatcher_constants.py +10 -43
  71. package/hooks/hooks_constants/test_bash_post_call_dispatcher_constants.py +17 -7
  72. package/hooks/hooks_constants/test_bash_pre_tool_use_dispatcher_constants.py +0 -7
  73. package/hooks/hooks_constants/test_failure_recorder_constants.py +3 -3
  74. package/hooks/hooks_constants/test_gh_post_body_texts_constants.py +19 -0
  75. package/hooks/hooks_constants/test_pr_done_reminder_constants.py +40 -0
  76. package/hooks/hooks_constants/test_pre_tool_use_dispatcher_constants.py +20 -32
  77. package/hooks/observability/test_failure_recorder.py +1 -1
  78. package/hooks/pyproject.toml +1 -1
  79. package/hooks/test_gh_post_body_texts.py +79 -0
  80. package/package.json +2 -2
  81. package/rules/durable-post-artifacts.md +32 -20
  82. package/rules/gh-cli-conventions.md +23 -13
  83. package/rules/git-workflow.md +21 -8
  84. package/scripts/check.ps1 +0 -1
  85. package/{.agents/skills/run-claude-dev-env/driver.mjs → scripts/ci/installer-lifecycle.mjs} +3 -28
  86. package/scripts/ci/installer-lifecycle.test.mjs +26 -0
  87. package/scripts/ci/windows-installer-lifecycle.ps1 +1 -11
  88. package/scripts/dev_env_scripts_constants/shared_tree_constants.py +4 -5
  89. package/scripts/durable_post_lint.py +206 -0
  90. package/scripts/durable_post_lint_config/config/constants.py +96 -0
  91. package/scripts/durable_post_lint_config/config/test_constants.py +25 -0
  92. package/scripts/policy_lint/adapter_configuration.py +53 -12
  93. package/scripts/policy_lint/adapter_detectors.py +13 -6
  94. package/scripts/policy_lint/adapters.py +7 -4
  95. package/scripts/policy_lint/config/constants.py +5 -0
  96. package/scripts/policy_lint/engine.py +48 -4
  97. package/scripts/policy_lint/model.py +1 -0
  98. package/scripts/policy_lint/registry.py +1 -1
  99. package/scripts/policy_lint/selection.py +2 -1
  100. package/scripts/policy_lint/selection_git.py +2 -0
  101. package/scripts/refresh_pstack_plugin_skills.mjs +200 -0
  102. package/scripts/repository_checks/config/constants.py +0 -5
  103. package/scripts/shared_tree_paths.py +32 -3
  104. package/scripts/test_durable_post_lint.py +332 -0
  105. package/scripts/test_shared_tree_paths.py +33 -0
  106. package/scripts/tests/test_adapter_configuration.py +54 -0
  107. package/scripts/tests/test_policy_lint_rules_archive.py +120 -0
  108. package/scripts/tests/test_policy_lint_rules_engine.py +25 -4
  109. package/scripts/tests/test_policy_lint_rules_installed_loader.py +29 -0
  110. package/scripts/tests/test_policy_lint_rules_registry.py +227 -13
  111. package/scripts/tests/test_policy_lint_selection.py +25 -2
  112. package/scripts/tests/test_policy_lint_selection_native_index.py +30 -0
  113. package/scripts/tests/test_rules.py +3 -7
  114. package/.agents/skills/_shared/pr-loop/.claude/CLAUDE.md +0 -1
  115. package/.agents/skills/_shared/pr-loop/AGENTS.md +0 -1
  116. package/.agents/skills/_shared/pr-loop/audit-contract.md +0 -5
  117. package/.agents/skills/_shared/pr-loop/audit-reply-template.md +0 -5
  118. package/.agents/skills/_shared/pr-loop/code-rules-gate.md +0 -5
  119. package/.agents/skills/_shared/pr-loop/fix-protocol.md +0 -5
  120. package/.agents/skills/_shared/pr-loop/gh-payloads.md +0 -5
  121. package/.agents/skills/_shared/pr-loop/portable-driver.md +0 -149
  122. package/.agents/skills/_shared/pr-loop/post-audit-thread-contract.md +0 -5
  123. package/.agents/skills/_shared/pr-loop/precatch-rubric.md +0 -5
  124. package/.agents/skills/_shared/pr-loop/preflight-proposal.contract.test.mjs +0 -119
  125. package/.agents/skills/_shared/pr-loop/preflight-proposal.md +0 -5
  126. package/.agents/skills/_shared/pr-loop/prompts/.claude/CLAUDE.md +0 -1
  127. package/.agents/skills/_shared/pr-loop/prompts/AGENTS.md +0 -1
  128. package/.agents/skills/_shared/pr-loop/prompts/pr-consistency-audit.xml +0 -324
  129. package/.agents/skills/_shared/pr-loop/scripts/.claude/CLAUDE.md +0 -1
  130. package/.agents/skills/_shared/pr-loop/scripts/AGENTS.md +0 -1
  131. package/.agents/skills/_shared/pr-loop/scripts/RUNTIME_SCRIPTS.md +0 -29
  132. package/.agents/skills/_shared/pr-loop/scripts/_cli_utils.py +0 -22
  133. package/.agents/skills/_shared/pr-loop/scripts/_path_resolver.py +0 -186
  134. package/.agents/skills/_shared/pr-loop/scripts/_xml_utils.py +0 -20
  135. package/.agents/skills/_shared/pr-loop/scripts/audit_category_schema.py +0 -355
  136. package/.agents/skills/_shared/pr-loop/scripts/build_audit_prompt.py +0 -289
  137. package/.agents/skills/_shared/pr-loop/scripts/build_converge_task_list.py +0 -310
  138. package/.agents/skills/_shared/pr-loop/scripts/build_fix_prompt.py +0 -292
  139. package/.agents/skills/_shared/pr-loop/scripts/init_loop_state.py +0 -134
  140. package/.agents/skills/_shared/pr-loop/scripts/portable_converge_driver.py +0 -1646
  141. package/.agents/skills/_shared/pr-loop/scripts/preflight_worktree.py +0 -392
  142. package/.agents/skills/_shared/pr-loop/scripts/select_converge_pacer.py +0 -215
  143. package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/.claude/CLAUDE.md +0 -1
  144. package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/AGENTS.md +0 -1
  145. package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/__init__.py +0 -0
  146. package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/audit_category_schema_constants.py +0 -37
  147. package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/converge_task_list_constants.py +0 -56
  148. package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/handoff_constants.py +0 -45
  149. package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/pacer_constants.py +0 -47
  150. package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/path_resolver_constants.py +0 -157
  151. package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/portable_driver_constants.py +0 -177
  152. package/.agents/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/preflight_constants.py +0 -70
  153. package/.agents/skills/_shared/pr-loop/scripts/teardown_worktrees.py +0 -172
  154. package/.agents/skills/_shared/pr-loop/scripts/test__path_resolver.py +0 -106
  155. package/.agents/skills/_shared/pr-loop/scripts/test_audit_category_schema.py +0 -94
  156. package/.agents/skills/_shared/pr-loop/scripts/test_build_audit_prompt.py +0 -499
  157. package/.agents/skills/_shared/pr-loop/scripts/test_build_converge_task_list.py +0 -140
  158. package/.agents/skills/_shared/pr-loop/scripts/test_build_fix_prompt.py +0 -262
  159. package/.agents/skills/_shared/pr-loop/scripts/test_init_loop_state.py +0 -71
  160. package/.agents/skills/_shared/pr-loop/scripts/test_portable_converge_driver.py +0 -1119
  161. package/.agents/skills/_shared/pr-loop/scripts/test_preflight_worktree.py +0 -287
  162. package/.agents/skills/_shared/pr-loop/scripts/test_select_converge_pacer.py +0 -207
  163. package/.agents/skills/_shared/pr-loop/scripts/test_teardown_worktrees.py +0 -59
  164. package/.agents/skills/_shared/pr-loop/scripts/test_write_handoff.py +0 -201
  165. package/.agents/skills/_shared/pr-loop/scripts/write_audit_outcomes.py +0 -182
  166. package/.agents/skills/_shared/pr-loop/scripts/write_fix_outcomes.py +0 -206
  167. package/.agents/skills/_shared/pr-loop/scripts/write_handoff.py +0 -309
  168. package/.agents/skills/_shared/pr-loop/state-schema.md +0 -5
  169. package/.agents/skills/_shared/pr-loop/worker-spawn.md +0 -5
  170. package/.agents/skills/autoconverge/.claude/CLAUDE.md +0 -1
  171. package/.agents/skills/autoconverge/AGENTS.md +0 -1
  172. package/.agents/skills/autoconverge/SKILL.md +0 -431
  173. package/.agents/skills/autoconverge/reference/.claude/CLAUDE.md +0 -1
  174. package/.agents/skills/autoconverge/reference/AGENTS.md +0 -1
  175. package/.agents/skills/autoconverge/reference/closing-report.md +0 -93
  176. package/.agents/skills/autoconverge/reference/convergence-gates.md +0 -31
  177. package/.agents/skills/autoconverge/reference/convergence.md +0 -219
  178. package/.agents/skills/autoconverge/reference/copilot-findings.md +0 -51
  179. package/.agents/skills/autoconverge/reference/gotchas.md +0 -66
  180. package/.agents/skills/autoconverge/reference/headless-safety.md +0 -44
  181. package/.agents/skills/autoconverge/reference/multi-pr.md +0 -128
  182. package/.agents/skills/autoconverge/reference/self-closing-loop.md +0 -84
  183. package/.agents/skills/autoconverge/reference/stop-conditions.md +0 -127
  184. package/.agents/skills/autoconverge/test_portable_pacer_gate.py +0 -54
  185. package/.agents/skills/autoconverge/workflow/.claude/CLAUDE.md +0 -1
  186. package/.agents/skills/autoconverge/workflow/AGENTS.md +0 -1
  187. package/.agents/skills/autoconverge/workflow/aggregate_runs.py +0 -371
  188. package/.agents/skills/autoconverge/workflow/autoconverge_report_constants/.claude/CLAUDE.md +0 -1
  189. package/.agents/skills/autoconverge/workflow/autoconverge_report_constants/AGENTS.md +0 -1
  190. package/.agents/skills/autoconverge/workflow/autoconverge_report_constants/__init__.py +0 -0
  191. package/.agents/skills/autoconverge/workflow/autoconverge_report_constants/render_report_constants.py +0 -250
  192. package/.agents/skills/autoconverge/workflow/converge.clean-audit.test.mjs +0 -665
  193. package/.agents/skills/autoconverge/workflow/converge.codex-gate.test.mjs +0 -472
  194. package/.agents/skills/autoconverge/workflow/converge.contract.test.mjs +0 -64
  195. package/.agents/skills/autoconverge/workflow/converge.copilot-gate.test.mjs +0 -1009
  196. package/.agents/skills/autoconverge/workflow/converge.fix-progress.test.mjs +0 -107
  197. package/.agents/skills/autoconverge/workflow/converge.fix-recovery.test.mjs +0 -33
  198. package/.agents/skills/autoconverge/workflow/converge.merge-conflict.test.mjs +0 -15
  199. package/.agents/skills/autoconverge/workflow/converge.mjs +0 -3132
  200. package/.agents/skills/autoconverge/workflow/converge.p2-advance.test.mjs +0 -202
  201. package/.agents/skills/autoconverge/workflow/converge.path-aware.test.mjs +0 -47
  202. package/.agents/skills/autoconverge/workflow/converge.precatch.test.mjs +0 -152
  203. package/.agents/skills/autoconverge/workflow/converge.run-input.test.mjs +0 -116
  204. package/.agents/skills/autoconverge/workflow/converge_multi.mjs +0 -169
  205. package/.agents/skills/autoconverge/workflow/converge_multi.run-input.test.mjs +0 -173
  206. package/.agents/skills/autoconverge/workflow/convergence_summary.py +0 -110
  207. package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a11d903476b803493.jsonl +0 -2
  208. package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a26213978adeef6fb.jsonl +0 -2
  209. package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a3def0d15ed9d9110.jsonl +0 -2
  210. package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a41f41b1b708ee3b7.jsonl +0 -2
  211. package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a758b880abecc3ff7.jsonl +0 -2
  212. package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-a8897b89656b1bd16.jsonl +0 -2
  213. package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-ab1c2d3e4f5a6b7c8.jsonl +0 -2
  214. package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-abd463d744a1437bc.jsonl +0 -2
  215. package/.agents/skills/autoconverge/workflow/fixtures/wf_run/subagents/workflows/wf_881252e6-700/agent-ad19d027ae8ee1816.jsonl +0 -2
  216. package/.agents/skills/autoconverge/workflow/fixtures/wf_run/workflows/wf_881252e6-700.json +0 -266
  217. package/.agents/skills/autoconverge/workflow/render_report.py +0 -1024
  218. package/.agents/skills/autoconverge/workflow/test_aggregate_runs.py +0 -134
  219. package/.agents/skills/autoconverge/workflow/test_convergence_summary.py +0 -149
  220. package/.agents/skills/autoconverge/workflow/test_render_report.py +0 -812
  221. package/.agents/skills/hitl/SKILL.md +0 -45
  222. package/.agents/skills/pr-cleanup/SKILL.md +0 -140
  223. package/.agents/skills/pr-cleanup/reference/process-inventory.md +0 -17
  224. package/.agents/skills/pr-cleanup/reference/task-seeds.md +0 -22
  225. package/.agents/skills/pr-name-by-capability/SKILL.md +0 -73
  226. package/.agents/skills/pr-name-by-capability/reference/fetch-commands.md +0 -29
  227. package/.agents/skills/pr-name-by-capability/reference/offense-examples.md +0 -32
  228. package/.agents/skills/pr-name-by-capability/reference/report-template.md +0 -29
  229. package/.agents/skills/pr-name-by-capability/reference/rule-checklist.md +0 -38
  230. package/.agents/skills/pr-name-by-capability/reference/task-seeds.md +0 -10
  231. package/.agents/skills/pr-plain-language-cleanup/SKILL.md +0 -93
  232. package/.agents/skills/pr-refinement/SKILL.md +0 -31
  233. package/.agents/skills/pr-shared-extraction/SKILL.md +0 -177
  234. package/.agents/skills/pr-shared-extraction/reference/examples.md +0 -46
  235. package/.agents/skills/pr-shared-extraction/reference/offense-taxonomy.md +0 -100
  236. package/.agents/skills/pr-small-cl/SKILL.md +0 -52
  237. package/.agents/skills/pr-title-description/SKILL.md +0 -96
  238. package/.agents/skills/prototype/SKILL.md +0 -85
  239. package/.agents/skills/prototype/reference/honest-limitations.md +0 -23
  240. package/.agents/skills/prototype/reference/promotion-tasks.md +0 -23
  241. package/.agents/skills/prototype/scripts/build_sandbox_settings.py +0 -249
  242. package/.agents/skills/prototype/scripts/conftest.py +0 -15
  243. package/.agents/skills/prototype/scripts/launch_sandbox.py +0 -205
  244. package/.agents/skills/prototype/scripts/probe_sandbox_safety.py +0 -311
  245. package/.agents/skills/prototype/scripts/prototype_scripts_constants/__init__.py +0 -1
  246. package/.agents/skills/prototype/scripts/prototype_scripts_constants/config/__init__.py +0 -0
  247. package/.agents/skills/prototype/scripts/prototype_scripts_constants/config/build_sandbox_settings_constants.py +0 -41
  248. package/.agents/skills/prototype/scripts/prototype_scripts_constants/config/launch_sandbox_constants.py +0 -23
  249. package/.agents/skills/prototype/scripts/prototype_scripts_constants/config/probe_sandbox_safety_constants.py +0 -45
  250. package/.agents/skills/prototype/scripts/prototype_scripts_constants/config/prototype_common_constants.py +0 -10
  251. package/.agents/skills/prototype/scripts/test_build_sandbox_settings.py +0 -275
  252. package/.agents/skills/prototype/scripts/test_launch_sandbox.py +0 -303
  253. package/.agents/skills/prototype/scripts/test_probe_sandbox_safety.py +0 -312
  254. package/.agents/skills/prototype/workflows/promotion.md +0 -25
  255. package/.agents/skills/prototype/workflows/sandbox.md +0 -35
  256. package/.agents/skills/rebase/.claude/CLAUDE.md +0 -1
  257. package/.agents/skills/rebase/AGENTS.md +0 -1
  258. package/.agents/skills/rebase/SKILL.md +0 -178
  259. package/.agents/skills/review-router/SKILL.md +0 -51
  260. package/.agents/skills/review-router/reference/route-policy.json +0 -7
  261. package/.agents/skills/review-router/reference/task-seeds.md +0 -1
  262. package/.agents/skills/review-router/scripts/conftest.py +0 -4
  263. package/.agents/skills/review-router/scripts/review_router.py +0 -50
  264. package/.agents/skills/review-router/scripts/review_router_cli.py +0 -211
  265. package/.agents/skills/review-router/scripts/review_router_constants/__init__.py +0 -1
  266. package/.agents/skills/review-router/scripts/review_router_constants/config/__init__.py +0 -0
  267. package/.agents/skills/review-router/scripts/review_router_constants/config/constants.py +0 -13
  268. package/.agents/skills/review-router/scripts/test_review_router.py +0 -23
  269. package/.agents/skills/review-router/scripts/test_review_router_cli.py +0 -47
  270. package/.agents/skills/review-tier/SKILL.md +0 -56
  271. package/.agents/skills/review-tier/reference/task-seeds.md +0 -1
  272. package/.agents/skills/review-tier/reference/tier-policy.json +0 -9
  273. package/.agents/skills/review-tier/scripts/conftest.py +0 -4
  274. package/.agents/skills/review-tier/scripts/review_tier.py +0 -359
  275. package/.agents/skills/review-tier/scripts/review_tier_constants/__init__.py +0 -1
  276. package/.agents/skills/review-tier/scripts/review_tier_constants/config/__init__.py +0 -0
  277. package/.agents/skills/review-tier/scripts/review_tier_constants/config/constants.py +0 -29
  278. package/.agents/skills/review-tier/scripts/test_generation.py +0 -159
  279. package/.agents/skills/review-tier/scripts/test_review_tier.py +0 -33
  280. package/.agents/skills/run-claude-dev-env/SKILL.md +0 -84
  281. package/.agents/skills/session-log/.claude/CLAUDE.md +0 -1
  282. package/.agents/skills/session-log/AGENTS.md +0 -1
  283. package/.agents/skills/session-log/SKILL.md +0 -170
  284. package/.agents/skills/session-log/templates/frontmatter.md +0 -40
  285. package/.agents/skills/session-tidy/.claude/CLAUDE.md +0 -1
  286. package/.agents/skills/session-tidy/AGENTS.md +0 -1
  287. package/.agents/skills/session-tidy/SKILL.md +0 -191
  288. package/.agents/skills/skill-builder/references/.claude/CLAUDE.md +0 -1
  289. package/.agents/skills/skill-builder/references/AGENTS.md +0 -1
  290. package/.agents/skills/skill-builder/references/delegation-map.md +0 -130
  291. package/.agents/skills/skill-builder/references/description-field.md +0 -111
  292. package/.agents/skills/skill-builder/references/deterministic-elements.md +0 -218
  293. package/.agents/skills/skill-builder/references/progressive-disclosure.md +0 -122
  294. package/.agents/skills/skill-builder/references/self-audit-checklist.md +0 -126
  295. package/.agents/skills/skill-builder/references/skill-modularity.md +0 -111
  296. package/.agents/skills/skill-builder/references/skill-types.md +0 -232
  297. package/.agents/skills/skill-builder/references/thariq-x-post-skills.json +0 -33
  298. package/.agents/skills/skill-builder/templates/.claude/CLAUDE.md +0 -1
  299. package/.agents/skills/skill-builder/templates/AGENTS.md +0 -1
  300. package/.agents/skills/skill-builder/templates/gap-analysis.md +0 -87
  301. package/.agents/skills/skill-builder/workflows/.claude/CLAUDE.md +0 -1
  302. package/.agents/skills/skill-builder/workflows/AGENTS.md +0 -1
  303. package/.agents/skills/skill-builder/workflows/improve-skill.md +0 -136
  304. package/.agents/skills/skill-builder/workflows/new-skill.md +0 -179
  305. package/.agents/skills/skill-builder/workflows/polish-skill.md +0 -139
  306. package/.agents/skills/source-command-sr-loop/SKILL.md +0 -82
  307. package/.agents/skills/update/.claude/CLAUDE.md +0 -1
  308. package/.agents/skills/update/AGENTS.md +0 -1
  309. package/.agents/skills/update/SKILL.md +0 -190
  310. package/_shared/pr-loop/AGENTS.md +0 -1
  311. package/_shared/pr-loop/CLAUDE.md +0 -1
  312. package/_shared/pr-loop/audit-contract.md +0 -183
  313. package/_shared/pr-loop/audit-reply-template.md +0 -151
  314. package/_shared/pr-loop/code-rules-gate.md +0 -65
  315. package/_shared/pr-loop/fix-protocol.md +0 -60
  316. package/_shared/pr-loop/gh-payloads.md +0 -75
  317. package/_shared/pr-loop/post-audit-thread-contract.md +0 -51
  318. package/_shared/pr-loop/precatch-rubric.md +0 -66
  319. package/_shared/pr-loop/preflight-proposal.md +0 -56
  320. package/_shared/pr-loop/scripts/post_audit_thread.py +0 -1242
  321. package/_shared/pr-loop/scripts/pr_loop_shared_constants/post_audit_thread_constants.py +0 -199
  322. package/_shared/pr-loop/scripts/tests/test_post_audit_thread.py +0 -1115
  323. package/_shared/pr-loop/scripts/tests/test_post_audit_thread_constants.py +0 -129
  324. package/_shared/pr-loop/state-schema.md +0 -45
  325. package/_shared/pr-loop/worker-spawn.md +0 -189
  326. package/hooks/_gh_pr_author_swap_utils.py +0 -1225
  327. package/hooks/blocking/conventional_pr_title_gate.py +0 -443
  328. package/hooks/blocking/gh_body_arg_blocker.py +0 -176
  329. package/hooks/blocking/gh_pr_author_enforcer.py +0 -487
  330. package/hooks/blocking/gh_pr_author_restore.py +0 -105
  331. package/hooks/blocking/pr_description_writer_gate.py +0 -256
  332. package/hooks/blocking/test_conventional_pr_title_gate.py +0 -640
  333. package/hooks/blocking/test_gh_body_arg_blocker.py +0 -441
  334. package/hooks/blocking/test_gh_pr_author_enforcer.py +0 -1166
  335. package/hooks/blocking/test_gh_pr_author_restore.py +0 -513
  336. package/hooks/blocking/test_gh_pr_author_swap_utils.py +0 -910
  337. package/hooks/blocking/test_pr_description_writer_gate.py +0 -234
  338. package/hooks/blocking/test_preflight_check.py +0 -343
  339. package/hooks/blocking/test_test_preflight_check.py +0 -80
  340. package/hooks/blocking/test_volatile_path_in_post_blocker.py +0 -322
  341. package/hooks/blocking/volatile_path_in_post_blocker.py +0 -461
  342. package/hooks/hooks_constants/conventional_pr_title_gate_constants.py +0 -58
  343. package/hooks/hooks_constants/gh_pr_author_swap_constants.py +0 -76
  344. package/hooks/hooks_constants/pr_description_writer_gate_constants.py +0 -73
  345. package/hooks/hooks_constants/volatile_path_in_post_blocker_constants.py +0 -55
  346. package/hooks/observability/pr_description_writer_spawn_tracker.py +0 -113
  347. package/hooks/observability/test_pr_description_writer_spawn_tracker.py +0 -150
  348. package/hooks/session/gh_pr_author_session_cleanup.py +0 -175
  349. package/hooks/session/test_gh_pr_author_session_cleanup.py +0 -576
  350. package/hooks/test__gh_pr_author_swap_utils.py +0 -333
@@ -1,1646 +0,0 @@
1
- #!/usr/bin/env python3
2
- """portable_converge_driver phase machine (deterministic only).
3
-
4
- The agent never invents phase transitions, wait delays, clean stamps, or
5
- ready decisions from prose. It runs this CLI, reads JSON, and either:
6
-
7
- * executes the named ``commands`` (shell helpers already scripted), or
8
- * performs the single judgment step named in ``next`` (fix implementation,
9
- worker spawn body) and reports outcomes back through this CLI.
10
-
11
- ::
12
-
13
- portable_converge_driver.py open-run --skill autoconverge \\
14
- --has-workflow 0 --has-schedule-wakeup 0 \\
15
- --owner O --repo R --pr-number N --cwd DIR --state-dir DIR \\
16
- [--session-model third-party] [--copilot-down 0|1] [--bugbot-down 0|1] \\
17
- [--codex-down 0|1] [--codex-required 0|1]
18
-
19
- portable_converge_driver.py after-code-review --state-file PATH \\
20
- --returncode 0 --dirty-tree 0 --current-head SHA \\
21
- --served-command ''|in_session|chain-command
22
-
23
- portable_converge_driver.py after-bugteam --state-file PATH \\
24
- --pushed 0|1 --converged 0|1 --current-head SHA
25
-
26
- portable_converge_driver.py after-bugbot --state-file PATH \\
27
- --classification clean|dirty|absent|down --current-head SHA \\
28
- [--inline-lag 0|1]
29
-
30
- portable_converge_driver.py after-codex --state-file PATH \\
31
- --classification clean|dirty|down --current-head SHA
32
-
33
- portable_converge_driver.py after-copilot-wait --state-file PATH \\
34
- --review-surfaced 0|1 --classification clean|dirty|absent|down \\
35
- --current-head SHA
36
-
37
- portable_converge_driver.py after-ready-check --state-file PATH \\
38
- --check-exit 0|1|2 --current-head SHA
39
-
40
- portable_converge_driver.py show-state --state-file PATH
41
-
42
- Successful code-review serve: returncode 0 only. ``served_command`` is
43
- informational (empty / ``in_session`` / chain argv) and does not decide
44
- success. Non-zero returncode is always a failed serve.
45
-
46
- After Bugbot clean or down, the machine runs the Codex step (or the
47
- codex-down / not-required waiver) before COPILOT_WAIT or check_ready.
48
-
49
- When ``--codex-down`` is off, open-run and every codex decision point probe
50
- weekly usage through ``codex_usage_probe`` so ``codex_required`` matches
51
- ``check_convergence`` (CLI ``--codex-required 1`` still forces the step on).
52
- """
53
-
54
- from __future__ import annotations
55
-
56
- import argparse
57
- import json
58
- import os
59
- import subprocess
60
- import sys
61
- from pathlib import Path
62
- from typing import Mapping
63
-
64
- _self_dir = Path(__file__).resolve().parent
65
- if str(_self_dir) not in sys.path:
66
- sys.path.insert(0, str(_self_dir))
67
-
68
- from build_converge_task_list import build_converge_task_list # noqa: E402
69
- from select_converge_pacer import ( # noqa: E402
70
- parse_bool_flag,
71
- select_converge_pacer,
72
- )
73
- from skills_pr_loop_constants.pacer_constants import ( # noqa: E402
74
- CLI_HAS_SCHEDULE_WAKEUP_FLAG,
75
- CLI_HAS_WORKFLOW_FLAG,
76
- CLI_SKILL_FLAG,
77
- PACER_PORTABLE,
78
- )
79
- from skills_pr_loop_constants.portable_driver_constants import ( # noqa: E402
80
- ALL_CLASSIFICATIONS,
81
- ALL_CODEX_CLASSIFICATIONS,
82
- ALL_COPILOT_CLASSIFICATIONS,
83
- ALL_GIT_REV_PARSE_HEAD_ARGV,
84
- BLOCKER_BUGBOT_WAIT_CAP,
85
- BLOCKER_COPILOT_WAIT_CAP,
86
- BLOCKER_INLINE_LAG_CAP,
87
- BLOCKER_NOT_PORTABLE,
88
- BLOCKER_PREFLIGHT,
89
- BLOCKER_REVIEW_FAILED,
90
- BUGBOT_ABSENT_WAIT_HARD_CAP,
91
- BUGBOT_INLINE_LAG_WAIT_SECONDS,
92
- CHECK_CONVERGENCE_BUGBOT_DOWN_FLAG,
93
- CHECK_CONVERGENCE_CODEX_CLEAN_AT_FLAG,
94
- CHECK_CONVERGENCE_CODEX_DOWN_FLAG,
95
- CHECK_CONVERGENCE_COPILOT_DOWN_FLAG,
96
- CHECK_CONVERGENCE_OWNER_FLAG,
97
- CHECK_CONVERGENCE_PR_NUMBER_FLAG,
98
- CHECK_CONVERGENCE_RELATIVE_PATH,
99
- CHECK_CONVERGENCE_REPO_FLAG,
100
- CLASSIFICATION_ABSENT,
101
- CLASSIFICATION_DIRTY,
102
- CLASSIFICATION_DOWN,
103
- CLI_BUGBOT_DOWN_FLAG,
104
- CLI_CHECK_CONVERGENCE_EXIT_FLAG,
105
- CLI_CLASSIFICATION_FLAG,
106
- CLI_CODEX_DOWN_FLAG,
107
- CLI_CODEX_REQUIRED_FLAG,
108
- CLI_CONVERGED_FLAG,
109
- CLI_COPILOT_DOWN_FLAG,
110
- CLI_CURRENT_HEAD_FLAG,
111
- CLI_CWD_FLAG,
112
- CLI_DESCRIPTION,
113
- CLI_DIRTY_TREE_FLAG,
114
- CLI_INLINE_LAG_FLAG,
115
- CLI_OWNER_FLAG,
116
- CLI_PR_NUMBER_FLAG,
117
- CLI_PUSHED_FLAG,
118
- CLI_REPO_FLAG,
119
- CLI_RETURNCODE_FLAG,
120
- CLI_REVIEW_SURFACED_FLAG,
121
- CLI_SERVED_COMMAND_FLAG,
122
- CLI_SESSION_MODEL_FLAG,
123
- CLI_STATE_DIR_FLAG,
124
- CLI_STATE_FILE_FLAG,
125
- COMMAND_AFTER_BUGBOT,
126
- COMMAND_AFTER_BUGTEAM,
127
- COMMAND_AFTER_CODE_REVIEW,
128
- COMMAND_AFTER_CODEX,
129
- COMMAND_AFTER_COPILOT_WAIT,
130
- COMMAND_AFTER_READY_CHECK,
131
- COMMAND_OPEN_RUN,
132
- COMMAND_SHOW_STATE,
133
- COPILOT_WAIT_HARD_CAP,
134
- DEFAULT_SESSION_MODEL,
135
- DEFAULT_WAIT_SECONDS,
136
- EXIT_CONTRACT_ERROR,
137
- EXIT_SUCCESS,
138
- EXIT_USAGE_ERROR,
139
- INLINE_LAG_STREAK_CAP,
140
- INVOKE_CODE_REVIEW_RELATIVE_PATH,
141
- NEXT_APPLY_FIXES,
142
- NEXT_CHECK_READY,
143
- NEXT_MARK_READY,
144
- NEXT_POLL_WAIT,
145
- NEXT_REQUEST_COPILOT,
146
- NEXT_RUN_BUGBOT_GATE,
147
- NEXT_RUN_BUGTEAM,
148
- NEXT_RUN_CODE_REVIEW,
149
- NEXT_RUN_CODEX,
150
- NEXT_STOP_BLOCKED,
151
- PHASE_BLOCKED,
152
- PHASE_BUGBOT,
153
- PHASE_BUGTEAM,
154
- PHASE_CODE_REVIEW,
155
- PHASE_COPILOT_WAIT,
156
- PHASE_READY,
157
- PREFLIGHT_WORKTREE_SCRIPT_NAME,
158
- RESULT_KEY_BLOCKER,
159
- RESULT_KEY_BUGBOT_DOWN,
160
- RESULT_KEY_CODEX_DOWN,
161
- RESULT_KEY_COMMANDS,
162
- RESULT_KEY_COPILOT_DOWN,
163
- RESULT_KEY_CURRENT_HEAD,
164
- RESULT_KEY_ENTRY_SKILL,
165
- RESULT_KEY_MESSAGE,
166
- RESULT_KEY_NEXT,
167
- RESULT_KEY_OWNER,
168
- RESULT_KEY_PACER,
169
- RESULT_KEY_PHASE,
170
- RESULT_KEY_PR_NUMBER,
171
- RESULT_KEY_REPO,
172
- RESULT_KEY_STATE_FILE,
173
- RESULT_KEY_STATUS,
174
- RESULT_KEY_TICK_COUNT,
175
- RESULT_KEY_WAIT_SECONDS,
176
- STATE_FILENAME,
177
- STATE_JSON_INDENT,
178
- MARK_READY_GH_BINARY,
179
- MARK_READY_PR_TOKEN,
180
- MARK_READY_REPO_FLAG,
181
- MARK_READY_SUBCOMMAND,
182
- STATE_KEY_CODEX_CLEAN_AT,
183
- STATE_KEY_CODEX_DOWN,
184
- STATE_KEY_CODEX_REQUIRED,
185
- STATE_KEY_CWD,
186
- STATE_KEY_PENDING_NEXT,
187
- STATE_KEY_PENDING_WAIT_SECONDS,
188
- STATE_KEY_SESSION_MODEL,
189
- STATE_STAGING_SUFFIX,
190
- STATUS_ERROR,
191
- STATUS_OK,
192
- )
193
- from skills_pr_loop_constants.preflight_constants import ( # noqa: E402
194
- MODE_ARG_FLAG,
195
- MODE_STRICT,
196
- OWNER_ARG_FLAG,
197
- REPO_ARG_FLAG,
198
- )
199
-
200
- def _resolve_shared_pr_loop_scripts_directory() -> Path:
201
- """Return the installed or package ``_shared/pr-loop/scripts`` directory."""
202
- scripts_directory = Path(__file__).resolve().parent
203
- skill_directory = scripts_directory.parent
204
- shared_skill_directory = skill_directory.parent
205
- skills_directory = shared_skill_directory.parent
206
- skills_home_directory = skills_directory.parent
207
- package_or_user_home = (
208
- skills_home_directory.parent
209
- if skills_home_directory.name == ".agents"
210
- else skills_home_directory
211
- )
212
- shared_under_package = package_or_user_home / "_shared" / "pr-loop" / "scripts"
213
- shared_under_claude = (
214
- package_or_user_home / ".claude" / "_shared" / "pr-loop" / "scripts"
215
- )
216
- if shared_under_package.is_dir():
217
- return shared_under_package
218
- return shared_under_claude
219
-
220
-
221
- _shared_pr_loop_scripts_directory = _resolve_shared_pr_loop_scripts_directory()
222
- _shared_scripts_text = str(_shared_pr_loop_scripts_directory)
223
- if _shared_scripts_text not in sys.path:
224
- sys.path.insert(0, _shared_scripts_text)
225
-
226
- from codex_review_scripts_constants.codex_usage_probe_constants import ( # noqa: E402
227
- USAGE_REPORT_KEY_PERCENT_LEFT,
228
- )
229
- from codex_usage_probe import ( # noqa: E402
230
- is_codex_review_required,
231
- probe_weekly_usage_via_subprocess,
232
- )
233
-
234
- _EMPTY_COMMANDS: list[str] = []
235
-
236
-
237
- def _home_path(*parts: str) -> Path:
238
- return Path.home().joinpath(*parts)
239
-
240
-
241
- def _emit(all_payload: Mapping[str, object]) -> None:
242
- print(json.dumps(dict(all_payload), sort_keys=True))
243
-
244
-
245
- def _error_payload(message: str, **extra: object) -> dict[str, object]:
246
- all_payload: dict[str, object] = {
247
- RESULT_KEY_STATUS: STATUS_ERROR,
248
- RESULT_KEY_MESSAGE: message,
249
- }
250
- all_payload.update(extra)
251
- return all_payload
252
-
253
-
254
- def load_state(state_file: Path) -> dict[str, object]:
255
- """Load loop state JSON from disk.
256
-
257
- Args:
258
- state_file: Path to ``pr-converge-state.json``.
259
-
260
- Returns:
261
- Parsed state mapping.
262
-
263
- Raises:
264
- ValueError: When the file is missing or not a JSON object.
265
- """
266
- if not state_file.is_file():
267
- raise ValueError(f"state file missing: {state_file}")
268
- loaded = json.loads(state_file.read_text(encoding="utf-8"))
269
- if not isinstance(loaded, dict):
270
- raise ValueError(f"state file is not a JSON object: {state_file}")
271
- return loaded
272
-
273
-
274
- def save_state(state_file: Path, all_state: Mapping[str, object]) -> None:
275
- """Write loop state JSON atomically.
276
-
277
- Args:
278
- state_file: Destination path.
279
- all_state: State mapping to persist.
280
- """
281
- state_file.parent.mkdir(parents=True, exist_ok=True)
282
- staging_path = state_file.with_suffix(state_file.suffix + STATE_STAGING_SUFFIX)
283
- staging_path.write_text(
284
- json.dumps(dict(all_state), indent=STATE_JSON_INDENT, sort_keys=True)
285
- + "\n",
286
- encoding="utf-8",
287
- )
288
- os.replace(staging_path, state_file)
289
-
290
-
291
- def build_initial_state(
292
- *,
293
- current_head: str,
294
- entry_skill: str,
295
- is_copilot_down: bool,
296
- is_bugbot_down: bool,
297
- owner: str,
298
- repo: str,
299
- pr_number: int,
300
- is_codex_down: bool = False,
301
- is_codex_required: bool = False,
302
- session_model: str = DEFAULT_SESSION_MODEL,
303
- cwd_path: str | None = None,
304
- ) -> dict[str, object]:
305
- """Build the initial portable loop state for a PR head.
306
-
307
- Args:
308
- current_head: Current PR head SHA.
309
- entry_skill: ``pr-converge`` or ``autoconverge``.
310
- is_copilot_down: Copilot gate skipped for the whole run.
311
- is_bugbot_down: Bugbot gate skipped for the whole run.
312
- owner: Repository owner.
313
- repo: Repository name.
314
- pr_number: Pull request number.
315
- is_codex_down: Codex gate skipped (disabled or opted out).
316
- is_codex_required: Codex is required by usage/policy for this run.
317
- session_model: Alias passed to invoke_code_review on re-entry.
318
- cwd_path: PR worktree path for invoke_code_review --cwd.
319
-
320
- Returns:
321
- New state mapping at ``CODE_REVIEW``.
322
- """
323
- return {
324
- "phase": PHASE_CODE_REVIEW,
325
- "tick_count": 0,
326
- "bugbot_clean_at": None,
327
- "code_review_clean_at": None,
328
- "bugteam_clean_at": None,
329
- "copilot_clean_at": None,
330
- STATE_KEY_CODEX_CLEAN_AT: None,
331
- "merge_state_status": None,
332
- "current_head": current_head,
333
- "copilot_wait_count": 0,
334
- "bugbot_wait_count": 0,
335
- "copilot_down": is_copilot_down,
336
- "bugbot_down": is_bugbot_down,
337
- STATE_KEY_CODEX_DOWN: is_codex_down,
338
- STATE_KEY_CODEX_REQUIRED: is_codex_required,
339
- "inline_lag_streak": 0,
340
- "pacer": PACER_PORTABLE,
341
- "entry_skill": entry_skill,
342
- "owner": owner,
343
- "repo": repo,
344
- "pr_number": pr_number,
345
- "blocker": None,
346
- STATE_KEY_PENDING_NEXT: NEXT_RUN_CODE_REVIEW,
347
- STATE_KEY_SESSION_MODEL: session_model,
348
- STATE_KEY_CWD: cwd_path,
349
- }
350
-
351
-
352
- def _reset_push_invalidated_markers(all_state: dict[str, object]) -> None:
353
- all_state["bugbot_clean_at"] = None
354
- all_state["code_review_clean_at"] = None
355
- all_state["bugteam_clean_at"] = None
356
- all_state["copilot_clean_at"] = None
357
- all_state[STATE_KEY_CODEX_CLEAN_AT] = None
358
- all_state["merge_state_status"] = None
359
- all_state["copilot_wait_count"] = 0
360
- all_state["bugbot_wait_count"] = 0
361
- all_state["inline_lag_streak"] = 0
362
- all_state["bugbot_down"] = False
363
- all_state[STATE_KEY_CODEX_DOWN] = False
364
-
365
-
366
- def _sync_task_list_from_down_flags(all_state: dict[str, object]) -> None:
367
- all_task_list = build_converge_task_list(
368
- is_bugbot_down=bool(all_state.get("bugbot_down")),
369
- is_copilot_down=bool(all_state.get("copilot_down")),
370
- is_codex_down=bool(all_state.get(STATE_KEY_CODEX_DOWN)),
371
- is_codex_required=bool(all_state.get(STATE_KEY_CODEX_REQUIRED)),
372
- )
373
- all_state["tasks"] = all_task_list["tasks"]
374
- all_state["final_task_id"] = all_task_list["final_task_id"]
375
- all_state["runnable_review_ids"] = all_task_list["runnable_review_ids"]
376
- all_state["done_when"] = all_task_list["done_when"]
377
-
378
-
379
- def probe_weekly_usage_requires_codex() -> bool:
380
- """Return whether live weekly Codex usage requires a review stamp.
381
-
382
- Matches ``check_convergence``: unknown percent and probe failures never
383
- require a review. A known percent above the shared threshold does.
384
-
385
- Returns:
386
- True only when the weekly probe exits successfully with a percent
387
- that ``is_codex_review_required`` accepts as required.
388
- """
389
- try:
390
- usage_report = probe_weekly_usage_via_subprocess()
391
- except (
392
- FileNotFoundError,
393
- OSError,
394
- subprocess.TimeoutExpired,
395
- subprocess.SubprocessError,
396
- json.JSONDecodeError,
397
- ValueError,
398
- TypeError,
399
- KeyError,
400
- ):
401
- return False
402
- raw_percent = usage_report.get(USAGE_REPORT_KEY_PERCENT_LEFT)
403
- if isinstance(raw_percent, bool):
404
- return False
405
- if isinstance(raw_percent, (int, float)):
406
- return is_codex_review_required(float(raw_percent))
407
- return False
408
-
409
-
410
- def resolve_codex_required_flag(
411
- *,
412
- is_codex_down: bool,
413
- is_codex_required: bool,
414
- ) -> bool:
415
- """Resolve whether Codex is a runnable review for this run.
416
-
417
- Args:
418
- is_codex_down: Codex gate skipped (disabled or opted out).
419
- is_codex_required: CLI force-on (``--codex-required 1``).
420
-
421
- Returns:
422
- False when Codex is down; otherwise the CLI force-on flag or the
423
- live weekly usage probe (same rule as ``check_convergence``).
424
- """
425
- if is_codex_down:
426
- return False
427
- if is_codex_required:
428
- return True
429
- return probe_weekly_usage_requires_codex()
430
-
431
-
432
- def _ensure_codex_required_matches_usage(all_state: dict[str, object]) -> None:
433
- """Promote ``codex_required`` when live usage requires a stamp.
434
-
435
- Args:
436
- all_state: Mutable loop state; may set ``codex_required`` and refresh
437
- the task list when the probe requires Codex.
438
- """
439
- if all_state.get(STATE_KEY_CODEX_DOWN):
440
- return
441
- if all_state.get(STATE_KEY_CODEX_REQUIRED):
442
- return
443
- if not probe_weekly_usage_requires_codex():
444
- return
445
- all_state[STATE_KEY_CODEX_REQUIRED] = True
446
- _sync_task_list_from_down_flags(all_state)
447
-
448
-
449
- def _maybe_reset_on_head_change(
450
- all_state: dict[str, object], current_head: str
451
- ) -> None:
452
- prior_head = all_state.get("current_head")
453
- if (
454
- isinstance(prior_head, str)
455
- and prior_head
456
- and prior_head != current_head
457
- ):
458
- _reset_push_invalidated_markers(all_state)
459
- _sync_task_list_from_down_flags(all_state)
460
- all_state["current_head"] = current_head
461
-
462
-
463
- def _code_review_commands(
464
- *,
465
- cwd_path: Path,
466
- session_model: str,
467
- ) -> list[str]:
468
- invoke_path = _home_path(*INVOKE_CODE_REVIEW_RELATIVE_PATH.split("/"))
469
- return [
470
- "python",
471
- str(invoke_path),
472
- "--cwd",
473
- str(cwd_path),
474
- "--session-model",
475
- session_model,
476
- ]
477
-
478
-
479
- def _code_review_commands_from_state(
480
- all_state: Mapping[str, object],
481
- ) -> list[str]:
482
- stored_cwd = all_state.get(STATE_KEY_CWD)
483
- stored_session_model = all_state.get(STATE_KEY_SESSION_MODEL)
484
- if isinstance(stored_cwd, str) and stored_cwd:
485
- cwd_path = Path(stored_cwd)
486
- else:
487
- cwd_path = Path.cwd()
488
- if isinstance(stored_session_model, str) and stored_session_model:
489
- session_model = stored_session_model
490
- else:
491
- session_model = DEFAULT_SESSION_MODEL
492
- return _code_review_commands(
493
- cwd_path=cwd_path, session_model=session_model
494
- )
495
-
496
-
497
- def _mark_ready_commands(all_state: Mapping[str, object]) -> list[str]:
498
- owner = str(all_state["owner"])
499
- repo = str(all_state["repo"])
500
- return [
501
- MARK_READY_GH_BINARY,
502
- MARK_READY_PR_TOKEN,
503
- MARK_READY_SUBCOMMAND,
504
- str(all_state["pr_number"]),
505
- MARK_READY_REPO_FLAG,
506
- f"{owner}/{repo}",
507
- ]
508
-
509
-
510
- def _check_convergence_commands(
511
- *,
512
- owner: str,
513
- repo: str,
514
- pr_number: int,
515
- is_copilot_down: bool,
516
- is_bugbot_down: bool,
517
- is_codex_down: bool,
518
- codex_clean_at: str | None,
519
- ) -> list[str]:
520
- check_path = _home_path(*CHECK_CONVERGENCE_RELATIVE_PATH.split("/"))
521
- all_arguments = [
522
- "python",
523
- str(check_path),
524
- CHECK_CONVERGENCE_OWNER_FLAG,
525
- owner,
526
- CHECK_CONVERGENCE_REPO_FLAG,
527
- repo,
528
- CHECK_CONVERGENCE_PR_NUMBER_FLAG,
529
- str(pr_number),
530
- ]
531
- if is_copilot_down:
532
- all_arguments.append(CHECK_CONVERGENCE_COPILOT_DOWN_FLAG)
533
- if is_bugbot_down:
534
- all_arguments.append(CHECK_CONVERGENCE_BUGBOT_DOWN_FLAG)
535
- if is_codex_down:
536
- all_arguments.append(CHECK_CONVERGENCE_CODEX_DOWN_FLAG)
537
- if codex_clean_at:
538
- all_arguments.extend(
539
- [CHECK_CONVERGENCE_CODEX_CLEAN_AT_FLAG, codex_clean_at]
540
- )
541
- return all_arguments
542
-
543
-
544
- def _state_pr_number(all_state: Mapping[str, object]) -> int:
545
- return int(str(all_state["pr_number"]))
546
-
547
-
548
- def _codex_clean_at_or_none(all_state: Mapping[str, object]) -> str | None:
549
- maybe_stamp = all_state.get(STATE_KEY_CODEX_CLEAN_AT)
550
- if isinstance(maybe_stamp, str) and maybe_stamp:
551
- return maybe_stamp
552
- return None
553
-
554
-
555
- def _check_convergence_commands_from_state(
556
- all_state: Mapping[str, object],
557
- ) -> list[str]:
558
- return _check_convergence_commands(
559
- owner=str(all_state["owner"]),
560
- repo=str(all_state["repo"]),
561
- pr_number=_state_pr_number(all_state),
562
- is_copilot_down=bool(all_state.get("copilot_down")),
563
- is_bugbot_down=bool(all_state.get("bugbot_down")),
564
- is_codex_down=bool(all_state.get(STATE_KEY_CODEX_DOWN)),
565
- codex_clean_at=_codex_clean_at_or_none(all_state),
566
- )
567
-
568
-
569
- def _base_ok_payload(
570
- all_state: Mapping[str, object], state_file: Path
571
- ) -> dict[str, object]:
572
- return {
573
- RESULT_KEY_STATUS: STATUS_OK,
574
- RESULT_KEY_PHASE: all_state["phase"],
575
- RESULT_KEY_STATE_FILE: str(state_file),
576
- RESULT_KEY_CURRENT_HEAD: all_state.get("current_head"),
577
- RESULT_KEY_TICK_COUNT: all_state.get("tick_count"),
578
- RESULT_KEY_PACER: all_state.get("pacer", PACER_PORTABLE),
579
- RESULT_KEY_ENTRY_SKILL: all_state.get("entry_skill"),
580
- RESULT_KEY_OWNER: all_state.get("owner"),
581
- RESULT_KEY_REPO: all_state.get("repo"),
582
- RESULT_KEY_PR_NUMBER: all_state.get("pr_number"),
583
- RESULT_KEY_COPILOT_DOWN: bool(all_state.get("copilot_down")),
584
- RESULT_KEY_BUGBOT_DOWN: bool(all_state.get("bugbot_down")),
585
- RESULT_KEY_CODEX_DOWN: bool(all_state.get(STATE_KEY_CODEX_DOWN)),
586
- }
587
-
588
-
589
- def _store_pending_wait_seconds(
590
- all_state: dict[str, object],
591
- *,
592
- wait_seconds: int | None,
593
- ) -> None:
594
- if wait_seconds is None:
595
- all_state.pop(STATE_KEY_PENDING_WAIT_SECONDS, None)
596
- return
597
- all_state[STATE_KEY_PENDING_WAIT_SECONDS] = wait_seconds
598
-
599
-
600
- def _resolve_poll_wait_seconds(all_state: Mapping[str, object]) -> int:
601
- pending_wait_seconds = all_state.get(STATE_KEY_PENDING_WAIT_SECONDS)
602
- if isinstance(pending_wait_seconds, int):
603
- return pending_wait_seconds
604
- return DEFAULT_WAIT_SECONDS
605
-
606
-
607
- def _finish_ok(
608
- all_state: dict[str, object],
609
- state_file: Path,
610
- *,
611
- next_action: str,
612
- all_commands: list[str],
613
- wait_seconds: int | None,
614
- blocker: str | None,
615
- all_extra_fields: Mapping[str, object] | None,
616
- ) -> tuple[dict[str, object], int]:
617
- all_state[STATE_KEY_PENDING_NEXT] = next_action
618
- _store_pending_wait_seconds(all_state, wait_seconds=wait_seconds)
619
- resolved_commands = all_commands
620
- if next_action == NEXT_RUN_CODE_REVIEW:
621
- resolved_commands = _code_review_commands_from_state(all_state)
622
- save_state(state_file, all_state)
623
- all_payload = _base_ok_payload(all_state, state_file)
624
- all_payload[RESULT_KEY_NEXT] = next_action
625
- all_payload[RESULT_KEY_COMMANDS] = resolved_commands
626
- if wait_seconds is not None:
627
- all_payload[RESULT_KEY_WAIT_SECONDS] = wait_seconds
628
- if blocker is not None:
629
- all_payload[RESULT_KEY_BLOCKER] = blocker
630
- if all_extra_fields is not None:
631
- all_payload.update(dict(all_extra_fields))
632
- return all_payload, EXIT_SUCCESS
633
-
634
-
635
- def _run_preflight(
636
- *,
637
- cwd_path: Path,
638
- owner: str,
639
- repo: str,
640
- ) -> tuple[dict[str, object] | None, int]:
641
- preflight_script = _self_dir / PREFLIGHT_WORKTREE_SCRIPT_NAME
642
- preflight_process = subprocess.run(
643
- [
644
- sys.executable,
645
- str(preflight_script),
646
- OWNER_ARG_FLAG,
647
- owner,
648
- REPO_ARG_FLAG,
649
- repo,
650
- MODE_ARG_FLAG,
651
- MODE_STRICT,
652
- ],
653
- cwd=str(cwd_path),
654
- check=False,
655
- capture_output=True,
656
- text=True,
657
- )
658
- if preflight_process.returncode == 0:
659
- return None, EXIT_SUCCESS
660
- return (
661
- _error_payload(
662
- preflight_process.stdout.strip()
663
- or preflight_process.stderr.strip()
664
- or "preflight failed",
665
- blocker=BLOCKER_PREFLIGHT,
666
- ),
667
- EXIT_CONTRACT_ERROR,
668
- )
669
-
670
-
671
- def _read_git_head(
672
- cwd_path: Path,
673
- ) -> tuple[str | None, dict[str, object] | None, int]:
674
- head_process = subprocess.run(
675
- list(ALL_GIT_REV_PARSE_HEAD_ARGV),
676
- cwd=str(cwd_path),
677
- check=False,
678
- capture_output=True,
679
- text=True,
680
- )
681
- if head_process.returncode != 0:
682
- return (
683
- None,
684
- _error_payload(
685
- "git rev-parse HEAD failed", blocker=BLOCKER_PREFLIGHT
686
- ),
687
- EXIT_CONTRACT_ERROR,
688
- )
689
- return head_process.stdout.strip(), None, EXIT_SUCCESS
690
-
691
-
692
- def _seed_open_run_task_list_and_finish(
693
- *,
694
- current_head: str,
695
- entry_skill: str,
696
- is_copilot_down: bool,
697
- is_bugbot_down: bool,
698
- is_codex_down: bool,
699
- is_codex_required: bool,
700
- owner: str,
701
- repo: str,
702
- pr_number: int,
703
- state_file: Path,
704
- cwd_path: Path,
705
- session_model: str,
706
- ) -> tuple[dict[str, object], int]:
707
- all_task_list = build_converge_task_list(
708
- is_bugbot_down=is_bugbot_down,
709
- is_copilot_down=is_copilot_down,
710
- is_codex_down=is_codex_down,
711
- is_codex_required=is_codex_required,
712
- )
713
- all_state = build_initial_state(
714
- current_head=current_head,
715
- entry_skill=entry_skill,
716
- is_copilot_down=is_copilot_down,
717
- is_bugbot_down=is_bugbot_down,
718
- is_codex_down=is_codex_down,
719
- is_codex_required=is_codex_required,
720
- owner=owner,
721
- repo=repo,
722
- pr_number=pr_number,
723
- session_model=session_model,
724
- cwd_path=str(cwd_path),
725
- )
726
- all_state["tasks"] = all_task_list["tasks"]
727
- all_state["final_task_id"] = all_task_list["final_task_id"]
728
- all_state["runnable_review_ids"] = all_task_list["runnable_review_ids"]
729
- all_state["done_when"] = all_task_list["done_when"]
730
- return _finish_ok(
731
- all_state,
732
- state_file,
733
- next_action=NEXT_RUN_CODE_REVIEW,
734
- all_commands=_code_review_commands(
735
- cwd_path=cwd_path, session_model=session_model
736
- ),
737
- wait_seconds=None,
738
- blocker=None,
739
- all_extra_fields=all_task_list,
740
- )
741
-
742
-
743
- def run_open_run(
744
- *,
745
- entry_skill: str,
746
- has_workflow: bool,
747
- has_schedule_wakeup: bool,
748
- owner: str,
749
- repo: str,
750
- pr_number: int,
751
- cwd_path: Path,
752
- state_dir: Path,
753
- session_model: str,
754
- is_copilot_down: bool,
755
- is_bugbot_down: bool,
756
- is_codex_down: bool = False,
757
- is_codex_required: bool = False,
758
- ) -> tuple[dict[str, object], int]:
759
- """Select pacer, require portable, preflight worktree, seed state + tasks.
760
-
761
- Args:
762
- entry_skill: Converge entry skill name.
763
- has_workflow: Host exposes Workflow.
764
- has_schedule_wakeup: Host exposes ScheduleWakeup.
765
- owner: Repository owner.
766
- repo: Repository name.
767
- pr_number: Pull request number.
768
- cwd_path: PR worktree path.
769
- state_dir: Directory for ``pr-converge-state.json``.
770
- session_model: Alias passed to invoke_code_review.
771
- is_copilot_down: Skip Copilot as a runnable review.
772
- is_bugbot_down: Skip Bugbot as a runnable review.
773
- is_codex_down: Skip Codex as a runnable review.
774
- is_codex_required: Codex is required by usage/policy for this run.
775
-
776
- Returns:
777
- Payload and process exit code. Payload includes the scripted task list
778
- from ``build_converge_task_list.py``; final task is all runnable reviews
779
- CLEAN on the same HEAD.
780
- """
781
- selection = select_converge_pacer(
782
- entry_skill=entry_skill,
783
- has_workflow=has_workflow,
784
- has_schedule_wakeup=has_schedule_wakeup,
785
- )
786
- if selection.pacer != PACER_PORTABLE:
787
- return (
788
- _error_payload(
789
- "open-run is only for pacer=portable",
790
- blocker=BLOCKER_NOT_PORTABLE,
791
- pacer=selection.pacer,
792
- ),
793
- EXIT_CONTRACT_ERROR,
794
- )
795
- maybe_error, preflight_exit = _run_preflight(
796
- cwd_path=cwd_path, owner=owner, repo=repo
797
- )
798
- if maybe_error is not None:
799
- return maybe_error, preflight_exit
800
- current_head, maybe_head_error, head_exit = _read_git_head(cwd_path)
801
- if maybe_head_error is not None or current_head is None:
802
- return maybe_head_error or _error_payload(
803
- "git rev-parse HEAD failed", blocker=BLOCKER_PREFLIGHT
804
- ), head_exit
805
- state_dir.mkdir(parents=True, exist_ok=True)
806
- resolved_codex_required = resolve_codex_required_flag(
807
- is_codex_down=is_codex_down,
808
- is_codex_required=is_codex_required,
809
- )
810
- return _seed_open_run_task_list_and_finish(
811
- current_head=current_head,
812
- entry_skill=selection.entry_skill,
813
- is_copilot_down=is_copilot_down,
814
- is_bugbot_down=is_bugbot_down,
815
- is_codex_down=is_codex_down,
816
- is_codex_required=resolved_codex_required,
817
- owner=owner,
818
- repo=repo,
819
- pr_number=pr_number,
820
- state_file=state_dir / STATE_FILENAME,
821
- cwd_path=cwd_path,
822
- session_model=session_model,
823
- )
824
-
825
-
826
- def _is_successful_serve(*, returncode: int) -> bool:
827
- """Return True when code-review completed a successful serve.
828
-
829
- Args:
830
- returncode: Helper process exit code.
831
-
832
- Returns:
833
- Whether the serve counts as successful for phase advance.
834
- """
835
- return returncode == 0
836
-
837
-
838
- def run_after_code_review(
839
- *,
840
- state_file: Path,
841
- returncode: int,
842
- is_dirty_tree: bool,
843
- served_command: str,
844
- current_head: str,
845
- ) -> tuple[dict[str, object], int]:
846
- """Advance state from a code-review helper result.
847
-
848
- Args:
849
- state_file: Loop state path.
850
- returncode: Helper process exit code.
851
- is_dirty_tree: Working tree dirty after review.
852
- served_command: Informational only (empty, ``in_session``, or chain
853
- argv). Success is decided solely by ``returncode == 0``.
854
- current_head: SHA after the review step.
855
-
856
- Returns:
857
- Payload and process exit code.
858
- """
859
- all_state = load_state(state_file)
860
- _maybe_reset_on_head_change(all_state, current_head)
861
- all_state["tick_count"] = int(all_state.get("tick_count") or 0) + 1
862
-
863
- del served_command
864
- is_successful_serve = _is_successful_serve(returncode=returncode)
865
- if not is_successful_serve:
866
- all_state["phase"] = PHASE_CODE_REVIEW
867
- all_state["blocker"] = BLOCKER_REVIEW_FAILED
868
- return _finish_ok(
869
- all_state,
870
- state_file,
871
- next_action=NEXT_RUN_CODE_REVIEW,
872
- all_commands=_EMPTY_COMMANDS,
873
- wait_seconds=None,
874
- blocker=BLOCKER_REVIEW_FAILED,
875
- all_extra_fields=None,
876
- )
877
-
878
- if is_dirty_tree:
879
- all_state["phase"] = PHASE_CODE_REVIEW
880
- all_state["code_review_clean_at"] = None
881
- return _finish_ok(
882
- all_state,
883
- state_file,
884
- next_action=NEXT_APPLY_FIXES,
885
- all_commands=_EMPTY_COMMANDS,
886
- wait_seconds=None,
887
- blocker=None,
888
- all_extra_fields=None,
889
- )
890
-
891
- all_state["code_review_clean_at"] = current_head
892
- all_state["phase"] = PHASE_BUGTEAM
893
- all_state["blocker"] = None
894
- return _finish_ok(
895
- all_state,
896
- state_file,
897
- next_action=NEXT_RUN_BUGTEAM,
898
- all_commands=_EMPTY_COMMANDS,
899
- wait_seconds=None,
900
- blocker=None,
901
- all_extra_fields=None,
902
- )
903
-
904
-
905
- def _needs_codex_step(all_state: Mapping[str, object]) -> bool:
906
- if all_state.get(STATE_KEY_CODEX_DOWN):
907
- return False
908
- current_head = all_state.get("current_head")
909
- if (
910
- isinstance(current_head, str)
911
- and all_state.get(STATE_KEY_CODEX_CLEAN_AT) == current_head
912
- ):
913
- return False
914
- if all_state.get(STATE_KEY_CODEX_REQUIRED):
915
- return True
916
- return probe_weekly_usage_requires_codex()
917
-
918
-
919
- def _advance_after_codex_resolved(
920
- all_state: dict[str, object],
921
- state_file: Path,
922
- ) -> tuple[dict[str, object], int]:
923
- if all_state.get("copilot_down"):
924
- all_state["phase"] = PHASE_READY
925
- return _finish_ok(
926
- all_state,
927
- state_file,
928
- next_action=NEXT_CHECK_READY,
929
- all_commands=_check_convergence_commands_from_state(all_state),
930
- wait_seconds=None,
931
- blocker=None,
932
- all_extra_fields=None,
933
- )
934
- all_state["phase"] = PHASE_COPILOT_WAIT
935
- return _finish_ok(
936
- all_state,
937
- state_file,
938
- next_action=NEXT_REQUEST_COPILOT,
939
- all_commands=_EMPTY_COMMANDS,
940
- wait_seconds=None,
941
- blocker=None,
942
- all_extra_fields=None,
943
- )
944
-
945
-
946
- def _advance_after_bugbot_resolved(
947
- all_state: dict[str, object],
948
- state_file: Path,
949
- ) -> tuple[dict[str, object], int]:
950
- _ensure_codex_required_matches_usage(all_state)
951
- if _needs_codex_step(all_state):
952
- all_state["phase"] = PHASE_BUGBOT
953
- return _finish_ok(
954
- all_state,
955
- state_file,
956
- next_action=NEXT_RUN_CODEX,
957
- all_commands=_EMPTY_COMMANDS,
958
- wait_seconds=None,
959
- blocker=None,
960
- all_extra_fields=None,
961
- )
962
- return _advance_after_codex_resolved(all_state, state_file)
963
-
964
-
965
- def _after_bugteam_converged(
966
- all_state: dict[str, object],
967
- state_file: Path,
968
- current_head: str,
969
- ) -> tuple[dict[str, object], int]:
970
- all_state["bugteam_clean_at"] = current_head
971
- if not all_state.get("bugbot_down"):
972
- all_state["phase"] = PHASE_BUGBOT
973
- return _finish_ok(
974
- all_state,
975
- state_file,
976
- next_action=NEXT_RUN_BUGBOT_GATE,
977
- all_commands=_EMPTY_COMMANDS,
978
- wait_seconds=None,
979
- blocker=None,
980
- all_extra_fields=None,
981
- )
982
- return _advance_after_bugbot_resolved(all_state, state_file)
983
-
984
-
985
- def run_after_bugteam(
986
- *,
987
- state_file: Path,
988
- is_pushed: bool,
989
- is_converged: bool,
990
- current_head: str,
991
- ) -> tuple[dict[str, object], int]:
992
- """Advance state from a bugteam outcome.
993
-
994
- Args:
995
- state_file: Loop state path.
996
- is_pushed: Bugteam pushed commits.
997
- is_converged: Bugteam reported clean with no push.
998
- current_head: SHA after bugteam.
999
-
1000
- Returns:
1001
- Payload and process exit code.
1002
- """
1003
- all_state = load_state(state_file)
1004
- _maybe_reset_on_head_change(all_state, current_head)
1005
- all_state["tick_count"] = int(all_state.get("tick_count") or 0) + 1
1006
-
1007
- if is_pushed:
1008
- _reset_push_invalidated_markers(all_state)
1009
- _sync_task_list_from_down_flags(all_state)
1010
- all_state["phase"] = PHASE_CODE_REVIEW
1011
- return _finish_ok(
1012
- all_state,
1013
- state_file,
1014
- next_action=NEXT_RUN_CODE_REVIEW,
1015
- all_commands=_EMPTY_COMMANDS,
1016
- wait_seconds=None,
1017
- blocker=None,
1018
- all_extra_fields=None,
1019
- )
1020
-
1021
- if is_converged:
1022
- return _after_bugteam_converged(all_state, state_file, current_head)
1023
-
1024
- all_state["phase"] = PHASE_CODE_REVIEW
1025
- return _finish_ok(
1026
- all_state,
1027
- state_file,
1028
- next_action=NEXT_APPLY_FIXES,
1029
- all_commands=_EMPTY_COMMANDS,
1030
- wait_seconds=None,
1031
- blocker=None,
1032
- all_extra_fields=None,
1033
- )
1034
-
1035
-
1036
- def _after_bugbot_down(
1037
- all_state: dict[str, object],
1038
- state_file: Path,
1039
- ) -> tuple[dict[str, object], int]:
1040
- all_state["bugbot_down"] = True
1041
- _sync_task_list_from_down_flags(all_state)
1042
- return _advance_after_bugbot_resolved(all_state, state_file)
1043
-
1044
-
1045
- def _after_bugbot_dirty(
1046
- all_state: dict[str, object],
1047
- state_file: Path,
1048
- *,
1049
- is_inline_lag: bool,
1050
- ) -> tuple[dict[str, object], int]:
1051
- if not is_inline_lag:
1052
- all_state["inline_lag_streak"] = 0
1053
- all_state["phase"] = PHASE_CODE_REVIEW
1054
- return _finish_ok(
1055
- all_state,
1056
- state_file,
1057
- next_action=NEXT_APPLY_FIXES,
1058
- all_commands=_EMPTY_COMMANDS,
1059
- wait_seconds=None,
1060
- blocker=None,
1061
- all_extra_fields=None,
1062
- )
1063
- streak = int(all_state.get("inline_lag_streak") or 0) + 1
1064
- all_state["inline_lag_streak"] = streak
1065
- if streak >= INLINE_LAG_STREAK_CAP:
1066
- all_state["phase"] = PHASE_BLOCKED
1067
- all_state["blocker"] = BLOCKER_INLINE_LAG_CAP
1068
- return _finish_ok(
1069
- all_state,
1070
- state_file,
1071
- next_action=NEXT_STOP_BLOCKED,
1072
- all_commands=_EMPTY_COMMANDS,
1073
- wait_seconds=None,
1074
- blocker=BLOCKER_INLINE_LAG_CAP,
1075
- all_extra_fields=None,
1076
- )
1077
- all_state["phase"] = PHASE_BUGBOT
1078
- return _finish_ok(
1079
- all_state,
1080
- state_file,
1081
- next_action=NEXT_POLL_WAIT,
1082
- all_commands=_EMPTY_COMMANDS,
1083
- wait_seconds=BUGBOT_INLINE_LAG_WAIT_SECONDS,
1084
- blocker=None,
1085
- all_extra_fields=None,
1086
- )
1087
-
1088
-
1089
- def _after_bugbot_clean(
1090
- all_state: dict[str, object],
1091
- state_file: Path,
1092
- current_head: str,
1093
- ) -> tuple[dict[str, object], int]:
1094
- all_state["bugbot_clean_at"] = current_head
1095
- all_state["inline_lag_streak"] = 0
1096
- return _advance_after_bugbot_resolved(all_state, state_file)
1097
-
1098
-
1099
- def run_after_codex(
1100
- *,
1101
- state_file: Path,
1102
- classification: str,
1103
- current_head: str,
1104
- ) -> tuple[dict[str, object], int]:
1105
- """Advance state from a Codex review step outcome.
1106
-
1107
- Args:
1108
- state_file: Loop state path.
1109
- classification: clean, dirty, or down.
1110
- current_head: Current PR head SHA.
1111
-
1112
- Returns:
1113
- Payload and process exit code.
1114
- """
1115
- if classification not in ALL_CODEX_CLASSIFICATIONS:
1116
- return (
1117
- _error_payload(f"unknown classification: {classification!r}"),
1118
- EXIT_USAGE_ERROR,
1119
- )
1120
- all_state = load_state(state_file)
1121
- _maybe_reset_on_head_change(all_state, current_head)
1122
- all_state["tick_count"] = int(all_state.get("tick_count") or 0) + 1
1123
-
1124
- if classification == CLASSIFICATION_DIRTY:
1125
- _reset_push_invalidated_markers(all_state)
1126
- _sync_task_list_from_down_flags(all_state)
1127
- all_state["phase"] = PHASE_CODE_REVIEW
1128
- return _finish_ok(
1129
- all_state,
1130
- state_file,
1131
- next_action=NEXT_APPLY_FIXES,
1132
- all_commands=_EMPTY_COMMANDS,
1133
- wait_seconds=None,
1134
- blocker=None,
1135
- all_extra_fields=None,
1136
- )
1137
-
1138
- if classification == CLASSIFICATION_DOWN:
1139
- all_state[STATE_KEY_CODEX_DOWN] = True
1140
- _sync_task_list_from_down_flags(all_state)
1141
- return _advance_after_codex_resolved(all_state, state_file)
1142
-
1143
- all_state[STATE_KEY_CODEX_CLEAN_AT] = current_head
1144
- return _advance_after_codex_resolved(all_state, state_file)
1145
-
1146
-
1147
- def run_after_bugbot(
1148
- *,
1149
- state_file: Path,
1150
- classification: str,
1151
- current_head: str,
1152
- is_inline_lag: bool,
1153
- ) -> tuple[dict[str, object], int]:
1154
- """Advance state from a Bugbot terminal-gate outcome.
1155
-
1156
- Args:
1157
- state_file: Loop state path.
1158
- classification: clean, dirty, absent, or down.
1159
- current_head: Current PR head SHA.
1160
- is_inline_lag: Body dirty but zero inline findings.
1161
-
1162
- Returns:
1163
- Payload and process exit code.
1164
- """
1165
- if classification not in ALL_CLASSIFICATIONS:
1166
- return (
1167
- _error_payload(f"unknown classification: {classification!r}"),
1168
- EXIT_USAGE_ERROR,
1169
- )
1170
- all_state = load_state(state_file)
1171
- _maybe_reset_on_head_change(all_state, current_head)
1172
- all_state["tick_count"] = int(all_state.get("tick_count") or 0) + 1
1173
-
1174
- if classification == CLASSIFICATION_DOWN:
1175
- return _after_bugbot_down(all_state, state_file)
1176
- if classification == CLASSIFICATION_DIRTY:
1177
- return _after_bugbot_dirty(
1178
- all_state, state_file, is_inline_lag=is_inline_lag
1179
- )
1180
- if classification == CLASSIFICATION_ABSENT:
1181
- wait_count = int(all_state.get("bugbot_wait_count") or 0) + 1
1182
- all_state["bugbot_wait_count"] = wait_count
1183
- if wait_count >= BUGBOT_ABSENT_WAIT_HARD_CAP:
1184
- all_state["phase"] = PHASE_BLOCKED
1185
- all_state["blocker"] = BLOCKER_BUGBOT_WAIT_CAP
1186
- return _finish_ok(
1187
- all_state,
1188
- state_file,
1189
- next_action=NEXT_STOP_BLOCKED,
1190
- all_commands=_EMPTY_COMMANDS,
1191
- wait_seconds=None,
1192
- blocker=BLOCKER_BUGBOT_WAIT_CAP,
1193
- all_extra_fields=None,
1194
- )
1195
- all_state["phase"] = PHASE_BUGBOT
1196
- return _finish_ok(
1197
- all_state,
1198
- state_file,
1199
- next_action=NEXT_POLL_WAIT,
1200
- all_commands=_EMPTY_COMMANDS,
1201
- wait_seconds=DEFAULT_WAIT_SECONDS,
1202
- blocker=None,
1203
- all_extra_fields=None,
1204
- )
1205
- return _after_bugbot_clean(all_state, state_file, current_head)
1206
-
1207
-
1208
- def _after_copilot_not_surfaced(
1209
- all_state: dict[str, object],
1210
- state_file: Path,
1211
- ) -> tuple[dict[str, object], int]:
1212
- wait_count = int(all_state.get("copilot_wait_count") or 0) + 1
1213
- all_state["copilot_wait_count"] = wait_count
1214
- if wait_count >= COPILOT_WAIT_HARD_CAP:
1215
- all_state["phase"] = PHASE_BLOCKED
1216
- all_state["blocker"] = BLOCKER_COPILOT_WAIT_CAP
1217
- return _finish_ok(
1218
- all_state,
1219
- state_file,
1220
- next_action=NEXT_STOP_BLOCKED,
1221
- all_commands=_EMPTY_COMMANDS,
1222
- wait_seconds=None,
1223
- blocker=BLOCKER_COPILOT_WAIT_CAP,
1224
- all_extra_fields=None,
1225
- )
1226
- all_state["phase"] = PHASE_COPILOT_WAIT
1227
- return _finish_ok(
1228
- all_state,
1229
- state_file,
1230
- next_action=NEXT_POLL_WAIT,
1231
- all_commands=_EMPTY_COMMANDS,
1232
- wait_seconds=DEFAULT_WAIT_SECONDS,
1233
- blocker=None,
1234
- all_extra_fields=None,
1235
- )
1236
-
1237
-
1238
- def _after_copilot_clean(
1239
- all_state: dict[str, object],
1240
- state_file: Path,
1241
- current_head: str,
1242
- ) -> tuple[dict[str, object], int]:
1243
- all_state["copilot_clean_at"] = current_head
1244
- all_state["phase"] = PHASE_READY
1245
- return _finish_ok(
1246
- all_state,
1247
- state_file,
1248
- next_action=NEXT_CHECK_READY,
1249
- all_commands=_check_convergence_commands_from_state(all_state),
1250
- wait_seconds=None,
1251
- blocker=None,
1252
- all_extra_fields=None,
1253
- )
1254
-
1255
-
1256
- def _after_copilot_down(
1257
- all_state: dict[str, object],
1258
- state_file: Path,
1259
- ) -> tuple[dict[str, object], int]:
1260
- all_state["copilot_down"] = True
1261
- _sync_task_list_from_down_flags(all_state)
1262
- all_state["phase"] = PHASE_READY
1263
- return _finish_ok(
1264
- all_state,
1265
- state_file,
1266
- next_action=NEXT_CHECK_READY,
1267
- all_commands=_check_convergence_commands_from_state(all_state),
1268
- wait_seconds=None,
1269
- blocker=None,
1270
- all_extra_fields=None,
1271
- )
1272
-
1273
-
1274
- def _after_copilot_surfaced(
1275
- all_state: dict[str, object],
1276
- state_file: Path,
1277
- *,
1278
- classification: str,
1279
- current_head: str,
1280
- ) -> tuple[dict[str, object], int]:
1281
- if classification not in ALL_COPILOT_CLASSIFICATIONS:
1282
- return (
1283
- _error_payload(f"unknown classification: {classification!r}"),
1284
- EXIT_USAGE_ERROR,
1285
- )
1286
- if classification == CLASSIFICATION_DIRTY:
1287
- all_state["copilot_wait_count"] = 0
1288
- all_state["phase"] = PHASE_CODE_REVIEW
1289
- return _finish_ok(
1290
- all_state,
1291
- state_file,
1292
- next_action=NEXT_APPLY_FIXES,
1293
- all_commands=_EMPTY_COMMANDS,
1294
- wait_seconds=None,
1295
- blocker=None,
1296
- all_extra_fields=None,
1297
- )
1298
- if classification == CLASSIFICATION_ABSENT:
1299
- return _after_copilot_not_surfaced(all_state, state_file)
1300
- if classification == CLASSIFICATION_DOWN:
1301
- return _after_copilot_down(all_state, state_file)
1302
- return _after_copilot_clean(all_state, state_file, current_head)
1303
-
1304
-
1305
- def run_after_copilot_wait(
1306
- *,
1307
- state_file: Path,
1308
- is_review_surfaced: bool,
1309
- classification: str,
1310
- current_head: str,
1311
- ) -> tuple[dict[str, object], int]:
1312
- """Advance state from a Copilot wait poll.
1313
-
1314
- Args:
1315
- state_file: Loop state path.
1316
- is_review_surfaced: A Copilot review exists at current_head.
1317
- classification: clean, dirty, absent, or down when surfaced.
1318
- current_head: Current PR head SHA.
1319
-
1320
- Returns:
1321
- Payload and process exit code.
1322
- """
1323
- all_state = load_state(state_file)
1324
- _maybe_reset_on_head_change(all_state, current_head)
1325
- all_state["tick_count"] = int(all_state.get("tick_count") or 0) + 1
1326
- if not is_review_surfaced:
1327
- return _after_copilot_not_surfaced(all_state, state_file)
1328
- return _after_copilot_surfaced(
1329
- all_state,
1330
- state_file,
1331
- classification=classification,
1332
- current_head=current_head,
1333
- )
1334
-
1335
-
1336
- def run_after_ready_check(
1337
- *,
1338
- state_file: Path,
1339
- check_exit: int,
1340
- current_head: str,
1341
- ) -> tuple[dict[str, object], int]:
1342
- """Advance state from check_convergence exit code.
1343
-
1344
- Args:
1345
- state_file: Loop state path.
1346
- check_exit: Process exit from check_convergence.py.
1347
- current_head: Current PR head SHA.
1348
-
1349
- Returns:
1350
- Payload and process exit code.
1351
- """
1352
- all_state = load_state(state_file)
1353
- _maybe_reset_on_head_change(all_state, current_head)
1354
- all_state["tick_count"] = int(all_state.get("tick_count") or 0) + 1
1355
- if check_exit == 0:
1356
- all_state["phase"] = PHASE_READY
1357
- all_state["blocker"] = None
1358
- return _finish_ok(
1359
- all_state,
1360
- state_file,
1361
- next_action=NEXT_MARK_READY,
1362
- all_commands=_mark_ready_commands(all_state),
1363
- wait_seconds=None,
1364
- blocker=None,
1365
- all_extra_fields=None,
1366
- )
1367
-
1368
- if check_exit == 1:
1369
- _ensure_codex_required_matches_usage(all_state)
1370
- if _needs_codex_step(all_state):
1371
- all_state["phase"] = PHASE_BUGBOT
1372
- return _finish_ok(
1373
- all_state,
1374
- state_file,
1375
- next_action=NEXT_RUN_CODEX,
1376
- all_commands=_EMPTY_COMMANDS,
1377
- wait_seconds=None,
1378
- blocker=None,
1379
- all_extra_fields=None,
1380
- )
1381
- all_state["phase"] = PHASE_CODE_REVIEW
1382
- return _finish_ok(
1383
- all_state,
1384
- state_file,
1385
- next_action=NEXT_APPLY_FIXES,
1386
- all_commands=_EMPTY_COMMANDS,
1387
- wait_seconds=None,
1388
- blocker=None,
1389
- all_extra_fields=None,
1390
- )
1391
-
1392
- all_state["phase"] = PHASE_BLOCKED
1393
- all_state["blocker"] = f"check_convergence_exit_{check_exit}"
1394
- return _finish_ok(
1395
- all_state,
1396
- state_file,
1397
- next_action=NEXT_STOP_BLOCKED,
1398
- all_commands=_EMPTY_COMMANDS,
1399
- wait_seconds=None,
1400
- blocker=str(all_state["blocker"]),
1401
- all_extra_fields=None,
1402
- )
1403
-
1404
-
1405
- def _register_open_run_arguments(
1406
- open_run_parser: argparse.ArgumentParser,
1407
- ) -> None:
1408
- open_run_parser.add_argument(CLI_SKILL_FLAG, required=True)
1409
- open_run_parser.add_argument(CLI_HAS_WORKFLOW_FLAG, required=True)
1410
- open_run_parser.add_argument(CLI_HAS_SCHEDULE_WAKEUP_FLAG, required=True)
1411
- open_run_parser.add_argument(CLI_OWNER_FLAG, required=True)
1412
- open_run_parser.add_argument(CLI_REPO_FLAG, required=True)
1413
- open_run_parser.add_argument(CLI_PR_NUMBER_FLAG, required=True, type=int)
1414
- open_run_parser.add_argument(CLI_CWD_FLAG, required=True)
1415
- open_run_parser.add_argument(CLI_STATE_DIR_FLAG, required=True)
1416
- open_run_parser.add_argument(
1417
- CLI_SESSION_MODEL_FLAG, default=DEFAULT_SESSION_MODEL
1418
- )
1419
- open_run_parser.add_argument(CLI_COPILOT_DOWN_FLAG, default="0")
1420
- open_run_parser.add_argument(CLI_BUGBOT_DOWN_FLAG, default="0")
1421
- open_run_parser.add_argument(CLI_CODEX_DOWN_FLAG, default="0")
1422
- open_run_parser.add_argument(CLI_CODEX_REQUIRED_FLAG, default="0")
1423
-
1424
-
1425
- def build_argument_parser() -> argparse.ArgumentParser:
1426
- """Build the multi-command CLI parser.
1427
-
1428
- Returns:
1429
- Configured argument parser.
1430
- """
1431
- parser = argparse.ArgumentParser(description=CLI_DESCRIPTION)
1432
- all_subparsers = parser.add_subparsers(dest="command", required=True)
1433
-
1434
- open_run_parser = all_subparsers.add_parser(COMMAND_OPEN_RUN)
1435
- _register_open_run_arguments(open_run_parser)
1436
-
1437
- after_review_parser = all_subparsers.add_parser(COMMAND_AFTER_CODE_REVIEW)
1438
- after_review_parser.add_argument(CLI_STATE_FILE_FLAG, required=True)
1439
- after_review_parser.add_argument(
1440
- CLI_RETURNCODE_FLAG, required=True, type=int
1441
- )
1442
- after_review_parser.add_argument(CLI_DIRTY_TREE_FLAG, required=True)
1443
- after_review_parser.add_argument(CLI_SERVED_COMMAND_FLAG, default="")
1444
- after_review_parser.add_argument(CLI_CURRENT_HEAD_FLAG, required=True)
1445
-
1446
- after_bugteam_parser = all_subparsers.add_parser(COMMAND_AFTER_BUGTEAM)
1447
- after_bugteam_parser.add_argument(CLI_STATE_FILE_FLAG, required=True)
1448
- after_bugteam_parser.add_argument(CLI_PUSHED_FLAG, required=True)
1449
- after_bugteam_parser.add_argument(CLI_CONVERGED_FLAG, required=True)
1450
- after_bugteam_parser.add_argument(CLI_CURRENT_HEAD_FLAG, required=True)
1451
-
1452
- after_bugbot_parser = all_subparsers.add_parser(COMMAND_AFTER_BUGBOT)
1453
- after_bugbot_parser.add_argument(CLI_STATE_FILE_FLAG, required=True)
1454
- after_bugbot_parser.add_argument(CLI_CLASSIFICATION_FLAG, required=True)
1455
- after_bugbot_parser.add_argument(CLI_CURRENT_HEAD_FLAG, required=True)
1456
- after_bugbot_parser.add_argument(CLI_INLINE_LAG_FLAG, default="0")
1457
-
1458
- after_codex_parser = all_subparsers.add_parser(COMMAND_AFTER_CODEX)
1459
- after_codex_parser.add_argument(CLI_STATE_FILE_FLAG, required=True)
1460
- after_codex_parser.add_argument(CLI_CLASSIFICATION_FLAG, required=True)
1461
- after_codex_parser.add_argument(CLI_CURRENT_HEAD_FLAG, required=True)
1462
-
1463
- after_copilot_parser = all_subparsers.add_parser(COMMAND_AFTER_COPILOT_WAIT)
1464
- after_copilot_parser.add_argument(CLI_STATE_FILE_FLAG, required=True)
1465
- after_copilot_parser.add_argument(CLI_REVIEW_SURFACED_FLAG, required=True)
1466
- after_copilot_parser.add_argument(
1467
- CLI_CLASSIFICATION_FLAG, default="absent"
1468
- )
1469
- after_copilot_parser.add_argument(CLI_CURRENT_HEAD_FLAG, required=True)
1470
-
1471
- after_ready_parser = all_subparsers.add_parser(COMMAND_AFTER_READY_CHECK)
1472
- after_ready_parser.add_argument(CLI_STATE_FILE_FLAG, required=True)
1473
- after_ready_parser.add_argument(
1474
- CLI_CHECK_CONVERGENCE_EXIT_FLAG, required=True, type=int
1475
- )
1476
- after_ready_parser.add_argument(CLI_CURRENT_HEAD_FLAG, required=True)
1477
-
1478
- show_parser = all_subparsers.add_parser(COMMAND_SHOW_STATE)
1479
- show_parser.add_argument(CLI_STATE_FILE_FLAG, required=True)
1480
-
1481
- return parser
1482
-
1483
-
1484
- def _dispatch_open_run(
1485
- parsed_arguments: argparse.Namespace,
1486
- ) -> tuple[dict[str, object], int]:
1487
- return run_open_run(
1488
- entry_skill=parsed_arguments.skill,
1489
- has_workflow=parse_bool_flag(parsed_arguments.has_workflow),
1490
- has_schedule_wakeup=parse_bool_flag(
1491
- parsed_arguments.has_schedule_wakeup
1492
- ),
1493
- owner=parsed_arguments.owner,
1494
- repo=parsed_arguments.repo,
1495
- pr_number=parsed_arguments.pr_number,
1496
- cwd_path=Path(parsed_arguments.cwd),
1497
- state_dir=Path(parsed_arguments.state_dir),
1498
- session_model=parsed_arguments.session_model,
1499
- is_copilot_down=parse_bool_flag(parsed_arguments.copilot_down),
1500
- is_bugbot_down=parse_bool_flag(parsed_arguments.bugbot_down),
1501
- is_codex_down=parse_bool_flag(parsed_arguments.codex_down),
1502
- is_codex_required=parse_bool_flag(parsed_arguments.codex_required),
1503
- )
1504
-
1505
-
1506
- def _dispatch_after_code_review(
1507
- parsed_arguments: argparse.Namespace,
1508
- ) -> tuple[dict[str, object], int]:
1509
- return run_after_code_review(
1510
- state_file=Path(parsed_arguments.state_file),
1511
- returncode=parsed_arguments.returncode,
1512
- is_dirty_tree=parse_bool_flag(parsed_arguments.dirty_tree),
1513
- served_command=parsed_arguments.served_command or "",
1514
- current_head=parsed_arguments.current_head,
1515
- )
1516
-
1517
-
1518
- def _dispatch_after_bugteam(
1519
- parsed_arguments: argparse.Namespace,
1520
- ) -> tuple[dict[str, object], int]:
1521
- return run_after_bugteam(
1522
- state_file=Path(parsed_arguments.state_file),
1523
- is_pushed=parse_bool_flag(parsed_arguments.pushed),
1524
- is_converged=parse_bool_flag(parsed_arguments.converged),
1525
- current_head=parsed_arguments.current_head,
1526
- )
1527
-
1528
-
1529
- def _dispatch_after_bugbot(
1530
- parsed_arguments: argparse.Namespace,
1531
- ) -> tuple[dict[str, object], int]:
1532
- return run_after_bugbot(
1533
- state_file=Path(parsed_arguments.state_file),
1534
- classification=parsed_arguments.classification,
1535
- current_head=parsed_arguments.current_head,
1536
- is_inline_lag=parse_bool_flag(parsed_arguments.inline_lag),
1537
- )
1538
-
1539
-
1540
- def _dispatch_after_codex(
1541
- parsed_arguments: argparse.Namespace,
1542
- ) -> tuple[dict[str, object], int]:
1543
- return run_after_codex(
1544
- state_file=Path(parsed_arguments.state_file),
1545
- classification=parsed_arguments.classification,
1546
- current_head=parsed_arguments.current_head,
1547
- )
1548
-
1549
-
1550
- def _dispatch_after_copilot_wait(
1551
- parsed_arguments: argparse.Namespace,
1552
- ) -> tuple[dict[str, object], int]:
1553
- return run_after_copilot_wait(
1554
- state_file=Path(parsed_arguments.state_file),
1555
- is_review_surfaced=parse_bool_flag(parsed_arguments.review_surfaced),
1556
- classification=parsed_arguments.classification,
1557
- current_head=parsed_arguments.current_head,
1558
- )
1559
-
1560
-
1561
- def _dispatch_after_ready_check(
1562
- parsed_arguments: argparse.Namespace,
1563
- ) -> tuple[dict[str, object], int]:
1564
- return run_after_ready_check(
1565
- state_file=Path(parsed_arguments.state_file),
1566
- check_exit=parsed_arguments.check_exit,
1567
- current_head=parsed_arguments.current_head,
1568
- )
1569
-
1570
-
1571
- def _resolve_show_state_next(all_state: Mapping[str, object]) -> str:
1572
- pending_next = all_state.get(STATE_KEY_PENDING_NEXT)
1573
- if isinstance(pending_next, str) and pending_next:
1574
- return pending_next
1575
- if all_state.get("phase") == PHASE_READY:
1576
- return NEXT_MARK_READY
1577
- if all_state.get("phase") == PHASE_BLOCKED:
1578
- return NEXT_STOP_BLOCKED
1579
- return str(all_state.get("phase"))
1580
-
1581
-
1582
- def _dispatch_show_state(
1583
- parsed_arguments: argparse.Namespace,
1584
- ) -> tuple[dict[str, object], int]:
1585
- state_file = Path(parsed_arguments.state_file)
1586
- all_state = load_state(state_file)
1587
- all_payload = _base_ok_payload(all_state, state_file)
1588
- next_action = _resolve_show_state_next(all_state)
1589
- all_payload[RESULT_KEY_NEXT] = next_action
1590
- if next_action == NEXT_RUN_CODE_REVIEW:
1591
- all_payload[RESULT_KEY_COMMANDS] = _code_review_commands_from_state(
1592
- all_state
1593
- )
1594
- elif next_action == NEXT_CHECK_READY:
1595
- all_payload[RESULT_KEY_COMMANDS] = (
1596
- _check_convergence_commands_from_state(all_state)
1597
- )
1598
- elif next_action == NEXT_MARK_READY:
1599
- all_payload[RESULT_KEY_COMMANDS] = _mark_ready_commands(all_state)
1600
- elif next_action == NEXT_POLL_WAIT:
1601
- all_payload[RESULT_KEY_WAIT_SECONDS] = _resolve_poll_wait_seconds(
1602
- all_state
1603
- )
1604
- return all_payload, EXIT_SUCCESS
1605
-
1606
-
1607
- def main(all_argv: list[str]) -> int:
1608
- """CLI entry: print one JSON control payload on stdout.
1609
-
1610
- Args:
1611
- all_argv: Argument vector without program name.
1612
-
1613
- Returns:
1614
- Process exit code.
1615
- """
1616
- parser = build_argument_parser()
1617
- parsed_arguments = parser.parse_args(all_argv)
1618
- dispatch_by_command = {
1619
- COMMAND_OPEN_RUN: _dispatch_open_run,
1620
- COMMAND_AFTER_CODE_REVIEW: _dispatch_after_code_review,
1621
- COMMAND_AFTER_BUGTEAM: _dispatch_after_bugteam,
1622
- COMMAND_AFTER_BUGBOT: _dispatch_after_bugbot,
1623
- COMMAND_AFTER_CODEX: _dispatch_after_codex,
1624
- COMMAND_AFTER_COPILOT_WAIT: _dispatch_after_copilot_wait,
1625
- COMMAND_AFTER_READY_CHECK: _dispatch_after_ready_check,
1626
- COMMAND_SHOW_STATE: _dispatch_show_state,
1627
- }
1628
- try:
1629
- maybe_dispatch = dispatch_by_command.get(parsed_arguments.command)
1630
- if maybe_dispatch is None:
1631
- _emit(
1632
- _error_payload(
1633
- f"unknown command: {parsed_arguments.command!r}"
1634
- )
1635
- )
1636
- return EXIT_USAGE_ERROR
1637
- all_payload, exit_code = maybe_dispatch(parsed_arguments)
1638
- _emit(all_payload)
1639
- return exit_code
1640
- except ValueError as validation_error:
1641
- _emit(_error_payload(str(validation_error)))
1642
- return EXIT_USAGE_ERROR
1643
-
1644
-
1645
- if __name__ == "__main__":
1646
- raise SystemExit(main(sys.argv[1:]))