claude-dev-env 2.0.2 → 2.2.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 (110) hide show
  1. package/CLAUDE.md +1 -1
  2. package/hooks/blocking/CLAUDE.md +1 -0
  3. package/hooks/blocking/code_rules_shared.py +126 -47
  4. package/hooks/blocking/config/CLAUDE.md +2 -0
  5. package/hooks/blocking/config/verified_commit_context_constants.py +21 -0
  6. package/hooks/blocking/config/verified_commit_gate_output_constants.py +14 -0
  7. package/hooks/blocking/conftest.py +36 -30
  8. package/hooks/blocking/convergence_gate_blocker.py +76 -8
  9. package/hooks/blocking/plain_language_blocker.py +8 -0
  10. package/hooks/blocking/state_description_blocker.py +4 -1
  11. package/hooks/blocking/test_code_rules_shared.py +120 -20
  12. package/hooks/blocking/test_convergence_gate_blocker.py +146 -0
  13. package/hooks/blocking/test_plain_language_blocker.py +15 -0
  14. package/hooks/blocking/test_state_description_blocker.py +15 -0
  15. package/hooks/blocking/test_verified_commit_config_bootstrap.py +18 -0
  16. package/hooks/blocking/test_verified_commit_gate_additional_context.py +134 -0
  17. package/hooks/blocking/tests/test_verified_commit_gate.py +41 -0
  18. package/hooks/blocking/verified_commit_config_bootstrap.py +22 -10
  19. package/hooks/blocking/verified_commit_gate.py +113 -568
  20. package/hooks/blocking/verified_commit_gate_parts/CLAUDE.md +28 -0
  21. package/hooks/blocking/verified_commit_gate_parts/__init__.py +1 -0
  22. package/hooks/blocking/verified_commit_gate_parts/command_tokenization.py +174 -0
  23. package/hooks/blocking/verified_commit_gate_parts/deny_payload.py +53 -0
  24. package/hooks/blocking/verified_commit_gate_parts/deny_reason.py +80 -0
  25. package/hooks/blocking/verified_commit_gate_parts/directory_resolution.py +170 -0
  26. package/hooks/blocking/verified_commit_gate_parts/gated_invocations.py +205 -0
  27. package/hooks/blocking/verified_commit_gate_parts/tests/conftest.py +10 -0
  28. package/hooks/blocking/verified_commit_gate_parts/tests/test_command_tokenization.py +94 -0
  29. package/hooks/blocking/verified_commit_gate_parts/tests/test_deny_payload.py +17 -0
  30. package/hooks/blocking/verified_commit_gate_parts/tests/test_deny_reason.py +38 -0
  31. package/hooks/blocking/verified_commit_gate_parts/tests/test_directory_resolution.py +71 -0
  32. package/hooks/blocking/verified_commit_gate_parts/tests/test_gated_invocations.py +61 -0
  33. package/hooks/hooks_constants/CLAUDE.md +4 -1
  34. package/hooks/hooks_constants/code_rules_path_utils_constants.py +1 -0
  35. package/hooks/hooks_constants/convergence_gate_blocker_constants.py +36 -0
  36. package/hooks/hooks_constants/harness_scratchpad_constants.py +10 -9
  37. package/hooks/hooks_constants/mypy_integration_constants.py +16 -0
  38. package/hooks/validators/mypy_integration.py +145 -24
  39. package/hooks/validators/python_antipattern_checks.py +16 -0
  40. package/hooks/validators/run_all_validators.py +9 -3
  41. package/hooks/validators/test_mypy_integration.py +154 -0
  42. package/hooks/validators/test_python_antipattern_checks.py +112 -1
  43. package/hooks/validators/test_run_all_validators_pretooluse.py +16 -0
  44. package/package.json +1 -1
  45. package/rules/CLAUDE.md +1 -0
  46. package/rules/verified-commit-gate-skip.md +28 -0
  47. package/skills/_shared/pr-loop/CLAUDE.md +18 -13
  48. package/skills/_shared/pr-loop/portable-driver.md +150 -0
  49. package/skills/_shared/pr-loop/scripts/CLAUDE.md +3 -0
  50. package/skills/_shared/pr-loop/scripts/build_converge_task_list.py +310 -0
  51. package/skills/_shared/pr-loop/scripts/portable_converge_driver.py +1637 -0
  52. package/skills/_shared/pr-loop/scripts/select_converge_pacer.py +215 -0
  53. package/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/CLAUDE.md +3 -0
  54. package/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/converge_task_list_constants.py +56 -0
  55. package/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/pacer_constants.py +47 -0
  56. package/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/portable_driver_constants.py +181 -0
  57. package/skills/_shared/pr-loop/scripts/test_build_converge_task_list.py +140 -0
  58. package/skills/_shared/pr-loop/scripts/test_portable_converge_driver.py +1119 -0
  59. package/skills/_shared/pr-loop/scripts/test_select_converge_pacer.py +207 -0
  60. package/skills/auditing-claude-config/CLAUDE.md +2 -1
  61. package/skills/auditing-claude-config/SKILL.md +114 -176
  62. package/skills/auditing-claude-config/reference/probe-hook.md +74 -0
  63. package/skills/autoconverge/CLAUDE.md +6 -3
  64. package/skills/autoconverge/SKILL.md +421 -346
  65. package/skills/autoconverge/reference/CLAUDE.md +1 -0
  66. package/skills/autoconverge/reference/convergence.md +5 -5
  67. package/skills/autoconverge/reference/copilot-findings.md +51 -0
  68. package/skills/autoconverge/reference/multi-pr.md +33 -2
  69. package/skills/autoconverge/reference/stop-conditions.md +9 -6
  70. package/skills/autoconverge/test_portable_pacer_gate.py +54 -0
  71. package/skills/closeout/SKILL.md +7 -9
  72. package/skills/copilot-finding-triage/SKILL.md +21 -11
  73. package/skills/copilot-review/CLAUDE.md +3 -2
  74. package/skills/copilot-review/SKILL.md +119 -155
  75. package/skills/copilot-review/templates/subagent-prompt.md +49 -0
  76. package/skills/fresh-branch/CLAUDE.md +6 -9
  77. package/skills/fresh-branch/SKILL.md +84 -33
  78. package/skills/fresh-branch/scripts/create_fresh_branch.py +445 -0
  79. package/skills/fresh-branch/scripts/fresh_branch_scripts_constants/__init__.py +1 -0
  80. package/skills/fresh-branch/scripts/fresh_branch_scripts_constants/fresh_branch_cli_constants.py +74 -0
  81. package/skills/fresh-branch/scripts/test_create_fresh_branch.py +670 -0
  82. package/skills/pr-converge/CLAUDE.md +4 -3
  83. package/skills/pr-converge/SKILL.md +469 -422
  84. package/skills/pr-converge/reference/CLAUDE.md +1 -0
  85. package/skills/pr-converge/reference/multi-pr-orchestration.md +5 -1
  86. package/skills/pr-converge/reference/per-tick.md +51 -24
  87. package/skills/pr-converge/reference/progress-checklist.md +168 -0
  88. package/skills/pr-converge/test_portable_pacer_gate.py +67 -0
  89. package/skills/pr-converge/test_step5_host_branch.py +8 -6
  90. package/skills/pr-converge/workflows/schedule-wakeup-loop.md +5 -3
  91. package/skills/pr-loop-cloud-transport/SKILL.md +1 -1
  92. package/skills/privacy-hygiene/SKILL.md +68 -115
  93. package/skills/privacy-hygiene/reference/sweep-procedure.md +62 -0
  94. package/skills/session-log/CLAUDE.md +2 -1
  95. package/skills/session-log/SKILL.md +4 -26
  96. package/skills/session-log/templates/frontmatter.md +40 -0
  97. package/skills/skill-builder/CLAUDE.md +8 -7
  98. package/skills/skill-builder/SKILL.md +26 -11
  99. package/skills/skill-builder/references/CLAUDE.md +3 -1
  100. package/skills/skill-builder/references/delegation-map.md +21 -12
  101. package/skills/skill-builder/references/description-field.md +9 -11
  102. package/skills/skill-builder/references/deterministic-elements.md +218 -0
  103. package/skills/skill-builder/references/self-audit-checklist.md +62 -45
  104. package/skills/skill-builder/references/skill-modularity.md +8 -9
  105. package/skills/skill-builder/templates/CLAUDE.md +2 -2
  106. package/skills/skill-builder/templates/gap-analysis.md +15 -0
  107. package/skills/skill-builder/workflows/CLAUDE.md +5 -5
  108. package/skills/skill-builder/workflows/improve-skill.md +18 -9
  109. package/skills/skill-builder/workflows/new-skill.md +23 -15
  110. package/skills/skill-builder/workflows/polish-skill.md +28 -21
@@ -1,28 +1,33 @@
1
1
  # pr-loop
2
2
 
3
- Shared infrastructure for the PR audit-fix loop used by `bugteam` and `pr-converge`. Provides the XML prompt template, Python runtime scripts, and named constants that both skills invoke during each loop tick.
3
+ Shared infrastructure for the PR audit-fix loop used by `bugteam`,
4
+ `pr-converge`, and `autoconverge`. Provides the XML prompt template, Python
5
+ runtime scripts, the portable converge driver protocol, and named constants
6
+ those skills invoke during each loop tick.
4
7
 
5
8
  ## Subdirectories
6
9
 
7
10
  | Directory | Role |
8
11
  |---|---|
9
12
  | `prompts/` | XML agent prompt templates. |
10
- | `scripts/` | Python scripts for loop state management, prompt building, outcome recording, path resolution, and preflight checks. |
13
+ | `scripts/` | Python scripts for loop state management, prompt building, outcome recording, path resolution, pacer selection, and preflight checks. |
11
14
 
12
15
  ## Key files
13
16
 
14
17
  | File | Role |
15
18
  |---|---|
19
+ | `portable-driver.md` | Continuous in-session pacer when Workflow / ScheduleWakeup are absent. |
16
20
  | `prompts/pr-consistency-audit.xml` | Structured prompt artifact for the cross-file consistency audit agent. |
17
- | `packages/claude-dev-env/skills/_shared/pr-loop/scripts/build_audit_prompt.py` | Assembles the audit agent prompt from loop state and the constants module. |
18
- | `packages/claude-dev-env/skills/_shared/pr-loop/scripts/build_fix_prompt.py` | Assembles the fix agent prompt from loop state and findings. |
19
- | `packages/claude-dev-env/skills/_shared/pr-loop/scripts/init_loop_state.py` | Initializes the per-PR loop state JSON file. |
20
- | `packages/claude-dev-env/skills/_shared/pr-loop/scripts/write_audit_outcomes.py` | Writes the per-loop audit outcome XML into the workspace. |
21
- | `packages/claude-dev-env/skills/_shared/pr-loop/scripts/write_fix_outcomes.py` | Writes the per-loop fix outcome XML into the workspace. |
22
- | `packages/claude-dev-env/skills/_shared/pr-loop/scripts/preflight_worktree.py` | Verifies the working directory is a healthy worktree for the target PR's repo. |
23
- | `packages/claude-dev-env/skills/_shared/pr-loop/scripts/teardown_worktrees.py` | Removes loop worktrees on clean exit. |
24
- | `packages/claude-dev-env/skills/_shared/pr-loop/scripts/write_handoff.py` | Writes durable resume-handoff files under `~/.claude/runtime/pr-loop/<run-name>/` at each converge checkpoint. |
25
- | `packages/claude-dev-env/skills/_shared/pr-loop/scripts/_path_resolver.py` | Resolves workspace and worktree paths from PR metadata. |
26
- | `packages/claude-dev-env/skills/_shared/pr-loop/scripts/_cli_utils.py` | Shared CLI argument parsing helpers. |
27
- | `packages/claude-dev-env/skills/_shared/pr-loop/scripts/_xml_utils.py` | XML serialization helpers. |
21
+ | `scripts/select_converge_pacer.py` | Maps entry skill + host tool flags to `workflow`, `schedule_wakeup`, or `portable`. |
22
+ | `scripts/build_audit_prompt.py` | Assembles the audit agent prompt from loop state and the constants module. |
23
+ | `scripts/build_fix_prompt.py` | Assembles the fix agent prompt from loop state and findings. |
24
+ | `scripts/init_loop_state.py` | Initializes the per-PR loop state JSON file. |
25
+ | `scripts/write_audit_outcomes.py` | Writes the per-loop audit outcome XML into the workspace. |
26
+ | `scripts/write_fix_outcomes.py` | Writes the per-loop fix outcome XML into the workspace. |
27
+ | `scripts/preflight_worktree.py` | Verifies the working directory is a healthy worktree for the target PR's repo. |
28
+ | `scripts/teardown_worktrees.py` | Removes loop worktrees on clean exit. |
29
+ | `scripts/write_handoff.py` | Writes durable resume-handoff files under `~/.claude/runtime/pr-loop/<run-name>/` at each converge checkpoint. |
30
+ | `scripts/_path_resolver.py` | Resolves workspace and worktree paths from PR metadata. |
31
+ | `scripts/_cli_utils.py` | Shared CLI argument parsing helpers. |
32
+ | `scripts/_xml_utils.py` | XML serialization helpers. |
28
33
  | `scripts/skills_pr_loop_constants/` | Named constants package imported by the scripts above. |
@@ -0,0 +1,150 @@
1
+ # Portable converge driver
2
+
3
+ **Rule: deterministic control is script-only.** Phase transitions, wait delays,
4
+ clean stamps, ready decisions, task lists, and “what next” never live as prose
5
+ for the agent to invent. The agent runs scripts, reads JSON, and only performs
6
+ judgment steps the JSON names.
7
+
8
+ ## Step 1 — task list (every autoconverge / portable run)
9
+
10
+ ```
11
+ python "$HOME/.claude/skills/_shared/pr-loop/scripts/build_converge_task_list.py" \
12
+ [--bugbot-down 0|1] [--copilot-down 0|1] \
13
+ [--codex-down 0|1] [--codex-required 0|1]
14
+ ```
15
+
16
+ Register every `tasks[]` entry on the session task list. **Final task id is
17
+ always** `all_runnable_reviews_clean_same_head`. The run is complete only when
18
+ that final task is completed: every runnable code review is CLEAN on one
19
+ shared HEAD. Do not invent tasks in prose.
20
+
21
+ `open-run` embeds the same list (`tasks`, `runnable_review_ids`,
22
+ `final_task_id`, `done_when`).
23
+
24
+ ## Pacer selection
25
+
26
+ ```
27
+ python "$HOME/.claude/skills/_shared/pr-loop/scripts/select_converge_pacer.py" \
28
+ --skill <pr-converge|autoconverge> \
29
+ --has-workflow <0|1> \
30
+ --has-schedule-wakeup <0|1>
31
+ ```
32
+
33
+ When `pacer` is not `portable`, use the skill’s native Workflow or
34
+ ScheduleWakeup path. When `pacer=portable`, use the control script below.
35
+
36
+ ## Isolation and worktree
37
+
38
+ 1. When the tool list includes `EnterWorktree`, call it (same contract as the
39
+ Claude-host skills).
40
+ 2. When `EnterWorktree` is absent, isolate with git worktree machinery:
41
+ - Prefer an existing worktree already on the PR head ref under
42
+ `.claude/worktrees/` (or another dedicated worktree path).
43
+ - Otherwise `git fetch origin <headRefName>` and
44
+ `git worktree add <path> <headRefName>` (or `gh pr checkout <N>` into a
45
+ dedicated directory), then `cd` into that checkout.
46
+ 3. Confirm the working directory is the PR’s own repo on the PR head SHA:
47
+ `python "$HOME/.claude/skills/_shared/pr-loop/scripts/preflight_worktree.py" --owner <O> --repo <R> --mode strict`.
48
+ Non-zero exit → report the `ABORT` line and stop.
49
+ 4. Cross-repo routing follows pr-converge Step 1.5: every local review and edit
50
+ runs with cwd set to the **PR worktree**.
51
+
52
+ `open-run` runs the same strict preflight before seeding state.
53
+
54
+ ## Control script
55
+
56
+ ```
57
+ python "$HOME/.claude/skills/_shared/pr-loop/scripts/portable_converge_driver.py" <command> ...
58
+ ```
59
+
60
+ Stdout JSON: `status`, `next`, `phase`, `state_file`, optional `commands`,
61
+ `wait_seconds`, `blocker`, and on `open-run` the task-list fields. Exit `0` =
62
+ ok; `1` = contract failure; `2` = usage error.
63
+
64
+ | Command | Deterministic effect |
65
+ |---|---|
66
+ | `open-run` | Require `portable`; preflight; seed state + task list; when `--codex-down` is off, set `codex_required` from CLI force-on or the weekly usage probe (same rule as `check_convergence`); next=`run_code_review` |
67
+ | `after-code-review` | From returncode / dirty_tree / served_command |
68
+ | `after-bugteam` | From pushed / converged |
69
+ | `after-bugbot` | From classification / inline lag |
70
+ | `after-codex` | From classification clean / dirty / down |
71
+ | `after-copilot-wait` | From review surfaced / wait cap |
72
+ | `after-ready-check` | From check_convergence exit |
73
+ | `show-state` | Echo state; rehydrate `commands` / `wait_seconds` for pending next |
74
+
75
+ ## Continuous tick loop
76
+
77
+ After transport check, PR scope, isolation, permission grant, and the once-per-run
78
+ Copilot quota pre-check:
79
+
80
+ 1. Seed or restore state via `open-run` or `show-state` (and handoff
81
+ `state-copy.json` when resuming).
82
+ 2. Run the driver command for the current step; read JSON `next` / `commands` /
83
+ `wait_seconds`.
84
+ 3. On non-terminal `next`:
85
+ - Write state and handoff when the after-* payload says so.
86
+ - If `next` is `poll_wait`, sleep `wait_seconds` only, then re-poll and call
87
+ the matching after-*.
88
+ - If `next` is immediate work, continue in the same turn without sleeping.
89
+ 4. On `mark_ready` / `stop_blocked` / named stop: run lifecycle Close, print the
90
+ entry skill’s exit block, omit further pacing.
91
+ 5. External reviewers remain skippable the same way as Claude-host runs when
92
+ opted out or down. Push and head-change reset push-invalidated markers
93
+ (`*_clean_at`, `merge_state_status`, `bugbot_down`, `codex_down`).
94
+
95
+ ## Agent loop (judgment only)
96
+
97
+ 1. Run the driver command for the current step.
98
+ 2. If JSON `commands` is non-empty, run that argv (scripted helper).
99
+ 3. Map JSON `next` to the single judgment action; report back via the matching
100
+ `after-*` command.
101
+ 4. Mark session tasks complete only when the scripted stamps say so; complete
102
+ the final task only when every runnable review is clean on the same HEAD.
103
+
104
+ | `next` | Agent does | Report with |
105
+ |---|---|---|
106
+ | `run_code_review` | Run `commands` | `after-code-review` |
107
+ | `apply_fixes_and_push` | Fix protocol; commit; push | re-review then `after-code-review` |
108
+ | `run_bugteam` | resolve_worker_spawn / bugteam body | `after-bugteam` |
109
+ | `run_bugbot_gate` | Bugbot helper scripts | `after-bugbot` |
110
+ | `run_codex_review` | Run Codex review step | `after-codex` |
111
+ | `request_copilot_review` | Request Copilot | `after-copilot-wait` |
112
+ | `poll_wait` | Sleep `wait_seconds` only | re-poll then after-* |
113
+ | `check_ready` | Run `commands` (check_convergence) | `after-ready-check` |
114
+ | `mark_ready` | Run `commands` (`gh pr ready`) | teardown |
115
+ | `stop_blocked` | Teardown; print blocker | stop |
116
+
117
+ ## Autoconverge entry on portable pacer
118
+
119
+ When `/autoconverge` selects `pacer=portable`:
120
+
121
+ - Complete autoconverge pre-flight (scope, draft ownership, strict worktree,
122
+ grant, Copilot quota).
123
+ - Drive the continuous tick loop above (scripted phase machine) until ready
124
+ or a documented blocker.
125
+ - Skip `Workflow({ scriptPath: converge.mjs })` — that path requires the
126
+ Workflow tool.
127
+ - Teardown uses the lifecycle Close path; the Workflow-only closing HTML
128
+ journal report is optional and skipped when no workflow run id exists.
129
+ - Resume command on handoff: `/autoconverge <PR URL>`.
130
+
131
+ When `/autoconverge` selects `pacer=workflow`, follow the skill’s Workflow
132
+ sections unchanged.
133
+
134
+ ## Helpers (scripted)
135
+
136
+ | Concern | Script |
137
+ |---|---|
138
+ | Task list | `build_converge_task_list.py` |
139
+ | Pacer | `select_converge_pacer.py` |
140
+ | Worktree | `preflight_worktree.py` |
141
+ | Code review | `$HOME/.claude/scripts/invoke_code_review.py` |
142
+ | Workers | `$HOME/.claude/scripts/resolve_worker_spawn.py` |
143
+ | Ready | `pr-converge/scripts/check_convergence.py` |
144
+ | Handoff | `write_handoff.py` |
145
+
146
+ ## Fail closed
147
+
148
+ Never abort solely because Workflow or ScheduleWakeup is missing once
149
+ pacer=`portable`. Fail closed on contract failures only: auth, worktree,
150
+ unresolvable gates, and wait caps.
@@ -14,6 +14,9 @@ Python scripts that run the PR audit-fix loop at runtime. Both `bugteam` and `pr
14
14
  | `preflight_worktree.py` | Verifies the working directory is a healthy git worktree for the target PR's repo. Supports `--mode strict` to abort when the repo does not match. |
15
15
  | `teardown_worktrees.py` | Removes per-PR worktrees after a clean loop exit. |
16
16
  | `write_handoff.py` | Writes durable resume-handoff files under the run's `~/.claude/runtime/pr-loop` directory at each converge checkpoint. |
17
+ | `select_converge_pacer.py` | Selects `workflow`, `schedule_wakeup`, or `portable` for pr-converge / autoconverge from host tool flags. |
18
+ | `build_converge_task_list.py` | Step-1 task list: runnable review gates + final all_runnable_reviews_clean_same_head. |
19
+ | `portable_converge_driver.py` | portable_converge_driver phase machine: open-run and post-step transitions emit JSON next/commands only. |
17
20
  | `_path_resolver.py` | Resolves workspace and worktree paths from PR owner, repo, and number. |
18
21
  | `_cli_utils.py` | Shared CLI argument parsing helpers (argparse wrappers). |
19
22
  | `_xml_utils.py` | XML serialization helpers for outcome files. |
@@ -0,0 +1,310 @@
1
+ #!/usr/bin/env python3
2
+ """``build_converge_task_list.py`` — ordered review tasks for a run.
3
+
4
+ Step 1 of autoconverge / portable pr-converge: call this script. Do not invent
5
+ tasks in prose. The final task is always all runnable code reviews CLEAN on
6
+ the same HEAD.
7
+
8
+ ::
9
+
10
+ python build_converge_task_list.py \\
11
+ [--bugbot-down 0|1] [--copilot-down 0|1] \\
12
+ [--codex-down 0|1] [--codex-required 0|1]
13
+
14
+ Stdout JSON::
15
+
16
+ {
17
+ "tasks": [...],
18
+ "runnable_review_ids": [...],
19
+ "skipped_review_ids": [...],
20
+ "final_task_id": "all_runnable_reviews_clean_same_head",
21
+ "done_when": "..."
22
+ }
23
+ """
24
+
25
+ from __future__ import annotations
26
+
27
+ import argparse
28
+ import json
29
+ import sys
30
+ from pathlib import Path
31
+
32
+ _self_dir = Path(__file__).resolve().parent
33
+ if str(_self_dir) not in sys.path:
34
+ sys.path.insert(0, str(_self_dir))
35
+
36
+ from select_converge_pacer import parse_bool_flag # noqa: E402
37
+ from skills_pr_loop_constants.converge_task_list_constants import ( # noqa: E402
38
+ CLI_BUGBOT_DOWN_FLAG,
39
+ CLI_CODEX_DOWN_FLAG,
40
+ CLI_CODEX_REQUIRED_FLAG,
41
+ CLI_COPILOT_DOWN_FLAG,
42
+ DONE_WHEN_TEXT,
43
+ EXIT_SUCCESS,
44
+ EXIT_USAGE_ERROR,
45
+ RESULT_KEY_DONE_WHEN,
46
+ RESULT_KEY_FINAL_TASK_ID,
47
+ RESULT_KEY_RUNNABLE_REVIEW_IDS,
48
+ RESULT_KEY_SKIPPED_REVIEW_IDS,
49
+ RESULT_KEY_TASKS,
50
+ SKIP_REASON_BUGBOT_DOWN,
51
+ SKIP_REASON_CODEX_DOWN,
52
+ SKIP_REASON_CODEX_NOT_REQUIRED,
53
+ SKIP_REASON_COPILOT_DOWN,
54
+ TASK_FIELD_ID,
55
+ TASK_FIELD_IS_RUNNABLE,
56
+ TASK_FIELD_KIND,
57
+ TASK_FIELD_SKIP_REASON,
58
+ TASK_FIELD_TITLE,
59
+ TASK_ID_ALL_CLEAN_SAME_HEAD,
60
+ TASK_ID_BUGBOT,
61
+ TASK_ID_BUGTEAM,
62
+ TASK_ID_CODE_REVIEW,
63
+ TASK_ID_CODEX,
64
+ TASK_ID_COPILOT,
65
+ TASK_KIND_FINAL,
66
+ TASK_KIND_REVIEW,
67
+ TASK_TITLE_ALL_CLEAN_SAME_HEAD,
68
+ TASK_TITLE_BUGBOT,
69
+ TASK_TITLE_BUGTEAM,
70
+ TASK_TITLE_CODE_REVIEW,
71
+ TASK_TITLE_CODEX,
72
+ TASK_TITLE_COPILOT,
73
+ )
74
+
75
+
76
+ def _review_task(
77
+ *,
78
+ task_id: str,
79
+ title: str,
80
+ is_runnable: bool,
81
+ skip_reason: str | None,
82
+ ) -> dict[str, object]:
83
+ task: dict[str, object] = {
84
+ TASK_FIELD_ID: task_id,
85
+ TASK_FIELD_TITLE: title,
86
+ TASK_FIELD_KIND: TASK_KIND_REVIEW,
87
+ TASK_FIELD_IS_RUNNABLE: is_runnable,
88
+ }
89
+ if skip_reason is not None:
90
+ task[TASK_FIELD_SKIP_REASON] = skip_reason
91
+ return task
92
+
93
+
94
+ def _append_always_runnable_reviews(
95
+ all_tasks: list[dict[str, object]],
96
+ ) -> None:
97
+ all_tasks.append(
98
+ _review_task(
99
+ task_id=TASK_ID_CODE_REVIEW,
100
+ title=TASK_TITLE_CODE_REVIEW,
101
+ is_runnable=True,
102
+ skip_reason=None,
103
+ )
104
+ )
105
+ all_tasks.append(
106
+ _review_task(
107
+ task_id=TASK_ID_BUGTEAM,
108
+ title=TASK_TITLE_BUGTEAM,
109
+ is_runnable=True,
110
+ skip_reason=None,
111
+ )
112
+ )
113
+
114
+
115
+ def _append_bugbot_task(
116
+ all_tasks: list[dict[str, object]],
117
+ *,
118
+ is_bugbot_down: bool,
119
+ ) -> None:
120
+ if is_bugbot_down:
121
+ all_tasks.append(
122
+ _review_task(
123
+ task_id=TASK_ID_BUGBOT,
124
+ title=TASK_TITLE_BUGBOT,
125
+ is_runnable=False,
126
+ skip_reason=SKIP_REASON_BUGBOT_DOWN,
127
+ )
128
+ )
129
+ return
130
+ all_tasks.append(
131
+ _review_task(
132
+ task_id=TASK_ID_BUGBOT,
133
+ title=TASK_TITLE_BUGBOT,
134
+ is_runnable=True,
135
+ skip_reason=None,
136
+ )
137
+ )
138
+
139
+
140
+ def _append_copilot_task(
141
+ all_tasks: list[dict[str, object]],
142
+ *,
143
+ is_copilot_down: bool,
144
+ ) -> None:
145
+ if is_copilot_down:
146
+ all_tasks.append(
147
+ _review_task(
148
+ task_id=TASK_ID_COPILOT,
149
+ title=TASK_TITLE_COPILOT,
150
+ is_runnable=False,
151
+ skip_reason=SKIP_REASON_COPILOT_DOWN,
152
+ )
153
+ )
154
+ return
155
+ all_tasks.append(
156
+ _review_task(
157
+ task_id=TASK_ID_COPILOT,
158
+ title=TASK_TITLE_COPILOT,
159
+ is_runnable=True,
160
+ skip_reason=None,
161
+ )
162
+ )
163
+
164
+
165
+ def _append_codex_task(
166
+ all_tasks: list[dict[str, object]],
167
+ *,
168
+ is_codex_down: bool,
169
+ is_codex_required: bool,
170
+ ) -> None:
171
+ if is_codex_down:
172
+ all_tasks.append(
173
+ _review_task(
174
+ task_id=TASK_ID_CODEX,
175
+ title=TASK_TITLE_CODEX,
176
+ is_runnable=False,
177
+ skip_reason=SKIP_REASON_CODEX_DOWN,
178
+ )
179
+ )
180
+ return
181
+ if not is_codex_required:
182
+ all_tasks.append(
183
+ _review_task(
184
+ task_id=TASK_ID_CODEX,
185
+ title=TASK_TITLE_CODEX,
186
+ is_runnable=False,
187
+ skip_reason=SKIP_REASON_CODEX_NOT_REQUIRED,
188
+ )
189
+ )
190
+ return
191
+ all_tasks.append(
192
+ _review_task(
193
+ task_id=TASK_ID_CODEX,
194
+ title=TASK_TITLE_CODEX,
195
+ is_runnable=True,
196
+ skip_reason=None,
197
+ )
198
+ )
199
+
200
+
201
+ def _append_final_task(all_tasks: list[dict[str, object]]) -> None:
202
+ all_tasks.append(
203
+ {
204
+ TASK_FIELD_ID: TASK_ID_ALL_CLEAN_SAME_HEAD,
205
+ TASK_FIELD_TITLE: TASK_TITLE_ALL_CLEAN_SAME_HEAD,
206
+ TASK_FIELD_KIND: TASK_KIND_FINAL,
207
+ TASK_FIELD_IS_RUNNABLE: True,
208
+ }
209
+ )
210
+
211
+
212
+ def _partition_review_ids(
213
+ all_tasks: list[dict[str, object]],
214
+ ) -> tuple[list[str], list[str]]:
215
+ all_runnable_review_ids = [
216
+ str(each_task[TASK_FIELD_ID])
217
+ for each_task in all_tasks
218
+ if each_task[TASK_FIELD_KIND] == TASK_KIND_REVIEW
219
+ and each_task[TASK_FIELD_IS_RUNNABLE] is True
220
+ ]
221
+ all_skipped_review_ids = [
222
+ str(each_task[TASK_FIELD_ID])
223
+ for each_task in all_tasks
224
+ if each_task[TASK_FIELD_KIND] == TASK_KIND_REVIEW
225
+ and each_task[TASK_FIELD_IS_RUNNABLE] is False
226
+ ]
227
+ return all_runnable_review_ids, all_skipped_review_ids
228
+
229
+
230
+ def build_converge_task_list(
231
+ *,
232
+ is_bugbot_down: bool,
233
+ is_copilot_down: bool,
234
+ is_codex_down: bool,
235
+ is_codex_required: bool,
236
+ ) -> dict[str, object]:
237
+ """Return the ordered task list and done criterion for one converge run.
238
+
239
+ Args:
240
+ is_bugbot_down: Skip Bugbot as a runnable review.
241
+ is_copilot_down: Skip Copilot as a runnable review.
242
+ is_codex_down: Skip Codex even when usage would require it.
243
+ is_codex_required: Weekly usage / policy requires a Codex review.
244
+
245
+ Returns:
246
+ JSON-serializable control object with ``tasks`` and ``done_when``.
247
+ """
248
+ all_tasks: list[dict[str, object]] = []
249
+ _append_always_runnable_reviews(all_tasks)
250
+ _append_bugbot_task(all_tasks, is_bugbot_down=is_bugbot_down)
251
+ _append_copilot_task(all_tasks, is_copilot_down=is_copilot_down)
252
+ _append_codex_task(
253
+ all_tasks,
254
+ is_codex_down=is_codex_down,
255
+ is_codex_required=is_codex_required,
256
+ )
257
+ _append_final_task(all_tasks)
258
+ all_runnable_review_ids, all_skipped_review_ids = _partition_review_ids(
259
+ all_tasks
260
+ )
261
+ return {
262
+ RESULT_KEY_TASKS: all_tasks,
263
+ RESULT_KEY_RUNNABLE_REVIEW_IDS: all_runnable_review_ids,
264
+ RESULT_KEY_SKIPPED_REVIEW_IDS: all_skipped_review_ids,
265
+ RESULT_KEY_FINAL_TASK_ID: TASK_ID_ALL_CLEAN_SAME_HEAD,
266
+ RESULT_KEY_DONE_WHEN: DONE_WHEN_TEXT,
267
+ }
268
+
269
+
270
+ def build_argument_parser() -> argparse.ArgumentParser:
271
+ """Build the CLI parser for ``build_converge_task_list``.
272
+
273
+ Returns:
274
+ Configured argument parser.
275
+ """
276
+ parser = argparse.ArgumentParser(description="build_converge_task_list")
277
+ parser.add_argument(CLI_BUGBOT_DOWN_FLAG, default="0")
278
+ parser.add_argument(CLI_COPILOT_DOWN_FLAG, default="0")
279
+ parser.add_argument(CLI_CODEX_DOWN_FLAG, default="0")
280
+ parser.add_argument(CLI_CODEX_REQUIRED_FLAG, default="0")
281
+ return parser
282
+
283
+
284
+ def main(all_argv: list[str]) -> int:
285
+ """CLI entry: print one task-list JSON object on stdout.
286
+
287
+ Args:
288
+ all_argv: Argument vector without program name.
289
+
290
+ Returns:
291
+ Process exit code.
292
+ """
293
+ parser = build_argument_parser()
294
+ parsed_arguments = parser.parse_args(all_argv)
295
+ try:
296
+ task_list_payload = build_converge_task_list(
297
+ is_bugbot_down=parse_bool_flag(parsed_arguments.bugbot_down),
298
+ is_copilot_down=parse_bool_flag(parsed_arguments.copilot_down),
299
+ is_codex_down=parse_bool_flag(parsed_arguments.codex_down),
300
+ is_codex_required=parse_bool_flag(parsed_arguments.codex_required),
301
+ )
302
+ except ValueError as validation_error:
303
+ print(str(validation_error), file=sys.stderr)
304
+ return EXIT_USAGE_ERROR
305
+ print(json.dumps(task_list_payload, sort_keys=True))
306
+ return EXIT_SUCCESS
307
+
308
+
309
+ if __name__ == "__main__":
310
+ raise SystemExit(main(sys.argv[1:]))