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,422 +1,469 @@
1
- ---
2
- name: pr-converge
3
- description: >-
4
- Loops Cursor Bugbot, a code review, a bug audit, and Copilot on the current
5
- PR, applying TDD fixes until all are clean on one HEAD. Use when the user says
6
- '/pr-converge', 'drive PR to convergence', 'loop bugbot and bugteam', 'babysit
7
- bugbot and bugteam', 'until both are clean', or 'converge this PR'.
8
- ---
9
-
10
- # PR Converge
11
-
12
- One tick per invocation. A code-review ↔ bugteam ↔ Bugbot ↔ Copilot loop on
13
- a draft PR: the internal code-review and bugteam passes drive the code to clean
14
- first, then Cursor Bugbot and Copilot run as terminal confirmation gates, until
15
- all are clean on the same `HEAD` and mergeable.
16
-
17
- ## Transport check (before any GitHub step)
18
-
19
- Run `command -v gh`; when it succeeds, run `gh auth status`; once the PR
20
- scope is resolved, run `gh api repos/<owner>/<repo> --jq .permissions.push`
21
- and take `true` as the pass. When any check fails, run the
22
- `pr-loop-cloud-transport` skill first and route every `gh` operation in this
23
- skill through its substitution matrix.
24
-
25
- ## Pre-flight
26
-
27
- Pause and physically scan the tool list at the top of this conversation
28
- for the literal string `ScheduleWakeup`. If you see it, proceed. If the
29
- string is absent after scanning, report `pr-converge requires
30
- ScheduleWakeup; aborting` and stop.
31
-
32
- Call `EnterWorktree` with no arguments before any API call, file read, or
33
- edit. Agent-view sessions start in the shared checkout; Bash (`gh`, `git`)
34
- does not auto-isolate. Do not proceed until the working directory contains
35
- `.claude/worktrees/`. If `EnterWorktree` fails, report the failure and stop.
36
-
37
- `EnterWorktree` isolates the session repo. When the PR lives in a different
38
- repo, Step 1.5 routes the working directory into a **PR worktree** of that
39
- repo on its head branch (routine, never a pause). See
40
- [`reference/per-tick.md` § Step 1.5](reference/per-tick.md).
41
-
42
- ## Resume from a prior run
43
-
44
- Before Step 0, check
45
- `~/.claude/runtime/pr-loop/bugteam-pr-<PR number>/handoff.json`. When
46
- `$CLAUDE_JOB_DIR/pr-converge-state.json` is absent but that handoff exists, seed
47
- `phase`, `tick_count`, and the clean-at SHAs from the run's `state-copy.json`, so
48
- a fresh session continues where the last one stopped rather than restarting at
49
- CODE_REVIEW.
50
-
51
- ## Copilot quota pre-check (start of run)
52
-
53
- On the first tick, apply the `reviewer-gates` skill's Copilot quota gate
54
- (`../reviewer-gates/SKILL.md` § Gate 2) once per run, never per tick. The
55
- flag lives in `pr-converge-state.json` as `copilot_down`, and every tick reads
56
- it. While `copilot_down` is true, the tick skips all Copilot work — no fetch,
57
- no request, no poll, no agent — and exports
58
- `CLAUDE_REVIEWS_DISABLED="copilot"` in that tick's shell before
59
- `check_convergence.py`, so the check bypasses the Copilot review gate and the
60
- pending-requested-reviews gate and the run still marks ready on the remaining
61
- signals.
62
-
63
- ## Copilot findings — tier, verify, then route
64
-
65
- Tier, verify, and route each Copilot finding via
66
- [`copilot-finding-triage`](../copilot-finding-triage/SKILL.md) (self-healing
67
- auto-fix; code-concern verify then confirmed / refuted / inconclusive).
68
-
69
- **pr-converge phase routing after triage:**
70
-
71
- - Self-healing and **confirmed** findings join the fix tick on `current_head`;
72
- after push, reset clean-at SHAs, set `phase = CODE_REVIEW`, return to Step 5.
73
- - **Refuted** findings resolve clean on the thread; no phase change.
74
- - One or more **inconclusive** findings: do not mark ready. Run the triage
75
- skill's user gate (ntfy + 45-minute hold across ticks; persist the deadline so
76
- each tick reads it on entry). Act on the user's direction inside the window;
77
- on timeout, teardown and report the findings un-reviewed.
78
- - Enter `COPILOT_WAIT` only from gate (d) after requesting a Copilot review
79
- (Step 7 → 7a). Stay on `COPILOT_WAIT` until a review surfaces at
80
- `current_head`, `copilot_wait_count >= 3` hard-blocks, or `copilot_down`
81
- skips the Copilot path entirely.
82
-
83
- ## Budget-aware tick boundaries
84
-
85
- Before starting any tick, estimate whether the remaining session/usage
86
- budget covers one full clean tick (worst case: a BUGBOT fetch + a
87
- full-diff CODE_REVIEW + a fix commit + replies). If it does not, do not
88
- start the tick. Stop at the current tick boundary: write updated state to
89
- `$CLAUDE_JOB_DIR/pr-converge-state.json`, write the durable handoff (see
90
- [State persistence](#state-persistence)), then report the exact resume
91
- command (`/pr-converge <PR URL>`) and the persisted `phase`/`tick_count`.
92
- A tick cut off mid-flight poisons the resume state clean SHAs recorded
93
- against work that never landed so an unstarted tick is always cheaper
94
- than a half-finished one.
95
-
96
- ## Findings discipline
97
-
98
- Every finding, reply, and report states verified facts only — no hedging
99
- language (`likely`, `probably`, `should`, `appears to`). Verify each
100
- claim against the code on `current_head` before stating it; the
101
- anti-hallucination Stop hook rejects hedged output, forcing a rework
102
- pass. A claim that cannot be verified is reported as unverified, not
103
- softened.
104
-
105
- ## State persistence
106
-
107
- Single-PR mode persists loop state to `$CLAUDE_JOB_DIR/pr-converge-state.json`.
108
- On tick entry, read this file if it exists to restore phase, tick_count, and
109
- clean-at SHAs. On tick exit, write updated state before calling ScheduleWakeup
110
- so the next tick resumes with accurate state.
111
-
112
- After the state write and before ScheduleWakeup, write the durable handoff so a
113
- fresh session in a new job can resume this run:
114
-
115
- ```
116
- python "$HOME/.claude/skills/_shared/pr-loop/scripts/write_handoff.py" \
117
- --pr-number <N> --head-ref <branch> --phase <phase> \
118
- --resume-command "/pr-converge <PR URL>" \
119
- --state-file "$CLAUDE_JOB_DIR/pr-converge-state.json" \
120
- --completed-steps "<clean phases this run>"
121
- ```
122
-
123
- It writes `handoff.json`, `HANDOFF.md`, and `state-copy.json` under
124
- `~/.claude/runtime/pr-loop/<run-name>/`. The job-dir state stays the source of
125
- truth for a resumed tick; the handoff copy is the pointer a fresh session reads
126
- when `$CLAUDE_JOB_DIR` is gone.
127
-
128
- Fields: `phase`, `tick_count`, `bugbot_clean_at`, `code_review_clean_at`,
129
- `bugteam_clean_at`, `copilot_clean_at`, `merge_state_status`, `current_head`,
130
- `bugbot_acknowledged_at`, `bugbot_down`, `copilot_down`,
131
- `bugteam_skill_invoked_at_head`, `bugteam_skill_invoked_at_tick`,
132
- `agents_session_id`, `persistent_agents`.
133
-
134
- ## Persistent per-step agents
135
-
136
- Three step-scoped agents persist across ticks so their context carries
137
- forward: `fix_executor`, `thread_sweep`, and `copilot_watch`, recorded in
138
- the `persistent_agents` map
139
- ([`reference/state-schema.md`](reference/state-schema.md)).
140
-
141
- - **Resume:** read `persistent_agents.<key>`. When an entry exists,
142
- `SendMessage` to the stored `agent_id` with a tick payload that restates
143
- the PR scope, `current_head`, the PR worktree path, this tick's findings
144
- or threads, and the report-back contract. Bump `last_used_tick`, then
145
- await completion.
146
- - **First spawn:** `Agent(subagent_type: "clean-coder", name:
147
- "prc-fix-<PR#>")` the `name` makes the agent a persistent teammate
148
- that idles awaiting messages. Record `{agent_id, created_tick,
149
- last_used_tick}` under the step key. Keep the spawn prompt fix-shaped,
150
- never audit-shaped: the `pr_converge_bugteam_enforcer` hook blocks
151
- audit-shaped clean-coder spawns during the BUGTEAM phase.
152
- - **Stale or dead id:** on a `SendMessage` failure, or no acknowledgment
153
- within one bounded wait, drop the map entry, spawn a fresh named agent,
154
- record it, and continue the tick. Never abort a tick on a stale id;
155
- never retry the same dead id.
156
- - **Fresh every round (never persisted):** the Step 5 host-aware
157
- `invoke_code_review.py` / `/code-review high --fix` pass and the Step 6
158
- bugteam audit (unbiased eyes each round; the enforcer needs the formal
159
- Skill call), and every `code-verifier` a named code-verifier never fires
160
- `SubagentStop`, so no verdict mints (see the named-`code-verifier` entry
161
- in the Gotchas list below).
162
- - **Shutdown:** at loop end (convergence or a stop condition), send each
163
- persistent agent a shutdown request and clear `persistent_agents` before
164
- the `pr-loop-lifecycle` Close.
165
-
166
- ## Gotchas
167
-
168
- Highest-signal content. Append a bullet each time a tick fails in a new
169
- way these are the hard-won lessons that keep the loop honest. Once this
170
- grows to 5 or more items, suggest spinning up a subagent to investigate, fix,
171
- post a fresh PR in a fresh branch based on origin main to the user.
172
-
173
- - **`ScheduleWakeup` not in subagent tool registries** — background
174
- `general-purpose` tick cannot schedule re-entry; only parent session
175
- with `ScheduleWakeup` in registry can call it.
176
- - **`state.json` without §Concurrency lock loses merges** when teammates
177
- finish in same wall-clock window.
178
- - **`tick_count` must not double-increment** conversation state line
179
- only when **no** `state.json`; with `state.json`, only the
180
- orchestrator bump increments.
181
- - **Bugbot trigger and detection** — CI check-run based. Full flow at
182
- [`per-tick.md` Step 3](reference/per-tick.md); see also
183
- `~/.claude/skills/pr-converge/scripts/check_bugbot_ci.py --help`.
184
- - **Bot login fields differ by endpoint** `get_reviews` returns
185
- `.user.login` (object), but `get_review_comments` returns `.author`
186
- (string, not an object). Always check the correct fields and use
187
- case-insensitive substring matching on login values, never strict
188
- equality.
189
- - **`isOutdated` has dual scope** GitHub marks a thread `isOutdated=true`
190
- when the line it anchors to has changed since the comment was posted. The
191
- machine gate (`check_convergence.py` / [convergence-gates.md](reference/convergence-gates.md)
192
- gate (e)) excludes `isOutdated == true` bot threads from the fail count —
193
- only non-outdated unresolved bot threads fail the gate. The agent-side
194
- unresolved-thread sweep in the shared fix protocol
195
- ([`../../_shared/pr-loop/fix-protocol.md`](../../_shared/pr-loop/fix-protocol.md)
196
- step 12; skill deltas in [`reference/fix-protocol.md`](reference/fix-protocol.md))
197
- still verifies outdated unresolved threads against HEAD before resolve when
198
- the protocol requires it (the original concern can still apply when the fix
199
- moved rather than landed).
200
- - **Tilde paths fail on Windows Git Bash** `~/.claude/skills/...`
201
- resolves to the wrong home directory in Bash-tool contexts. Use
202
- `$HOME/.claude/skills/...` in shell invocations or `Path.home() /
203
- ".claude/skills/..."` in Python scripts. Script invocations through
204
- Bash that reference `~` produce "file not found" errors
205
- indistinguishable from actual script failures.
206
- - **PowerShell cmdlets fail in Bash tool** — `.ps1` scripts and
207
- `pwsh` calls run through the PowerShell tool, not Bash. Bash on
208
- Windows is Git Bash which cannot execute PowerShell cmdlets. Route all
209
- PowerShell work through the PowerShell tool or `pwsh -NoProfile -File`.
210
- - **Cross-repo PR: route cwd into the PR worktree before Step 5 review**
211
- `invoke_code_review.py` and `/code-review high --fix` audit the repo of the
212
- cwd (the helper's `--cwd`). When the session is rooted in a different repo
213
- than the PR, `EnterWorktree` cannot re-root (it is scoped to the session's
214
- repo); resolve the PR worktree and `cd` into it per
215
- [Step 1.5](reference/per-tick.md). Skipping this reviews and edits the
216
- wrong repo. The route is routine and automatic never a material fork
217
- to pause on.
218
- - **A named/teammate `code-verifier` never mints a verdict** — In a background-job session, an `Agent`-tool `code-verifier` spawned with a `name` (or otherwise as a persistent teammate) goes idle/"available" awaiting messages rather than terminating, so its `SubagentStop` never fires. `verifier_verdict_minter.py` mints the verdict only on `SubagentStop`, so no verdict file is written and `verified_commit_gate` blocks the `git commit`/`git push` with "no passing verification verdict" even though the verifier emitted `all_pass`. A `shutdown_request` is ignored and `TaskStop` cannot resolve the teammate's id. Spawn the code-verifier as a one-shot agent with NO `name` (a plain async `Agent` call) so it runs to completion and fires `SubagentStop`, minting the verdict bound to the live surface. Keep the work tree frozen between verification and the commit so the minted surface hash still matches.
219
-
220
- ## Progress checklist
221
-
222
- State variables (`phase`, `bugbot_clean_at`, `code_review_clean_at`,
223
- `bugteam_clean_at`, `copilot_clean_at`, `merge_state_status`, counters) are
224
- defined in [`reference/state-schema.md`](reference/state-schema.md). Ground rules
225
- in [`reference/ground-rules.md`](reference/ground-rules.md).
226
-
227
- Each step references its spoke file for full procedural detail — MCP calls,
228
- script invocations, decision criteria. Every "return to Step N" means the next
229
- tick starts fresh from that step.
230
-
231
- **Hard gate: do not advance from any step while ANY unresolved review
232
- thread exists on the PR.** Sweep semantics and per-thread handling live in
233
- the shared fix protocol
234
- ([`../../_shared/pr-loop/fix-protocol.md`](../../_shared/pr-loop/fix-protocol.md)
235
- step 12; skill deltas in [`reference/fix-protocol.md`](reference/fix-protocol.md)).
236
-
237
- **Full-PR-diff rule: every CODE-REVIEW round (Step 5) and every BUGTEAM
238
- round (Step 6) covers the FULL `origin/main...HEAD` diff every file
239
- the PR touches.** A round that scopes to a subset — only the last commit,
240
- only files touched since the prior clean SHA, only bugbot-flagged paths,
241
- or any other delta cut — does not satisfy the gate, and a "clean" verdict
242
- against a partial diff is not a valid clean. Re-run the round against the
243
- full diff before recording `code_review_clean_at` or treating the bugteam
244
- round as converged. This rule holds every tick, every loop, every PR.
245
-
246
- - [ ] **Step 0: Open the run**
247
- Apply the `pr-loop-lifecycle` skill's Open section
248
- (`../pr-loop-lifecycle/SKILL.md`): permission grant + worktree preflight.
249
-
250
- - [ ] **Step 1: Resolve PR scope + PR worktree**
251
- Apply Step 1.5 scope resolution in
252
- [`reference/per-tick.md`](reference/per-tick.md). Resolve the **PR
253
- worktree** the local checkout every local step this tick targets:
254
- the `EnterWorktree` checkout when the PR shares the session's repo,
255
- else a checkout of the PR's repo that the working directory routes
256
- into via `cd`. Cross-repo routing is automatic, not a fork.
257
- See: [`reference/per-tick.md` § Step 1](reference/per-tick.md)
258
- and Step 1.5](reference/per-tick.md)
259
-
260
- - [ ] **Step 2: Initialize loop state**
261
- `phase = CODE_REVIEW`; all counters at zero; `run_name` resolved.
262
-
263
- - [ ] **Step 3: Mergeability check**
264
- See: [`reference/convergence-gates.md` § (c)](reference/convergence-gates.md)
265
-
266
- ```
267
- pull_request_read(method="get") .mergeable_state, .mergeable
268
- ```
269
-
270
- - [ ] mergeable → advance to Step 4
271
- - [ ] not mergeable → rebase → force-push → return to Step 1
272
-
273
- - [ ] **Step 4: BUGBOT terminal Bugbot confirmation gate**
274
- Step 6 routes here after BUGTEAM converges. Bugbot confirms code the
275
- internal passes already drove to clean.
276
- See: [`reference/per-tick.md` § BUGBOT terminal gate + Step 3](reference/per-tick.md);
277
- availability and trigger via `reviewer-gates`
278
- (`../reviewer-gates/SKILL.md` § Gate 1 / Gate 3).
279
-
280
- - [ ] **disabled / down** → `bugbot_down = true` → Step 7
281
- - [ ] **dirty on `current_head`** → apply shared fix protocol
282
- ([`../../_shared/pr-loop/fix-protocol.md`](../../_shared/pr-loop/fix-protocol.md);
283
- skill deltas in [`reference/fix-protocol.md`](reference/fix-protocol.md))
284
- push reset push-invalidated markers `phase = CODE_REVIEW` Step 5
285
- - [ ] **clean on `current_head`** zero unresolved threads (else fix + resolve first)
286
- `bugbot_clean_at = current_head` Step 7
287
- - [ ] **no review / commit_id mismatch** `reviewer-gates` Bugbot flow (Gate 3):
288
- silent pass stamp + Step 7; queued/triggered → 360s wakeup → Step 4;
289
- down `bugbot_down = true` Step 7
290
-
291
- - [ ] **Step 5: CODE-REVIEW static sweep, review, fix, advance**
292
- Entry phase every tick; re-entered after any fix push.
293
- See: [`reference/per-tick.md` § CODE_REVIEW entry](reference/per-tick.md).
294
- Pre-condition: cwd is the Step 1.5 PR worktree on `current_head`.
295
- Scope: FULL `origin/main...HEAD` diff every tick (no path args, no delta cut).
296
- Review always runs at effort high on model opus through
297
- `invoke_code_review.py`. Mode decision inputs: host profile + session
298
- model. Call:
299
- `python "$HOME/.claude/scripts/invoke_code_review.py" --cwd <PR-worktree>
300
- --session-model <alias>`. Chain mode uses that cwd and empty stdin; the
301
- chain process never commits and never pushes. JSON stdout carries
302
- `mode` (`in_session` | `chain`), `served_command`, `returncode`, and
303
- `dirty_tree`. Config/host errors still emit that JSON with non-zero
304
- `returncode` (no traceback-only failure).
305
-
306
- - [ ] **Static sweep fails** apply shared fix protocol → push → reset markers
307
- → stay CODE_REVIEW → Step 5
308
- - [ ] **`mode == in_session`** (Claude host, session model opus) → run
309
- `/code-review high --fix` in-session (no path args)
310
- - [ ] **`mode == chain`** (any other host or non-opus session) → helper
311
- already ran the headless review; read `returncode`,
312
- `served_command`, and `dirty_tree` from JSON
313
- - [ ] **failed review** (`returncode != 0`, or chain with null
314
- `served_command`) → do not set `code_review_clean_at` → stay
315
- CODE_REVIEW → Step 5
316
- - [ ] **fixes applied** (`dirty_tree` true / working tree dirty)
317
- commit + push via shared fix protocol → reset markers → stay
318
- CODE_REVIEW Step 5
319
- - [ ] **clean** (successful serve: `returncode == 0`, chain
320
- `served_command` non-null when chain, and `dirty_tree` false) →
321
- zero unresolved threads (else fix + resolve)
322
- `code_review_clean_at = current_head` `phase = BUGTEAM` → Step 6
323
-
324
- - [ ] **Step 6: BUGTEAM — run, decide, fix, reply, resolve**
325
- See: [`reference/per-tick.md` § Step 2 BUGTEAM](reference/per-tick.md);
326
- [`../bugteam/SKILL.md`](../bugteam/SKILL.md).
327
- Pre-condition: `code_review_clean_at == current_head`.
328
- Mandatory: `Skill({skill: "bugteam", args: "<PR URL>"})` this tick
329
- (enforcer-blocked otherwise; `qbug` is not a substitute). Scope: FULL
330
- `origin/main...HEAD` diff. Re-resolve HEAD after bugteam.
331
-
332
- - [ ] **bugteam pushed** verify threads replied + resolved reset markers
333
- `phase = CODE_REVIEW` → 360s wakeup → Step 5
334
- - [ ] **converged, no push** zero unresolved threads →
335
- `bugteam_clean_at = current_head` → `phase = BUGBOT` → Step 4
336
- - [ ] **findings without committed fixes** apply shared fix protocol → push →
337
- reset markers `phase = CODE_REVIEW` Step 5
338
-
339
- - [ ] **Step 7: Convergence gates**
340
- Full procedure: [`reference/convergence-gates.md`](reference/convergence-gates.md).
341
-
342
- Pre-condition: Step 6 converged AND (`bugbot_clean_at == current_head` OR
343
- `bugbot_down`). The terminal Bugbot gate (Step 4) sets that state just
344
- before these gates run. Count unresolved threads before each gate.
345
- Every gate records evidence; gate (f) cites evidence from (a)–(e).
346
-
347
- - [ ] **(a) Copilot findings** — fetch Copilot on `current_head`; dirty → fix + return to Step 5; clean → stamp `copilot_clean_at`; absent → continue; when `copilot_down`, skip
348
- - [ ] **(b) Claude reviewer** — fetch Claude on `current_head`; dirty fix + return to Step 5; clean or absent → continue
349
- - [ ] **(c) Mergeability** — `mergeable_state == "clean"` and `mergeable == true`; dirty → rebase + return to Step 1; blocked/behind/unknown/unstable → hard blocker
350
- - [ ] **(d) Post-convergence Copilot request** — request Copilot when not pending and not `copilot_down`; enter `COPILOT_WAIT` (Step 7a); when `copilot_down`, skip to (e)
351
- - [ ] **(e) Thread-resolution** zero unresolved threads across the PR; else sweep + fix/resolve
352
- - [ ] **(f) Mark ready** — run `check_convergence.py`; exit 0 `update_pull_request(draft=false)` → Step 8; exit 1 → fix path; exit 2 → retry/stop
353
-
354
- - [ ] **Step 7a: COPILOT_WAIT — fetch Copilot, decide**
355
- See: [`reference/per-tick.md` § Step 2 COPILOT_WAIT](reference/per-tick.md).
356
- Skipped entirely when `copilot_down` (gate (d) never enters this phase).
357
-
358
- - [ ] **clean** `copilot_clean_at = current_head` Step 7 (re-validate (b), (c), (e), (f))
359
- - [ ] **dirty** apply shared fix protocol
360
- ([`../../_shared/pr-loop/fix-protocol.md`](../../_shared/pr-loop/fix-protocol.md);
361
- skill deltas in [`reference/fix-protocol.md`](reference/fix-protocol.md))
362
- push reset markers`phase = CODE_REVIEW` Step 5
363
- - [ ] **no review yet** increment `copilot_wait_count` → ≥ 3 hard-blocks;
364
- else 360s wakeup Step 7a next tick
365
-
366
- - [ ] **Step 8: Clean working tree**
367
- `pr-loop-lifecycle` Close (`../pr-loop-lifecycle/SKILL.md`).
368
- See: [`pr-loop-lifecycle/reference/teardown-publish-permissions.md` § Clean working tree](../pr-loop-lifecycle/reference/teardown-publish-permissions.md)
369
-
370
- - [ ] **Step 9: Rewrite PR description**
371
- `pr-loop-lifecycle` Close.
372
- See: [`pr-loop-lifecycle/reference/teardown-publish-permissions.md` § Publish the final PR description](../pr-loop-lifecycle/reference/teardown-publish-permissions.md)
373
-
374
- - [ ] **Step 10: Revoke project permissions (always)**
375
- `pr-loop-lifecycle` Close (`../pr-loop-lifecycle/SKILL.md` § Close).
376
-
377
- - [ ] **Step 11: Print final report**
378
- Print this block verbatim no paraphrase, no extra commentary:
379
- ```
380
- /pr-converge exit: converged
381
- Loops: <N>
382
- Final commit: <SHA>
383
- ```
384
-
385
- ## Edge cases
386
-
387
- | Situation | Read |
388
- |---|---|
389
- | Multi-PR session (`state.json` exists) | [`reference/multi-pr-orchestration.md`](reference/multi-pr-orchestration.md) |
390
- | Hard blocker or user stops loop | [`reference/stop-conditions.md`](reference/stop-conditions.md) |
391
- | Tick is ambiguous against the steps above | [`reference/examples.md`](reference/examples.md) |
392
-
393
- ## Depends on / invokes
394
-
395
- | Skill / path | Role |
396
- |---|---|
397
- | `pr-loop-lifecycle` | Open (permission grant + worktree preflight) and Close (cleanup, PR description, revoke) |
398
- | `reviewer-gates` | Copilot quota gate, Bugbot availability, Bugbot trigger flow |
399
- | `copilot-finding-triage` | Tier, verify, and route each Copilot finding |
400
- | `bugteam` | Full-diff bug audit in Step 6 (Skill invocation mandatory) |
401
- | `pr-loop-cloud-transport` | When `gh` is unavailable or unauthenticated |
402
- | [`../../_shared/pr-loop/fix-protocol.md`](../../_shared/pr-loop/fix-protocol.md) | Shared 13-step fix sequence (TDD, commit, push, reply, resolve) |
403
- | [`reference/fix-protocol.md`](reference/fix-protocol.md) | pr-converge deltas on the shared fix protocol |
404
-
405
- ## Package file index
406
-
407
- | Path | Purpose |
408
- |---|---|
409
- | `SKILL.md` | Hub: pre-flight, state, progress checklist, gotchas |
410
- | `CLAUDE.md` | Package map for agents working in this skill |
411
- | `pr_converge_skill_constants/` | Importable constants for skill scripts |
412
- | `reference/per-tick.md` | Full per-tick procedure (phases, cwd routing, pacing) |
413
- | `reference/convergence-gates.md` | Six gates before mark-ready |
414
- | `reference/fix-protocol.md` | pr-converge fix-protocol deltas |
415
- | `reference/ground-rules.md` | Non-negotiable loop constraints |
416
- | `reference/state-schema.md` | `pr-converge-state.json` fields |
417
- | `reference/stop-conditions.md` | Hard stops without convergence |
418
- | `reference/multi-pr-orchestration.md` | Multi-PR session orchestration |
419
- | `reference/examples.md` | Worked tick sequences |
420
- | `reference/obstacles/` | Per-obstacle fix runbooks |
421
- | `scripts/` | Convergence helpers, Copilot fetch, fix-reply poster, tests |
422
- | `workflows/schedule-wakeup-loop.md` | ScheduleWakeup pacing |
1
+ ---
2
+ name: pr-converge
3
+ description: >-
4
+ Loops Cursor Bugbot, a code review, a bug audit, and Copilot on the current
5
+ PR, applying TDD fixes until all are clean on one HEAD. Use when the user says
6
+ '/pr-converge', 'drive PR to convergence', 'loop bugbot and bugteam', 'babysit
7
+ bugbot and bugteam', 'until both are clean', or 'converge this PR'.
8
+ ---
9
+
10
+ # PR Converge
11
+
12
+ A code-review ↔ bugteam ↔ Bugbot ↔ Copilot loop on a draft PR: the internal
13
+ code-review and bugteam passes drive the code to clean first, then Cursor
14
+ Bugbot and Copilot run as terminal confirmation gates, until all are clean on
15
+ the same `HEAD` and mergeable. On `pacer=schedule_wakeup`, one tick runs per
16
+ invocation and the next tick is scheduled. On `pacer=portable`, ticks run
17
+ continuously in-session (see
18
+ [`../_shared/pr-loop/portable-driver.md`](../_shared/pr-loop/portable-driver.md)).
19
+
20
+ ## Transport check (before any GitHub step)
21
+
22
+ Run `command -v gh`; when it succeeds, run `gh auth status`; once the PR
23
+ scope is resolved, run `gh api repos/<owner>/<repo> --jq .permissions.push`
24
+ and take `true` as the pass. When any check fails, run the
25
+ `pr-loop-cloud-transport` skill first and route every `gh` operation in this
26
+ skill through its substitution matrix.
27
+
28
+ ## Pre-flight
29
+
30
+ ### Task list (step 1)
31
+
32
+ After down/opt-out flags are known, build the work list with
33
+ `build_converge_task_list.py` only (same script as autoconverge). Register every
34
+ task; **final task** is always `all_runnable_reviews_clean_same_head`. Complete
35
+ that final task only when every runnable review is CLEAN on the same HEAD.
36
+ On `pacer=portable`, `open-run` returns the same list.
37
+
38
+ ### Pacer selection
39
+
40
+ Scan the tool list for `ScheduleWakeup`. Record whether it is present, then
41
+ select the pacer:
42
+
43
+ ```
44
+ python "$HOME/.claude/skills/_shared/pr-loop/scripts/select_converge_pacer.py" \
45
+ --skill pr-converge \
46
+ --has-workflow <0|1> \
47
+ --has-schedule-wakeup <0|1>
48
+ ```
49
+
50
+ - `pacer=schedule_wakeup` — native tick pacing via `ScheduleWakeup` (see
51
+ [`workflows/schedule-wakeup-loop.md`](workflows/schedule-wakeup-loop.md)).
52
+ - `pacer=portable` — continuous in-session driver on
53
+ [`../_shared/pr-loop/portable-driver.md`](../_shared/pr-loop/portable-driver.md).
54
+ **Do not abort** because `ScheduleWakeup` is missing.
55
+
56
+ ### Worktree isolation
57
+
58
+ When the tool list includes `EnterWorktree`, call it with no arguments before
59
+ any API call, file read, or edit. Agent-view sessions start in the shared
60
+ checkout; Bash (`gh`, `git`) does not auto-isolate. Do not proceed until the
61
+ working directory contains `.claude/worktrees/`. If `EnterWorktree` fails,
62
+ report the failure and stop.
63
+
64
+ When `EnterWorktree` is absent, isolate with git worktree machinery per
65
+ [`../_shared/pr-loop/portable-driver.md`](../_shared/pr-loop/portable-driver.md)
66
+ § Isolation and worktree, then run strict
67
+ `preflight_worktree.py` for the PR's owner/repo. Fail closed only when the
68
+ checkout is not the PR's repo on the PR head ref.
69
+
70
+ `EnterWorktree` (or the portable worktree path) isolates the session repo.
71
+ When the PR lives in a different repo, Step 1.5 routes the working directory
72
+ into a **PR worktree** of that repo on its head branch (routine, never a
73
+ pause). See [`reference/per-tick.md` § Step 1.5](reference/per-tick.md).
74
+
75
+ ## Resume from a prior run
76
+
77
+ Before Step 0, check
78
+ `~/.claude/runtime/pr-loop/bugteam-pr-<PR number>/handoff.json`. When
79
+ `$CLAUDE_JOB_DIR/pr-converge-state.json` is absent but that handoff exists, seed
80
+ `phase`, `tick_count`, and the clean-at SHAs from the run's `state-copy.json`, so
81
+ a fresh session continues where the last one stopped rather than restarting at
82
+ CODE_REVIEW.
83
+
84
+ ## Copilot quota pre-check (start of run)
85
+
86
+ On the first tick, apply the `reviewer-gates` skill's Copilot quota gate
87
+ (`../reviewer-gates/SKILL.md` § Gate 2) once per run, never per tick. The
88
+ flag lives in `pr-converge-state.json` as `copilot_down`, and every tick reads
89
+ it. While `copilot_down` is true, the tick skips all Copilot work — no fetch,
90
+ no request, no poll, no agent — and exports
91
+ `CLAUDE_REVIEWS_DISABLED="copilot"` in that tick's shell before
92
+ `check_convergence.py`, so the check bypasses the Copilot review gate and the
93
+ pending-requested-reviews gate and the run still marks ready on the remaining
94
+ signals.
95
+
96
+ ## Copilot findings — tier, verify, then route
97
+
98
+ Tier, verify, and route each Copilot finding via
99
+ [`copilot-finding-triage`](../copilot-finding-triage/SKILL.md) (self-healing
100
+ auto-fix; code-concern verify then confirmed / refuted / inconclusive).
101
+
102
+ **pr-converge phase routing after triage:**
103
+
104
+ - Self-healing and **confirmed** findings join the fix tick on `current_head`;
105
+ after push, reset clean-at SHAs, set `phase = CODE_REVIEW`, return to Step 5.
106
+ - **Refuted** findings resolve clean on the thread; no phase change.
107
+ - One or more **inconclusive** findings: do not mark ready. Page via the
108
+ triage skill's ntfy step, then hold by pacer:
109
+ - **`pacer=schedule_wakeup`** triage user gate (`ScheduleWakeup` /
110
+ `send_later` 45-minute hold across ticks; persist the deadline so each tick
111
+ reads it on entry).
112
+ - **`pacer=portable`** in-session deadline poll or handoff per
113
+ [`../_shared/pr-loop/portable-driver.md`](../_shared/pr-loop/portable-driver.md);
114
+ never `ScheduleWakeup` or `send_later`.
115
+ Act on the user's direction inside the window; on timeout, teardown and report
116
+ the findings un-reviewed.
117
+ - Enter `COPILOT_WAIT` only from gate (d) after requesting a Copilot review
118
+ (Step 7 7a). Stay on `COPILOT_WAIT` until a review surfaces at
119
+ `current_head`, `copilot_wait_count >= 3` hard-blocks, or `copilot_down`
120
+ skips the Copilot path entirely.
121
+
122
+ ## Budget-aware tick boundaries
123
+
124
+ Before starting any tick, estimate whether the remaining session/usage
125
+ budget covers one full clean tick (worst case: a BUGBOT fetch + a
126
+ full-diff CODE_REVIEW + a fix commit + replies). If it does not, do not
127
+ start the tick. Stop at the current tick boundary: write updated state to
128
+ `$CLAUDE_JOB_DIR/pr-converge-state.json`, write the durable handoff (see
129
+ [State persistence](#state-persistence)), then report the exact resume
130
+ command (`/pr-converge <PR URL>`) and the persisted `phase`/`tick_count`.
131
+ A tick cut off mid-flight poisons the resume state — clean SHAs recorded
132
+ against work that never landed — so an unstarted tick is always cheaper
133
+ than a half-finished one.
134
+
135
+ ## Findings discipline
136
+
137
+ Every finding, reply, and report states verified facts only — no hedging
138
+ language (`likely`, `probably`, `should`, `appears to`). Verify each
139
+ claim against the code on `current_head` before stating it; the
140
+ anti-hallucination Stop hook rejects hedged output, forcing a rework
141
+ pass. A claim that cannot be verified is reported as unverified, not
142
+ softened.
143
+
144
+ ## State persistence
145
+
146
+ Single-PR mode persists loop state to `$CLAUDE_JOB_DIR/pr-converge-state.json`.
147
+ On tick entry, read this file if it exists to restore phase, tick_count, and
148
+ clean-at SHAs. On tick exit, write updated state before the next pacer step
149
+ (`ScheduleWakeup` when `pacer=schedule_wakeup`; continuous continue or
150
+ in-session poll when `pacer=portable`) so the next tick resumes with accurate
151
+ state.
152
+
153
+ After the state write and before the next pacer step, write the durable handoff
154
+ so a fresh session in a new job can resume this run:
155
+
156
+ ```
157
+ python "$HOME/.claude/skills/_shared/pr-loop/scripts/write_handoff.py" \
158
+ --pr-number <N> --head-ref <branch> --phase <phase> \
159
+ --resume-command "/pr-converge <PR URL>" \
160
+ --state-file "$CLAUDE_JOB_DIR/pr-converge-state.json" \
161
+ --completed-steps "<clean phases this run>"
162
+ ```
163
+
164
+ It writes `handoff.json`, `HANDOFF.md`, and `state-copy.json` under
165
+ `~/.claude/runtime/pr-loop/<run-name>/`. The job-dir state stays the source of
166
+ truth for a resumed tick; the handoff copy is the pointer a fresh session reads
167
+ when `$CLAUDE_JOB_DIR` is gone.
168
+
169
+ Fields: `phase`, `tick_count`, `bugbot_clean_at`, `code_review_clean_at`,
170
+ `bugteam_clean_at`, `copilot_clean_at`, `merge_state_status`, `current_head`,
171
+ `bugbot_acknowledged_at`, `bugbot_down`, `copilot_down`,
172
+ `bugteam_skill_invoked_at_head`, `bugteam_skill_invoked_at_tick`,
173
+ `agents_session_id`, `persistent_agents`.
174
+
175
+ ## Persistent per-step agents
176
+
177
+ Three step-scoped agents persist across ticks so their context carries
178
+ forward: `fix_executor`, `thread_sweep`, and `copilot_watch`, recorded in
179
+ the `persistent_agents` map
180
+ ([`reference/state-schema.md`](reference/state-schema.md)).
181
+
182
+ - **Resume:** read `persistent_agents.<key>`. When an entry exists,
183
+ `SendMessage` to the stored `agent_id` with a tick payload that restates
184
+ the PR scope, `current_head`, the PR worktree path, this tick's findings
185
+ or threads, and the report-back contract. Bump `last_used_tick`, then
186
+ await completion.
187
+ - **First spawn:** `Agent(subagent_type: "clean-coder", name:
188
+ "prc-fix-<PR#>")` — the `name` makes the agent a persistent teammate
189
+ that idles awaiting messages. Record `{agent_id, created_tick,
190
+ last_used_tick}` under the step key. Keep the spawn prompt fix-shaped,
191
+ never audit-shaped: the `pr_converge_bugteam_enforcer` hook blocks
192
+ audit-shaped clean-coder spawns during the BUGTEAM phase.
193
+ - **Stale or dead id:** on a `SendMessage` failure, or no acknowledgment
194
+ within one bounded wait, drop the map entry, spawn a fresh named agent,
195
+ record it, and continue the tick. Never abort a tick on a stale id;
196
+ never retry the same dead id.
197
+ - **Fresh every round (never persisted):** the Step 5 host-aware
198
+ `invoke_code_review.py` / `/code-review high --fix` pass and the Step 6
199
+ bugteam audit (unbiased eyes each round; the enforcer needs the formal
200
+ Skill call), and every `code-verifier` a named code-verifier never fires
201
+ `SubagentStop`, so no verdict mints (see the named-`code-verifier` entry
202
+ in the Gotchas list below).
203
+ - **Shutdown:** at loop end (convergence or a stop condition), send each
204
+ persistent agent a shutdown request and clear `persistent_agents` before
205
+ the `pr-loop-lifecycle` Close.
206
+
207
+ ## Gotchas
208
+
209
+ Highest-signal content. Append a bullet each time a tick fails in a new
210
+ way these are the hard-won lessons that keep the loop honest. Once this
211
+ grows to 5 or more items, suggest spinning up a subagent to investigate, fix,
212
+ post a fresh PR in a fresh branch based on origin main to the user.
213
+
214
+ - **`ScheduleWakeup` not in subagent tool registries** background
215
+ `general-purpose` tick cannot schedule re-entry; only the parent session
216
+ with `ScheduleWakeup` (or the portable continuous driver on the parent)
217
+ may pace the next tick.
218
+ - **Portable host has no durable wake outside the session** when
219
+ `pacer=portable`, budget and context bound how many ticks complete; write
220
+ handoff at a tick boundary and resume with `/pr-converge <PR URL>`.
221
+ - **`state.json` without §Concurrency lock loses merges** when teammates
222
+ finish in same wall-clock window.
223
+ - **`tick_count` must not double-increment** — conversation state line
224
+ only when **no** `state.json`; with `state.json`, only the
225
+ orchestrator bump increments.
226
+ - **Bugbot trigger and detection** — CI check-run based. Full flow at
227
+ [`per-tick.md` Step 3](reference/per-tick.md); see also
228
+ `~/.claude/skills/pr-converge/scripts/check_bugbot_ci.py --help`.
229
+ - **Bot login fields differ by endpoint** — `get_reviews` returns
230
+ `.user.login` (object), but `get_review_comments` returns `.author`
231
+ (string, not an object). Always check the correct fields and use
232
+ case-insensitive substring matching on login values, never strict
233
+ equality.
234
+ - **`isOutdated` has dual scope** — GitHub marks a thread `isOutdated=true`
235
+ when the line it anchors to has changed since the comment was posted. The
236
+ machine gate (`check_convergence.py` / [convergence-gates.md](reference/convergence-gates.md)
237
+ gate (e)) excludes `isOutdated == true` bot threads from the fail count —
238
+ only non-outdated unresolved bot threads fail the gate. The agent-side
239
+ unresolved-thread sweep in the shared fix protocol
240
+ ([`../../_shared/pr-loop/fix-protocol.md`](../../_shared/pr-loop/fix-protocol.md)
241
+ step 12; skill deltas in [`reference/fix-protocol.md`](reference/fix-protocol.md))
242
+ still verifies outdated unresolved threads against HEAD before resolve when
243
+ the protocol requires it (the original concern can still apply when the fix
244
+ moved rather than landed).
245
+ - **Tilde paths fail on Windows Git Bash** — `~/.claude/skills/...`
246
+ resolves to the wrong home directory in Bash-tool contexts. Use
247
+ `$HOME/.claude/skills/...` in shell invocations or `Path.home() /
248
+ ".claude/skills/..."` in Python scripts. Script invocations through
249
+ Bash that reference `~` produce "file not found" errors
250
+ indistinguishable from actual script failures.
251
+ - **PowerShell cmdlets fail in Bash tool** — `.ps1` scripts and
252
+ `pwsh` calls run through the PowerShell tool, not Bash. Bash on
253
+ Windows is Git Bash which cannot execute PowerShell cmdlets. Route all
254
+ PowerShell work through the PowerShell tool or `pwsh -NoProfile -File`.
255
+ - **Cross-repo PR: route cwd into the PR worktree before Step 5 review**
256
+ `invoke_code_review.py` and `/code-review high --fix` audit the repo of the
257
+ cwd (the helper's `--cwd`). When the session is rooted in a different repo
258
+ than the PR, `EnterWorktree` cannot re-root (it is scoped to the session's
259
+ repo); resolve the PR worktree and `cd` into it per
260
+ [Step 1.5](reference/per-tick.md). Skipping this reviews and edits the
261
+ wrong repo. The route is routine and automatic — never a material fork
262
+ to pause on.
263
+ - **A named/teammate `code-verifier` never mints a verdict** In a background-job session, an `Agent`-tool `code-verifier` spawned with a `name` (or otherwise as a persistent teammate) goes idle/"available" awaiting messages rather than terminating, so its `SubagentStop` never fires. `verifier_verdict_minter.py` mints the verdict only on `SubagentStop`, so no verdict file is written and `verified_commit_gate` blocks the `git commit`/`git push` with "no passing verification verdict" even though the verifier emitted `all_pass`. A `shutdown_request` is ignored and `TaskStop` cannot resolve the teammate's id. Spawn the code-verifier as a one-shot agent with NO `name` (a plain async `Agent` call) so it runs to completion and fires `SubagentStop`, minting the verdict bound to the live surface. Keep the work tree frozen between verification and the commit so the minted surface hash still matches.
264
+
265
+ ## Progress checklist
266
+
267
+ State variables (`phase`, `bugbot_clean_at`, `code_review_clean_at`,
268
+ `bugteam_clean_at`, `copilot_clean_at`, `merge_state_status`, counters) are
269
+ defined in [`reference/state-schema.md`](reference/state-schema.md). Ground rules
270
+ in [`reference/ground-rules.md`](reference/ground-rules.md).
271
+
272
+ Each step references its spoke file for full procedural detail — MCP calls,
273
+ script invocations, decision criteria. Every "return to Step N" means the next
274
+ tick starts fresh from that step.
275
+
276
+ **Hard gate: do not advance from any step while ANY unresolved review
277
+ thread exists on the PR.** Sweep semantics and per-thread handling live in
278
+ the shared fix protocol
279
+ ([`../../_shared/pr-loop/fix-protocol.md`](../../_shared/pr-loop/fix-protocol.md)
280
+ step 12; skill deltas in [`reference/fix-protocol.md`](reference/fix-protocol.md)).
281
+
282
+ **Full-PR-diff rule: every CODE-REVIEW round (Step 5) and every BUGTEAM
283
+ round (Step 6) covers the FULL `origin/main...HEAD` diff — every file
284
+ the PR touches.** A round that scopes to a subset only the last commit,
285
+ only files touched since the prior clean SHA, only bugbot-flagged paths,
286
+ or any other delta cut does not satisfy the gate, and a "clean" verdict
287
+ against a partial diff is not a valid clean. Re-run the round against the
288
+ full diff before recording `code_review_clean_at` or treating the bugteam
289
+ round as converged. This rule holds every tick, every loop, every PR.
290
+
291
+ - [ ] **Step 0: Open the run**
292
+ Apply the `pr-loop-lifecycle` skill's Open section
293
+ (`../pr-loop-lifecycle/SKILL.md`): permission grant + worktree preflight.
294
+
295
+ - [ ] **Step 1: Resolve PR scope + PR worktree**
296
+ Apply Step 1.5 scope resolution in
297
+ [`reference/per-tick.md`](reference/per-tick.md). Resolve the **PR
298
+ worktree** — the local checkout every local step this tick targets:
299
+ the `EnterWorktree` checkout when the PR shares the session's repo,
300
+ else a checkout of the PR's repo that the working directory routes
301
+ into via `cd`. Cross-repo routing is automatic, not a fork.
302
+ See: [`reference/per-tick.md` § Step 1](reference/per-tick.md)
303
+ and Step 1.5](reference/per-tick.md)
304
+
305
+ - [ ] **Step 2: Initialize loop state**
306
+ `phase = CODE_REVIEW`; all counters at zero; `run_name` resolved.
307
+
308
+ - [ ] **Step 3: Mergeability check**
309
+ See: [`reference/convergence-gates.md` § (c)](reference/convergence-gates.md)
310
+
311
+ ```
312
+ pull_request_read(method="get") .mergeable_state, .mergeable
313
+ ```
314
+
315
+ - [ ] mergeable advance to Step 4
316
+ - [ ] not mergeable rebase force-push return to Step 1
317
+
318
+ - [ ] **Step 4: BUGBOT — terminal Bugbot confirmation gate**
319
+ Step 6 routes here after BUGTEAM converges. Bugbot confirms code the
320
+ internal passes already drove to clean.
321
+ See: [`reference/per-tick.md` § BUGBOT terminal gate + Step 3](reference/per-tick.md);
322
+ availability and trigger via `reviewer-gates`
323
+ (`../reviewer-gates/SKILL.md` § Gate 1 / Gate 3).
324
+
325
+ - [ ] **disabled / down** → `bugbot_down = true` → Step 7
326
+ - [ ] **dirty on `current_head`** → apply shared fix protocol
327
+ ([`../../_shared/pr-loop/fix-protocol.md`](../../_shared/pr-loop/fix-protocol.md);
328
+ skill deltas in [`reference/fix-protocol.md`](reference/fix-protocol.md))
329
+ → push → reset push-invalidated markers `phase = CODE_REVIEW` Step 5
330
+ - [ ] **clean on `current_head`** zero unresolved threads (else fix + resolve first)
331
+ → `bugbot_clean_at = current_head` → Step 7
332
+ - [ ] **no review / commit_id mismatch** `reviewer-gates` Bugbot flow (Gate 3):
333
+ silent pass stamp + Step 7; queued/triggered ScheduleWakeup 360s or portable in-session poll → Step 4;
334
+ down `bugbot_down = true`Step 7
335
+
336
+ - [ ] **Step 5: CODE-REVIEW static sweep, review, fix, advance**
337
+ Entry phase every tick; re-entered after any fix push.
338
+ See: [`reference/per-tick.md` § CODE_REVIEW entry](reference/per-tick.md).
339
+ Pre-condition: cwd is the Step 1.5 PR worktree on `current_head`.
340
+ Scope: FULL `origin/main...HEAD` diff every tick (no path args, no delta cut).
341
+ Review always runs at effort high on model opus through
342
+ `invoke_code_review.py`. Mode decision inputs: host profile + session
343
+ model. Call:
344
+ `python "$HOME/.claude/scripts/invoke_code_review.py" --cwd <PR-worktree>
345
+ --session-model <alias>`. Chain mode uses that cwd and empty stdin; the
346
+ chain process never commits and never pushes. JSON stdout carries
347
+ `mode` (`in_session` | `chain`), `served_command`, `returncode`, and
348
+ `dirty_tree`. Config/host errors still emit that JSON with non-zero
349
+ `returncode` (no traceback-only failure).
350
+
351
+ - [ ] **Static sweep fails** apply shared fix protocol push reset markers
352
+ stay CODE_REVIEW → Step 5
353
+ - [ ] **`mode == in_session`** (Claude host, session model opus) → run
354
+ `/code-review high --fix` in-session (no path args)
355
+ - [ ] **`mode == chain`** (any other host or non-opus session) → helper
356
+ already ran the headless review; read `returncode`,
357
+ `served_command`, and `dirty_tree` from JSON
358
+ - [ ] **failed review** (`returncode != 0`, or chain with null
359
+ `served_command`) do not set `code_review_clean_at` stay
360
+ CODE_REVIEW → Step 5
361
+ - [ ] **fixes applied** (`dirty_tree` true / working tree dirty)
362
+ commit + push via shared fix protocol reset markersstay
363
+ CODE_REVIEWStep 5
364
+ - [ ] **clean** (successful serve: `returncode == 0`, chain
365
+ `served_command` non-null when chain, and `dirty_tree` false) →
366
+ zero unresolved threads (else fix + resolve)
367
+ `code_review_clean_at = current_head` `phase = BUGTEAM` → Step 6
368
+
369
+ - [ ] **Step 6: BUGTEAM — run, decide, fix, reply, resolve**
370
+ See: [`reference/per-tick.md` § Step 2 BUGTEAM](reference/per-tick.md);
371
+ [`../bugteam/SKILL.md`](../bugteam/SKILL.md).
372
+ Pre-condition: `code_review_clean_at == current_head`.
373
+ Mandatory: `Skill({skill: "bugteam", args: "<PR URL>"})` this tick
374
+ (enforcer-blocked otherwise; `qbug` is not a substitute). Scope: FULL
375
+ `origin/main...HEAD` diff. Re-resolve HEAD after bugteam.
376
+
377
+ - [ ] **bugteam pushed** verify threads replied + resolved → reset markers
378
+ `phase = CODE_REVIEW` ScheduleWakeup 360s or portable in-session poll → Step 5
379
+ - [ ] **converged, no push** → zero unresolved threads →
380
+ `bugteam_clean_at = current_head` → `phase = BUGBOT` → Step 4
381
+ - [ ] **findings without committed fixes** → apply shared fix protocol → push →
382
+ reset markers → `phase = CODE_REVIEW` → Step 5
383
+
384
+ - [ ] **Step 7: Convergence gates**
385
+ Full procedure: [`reference/convergence-gates.md`](reference/convergence-gates.md).
386
+
387
+ Pre-condition: Step 6 converged AND (`bugbot_clean_at == current_head` OR
388
+ `bugbot_down`). The terminal Bugbot gate (Step 4) sets that state just
389
+ before these gates run. Count unresolved threads before each gate.
390
+ Every gate records evidence; gate (f) cites evidence from (a)–(e).
391
+
392
+ - [ ] **(a) Copilot findings** — fetch Copilot on `current_head`; dirty → fix + return to Step 5; clean → stamp `copilot_clean_at`; absent → continue; when `copilot_down`, skip
393
+ - [ ] **(b) Claude reviewer** — fetch Claude on `current_head`; dirty → fix + return to Step 5; clean or absent → continue
394
+ - [ ] **(c) Mergeability** — `mergeable_state == "clean"` and `mergeable == true`; dirty → rebase + return to Step 1; blocked/behind/unknown/unstable → hard blocker
395
+ - [ ] **(d) Post-convergence Copilot request** — request Copilot when not pending and not `copilot_down`; enter `COPILOT_WAIT` (Step 7a); when `copilot_down`, skip to (e)
396
+ - [ ] **(e) Thread-resolution** — zero unresolved threads across the PR; else sweep + fix/resolve
397
+ - [ ] **(f) Mark ready** run `check_convergence.py`; exit 0 → `update_pull_request(draft=false)` Step 8; exit 1 → fix path; exit 2 → retry/stop
398
+
399
+ - [ ] **Step 7a: COPILOT_WAIT fetch Copilot, decide**
400
+ See: [`reference/per-tick.md` § Step 2 COPILOT_WAIT](reference/per-tick.md).
401
+ Skipped entirely when `copilot_down` (gate (d) never enters this phase).
402
+
403
+ - [ ] **clean** → `copilot_clean_at = current_head` Step 7 (re-validate (b), (c), (e), (f))
404
+ - [ ] **dirty** → apply shared fix protocol
405
+ ([`../../_shared/pr-loop/fix-protocol.md`](../../_shared/pr-loop/fix-protocol.md);
406
+ skill deltas in [`reference/fix-protocol.md`](reference/fix-protocol.md))
407
+ push reset markers → `phase = CODE_REVIEW` → Step 5
408
+ - [ ] **no review yet** → increment `copilot_wait_count` → ≥ 3 hard-blocks;
409
+ else ScheduleWakeup 360s or portable in-session poll Step 7a next tick
410
+
411
+ - [ ] **Step 8: Clean working tree**
412
+ `pr-loop-lifecycle` Close (`../pr-loop-lifecycle/SKILL.md`).
413
+ See: [`pr-loop-lifecycle/reference/teardown-publish-permissions.md` § Clean working tree](../pr-loop-lifecycle/reference/teardown-publish-permissions.md)
414
+
415
+ - [ ] **Step 9: Rewrite PR description**
416
+ `pr-loop-lifecycle` Close.
417
+ See: [`pr-loop-lifecycle/reference/teardown-publish-permissions.md` § Publish the final PR description](../pr-loop-lifecycle/reference/teardown-publish-permissions.md)
418
+
419
+ - [ ] **Step 10: Revoke project permissions (always)**
420
+ `pr-loop-lifecycle` Close (`../pr-loop-lifecycle/SKILL.md` § Close).
421
+
422
+ - [ ] **Step 11: Print final report**
423
+ Print this block verbatim — no paraphrase, no extra commentary:
424
+ ```
425
+ /pr-converge exit: converged
426
+ Loops: <N>
427
+ Final commit: <SHA>
428
+ ```
429
+
430
+ ## Edge cases
431
+
432
+ | Situation | Read |
433
+ |---|---|
434
+ | Multi-PR session (`state.json` exists) | [`reference/multi-pr-orchestration.md`](reference/multi-pr-orchestration.md) |
435
+ | Hard blocker or user stops loop | [`reference/stop-conditions.md`](reference/stop-conditions.md) |
436
+ | Tick is ambiguous against the steps above | [`reference/examples.md`](reference/examples.md) |
437
+
438
+ ## Depends on / invokes
439
+
440
+ | Skill / path | Role |
441
+ |---|---|
442
+ | `pr-loop-lifecycle` | Open (permission grant + worktree preflight) and Close (cleanup, PR description, revoke) |
443
+ | `reviewer-gates` | Copilot quota gate, Bugbot availability, Bugbot trigger flow |
444
+ | `copilot-finding-triage` | Tier, verify, and route each Copilot finding |
445
+ | `bugteam` | Full-diff bug audit in Step 6 (Skill invocation mandatory) |
446
+ | `pr-loop-cloud-transport` | When `gh` is unavailable or unauthenticated |
447
+ | [`../_shared/pr-loop/portable-driver.md`](../_shared/pr-loop/portable-driver.md) | Continuous in-session pacer when `ScheduleWakeup` is absent |
448
+ | [`../../_shared/pr-loop/fix-protocol.md`](../../_shared/pr-loop/fix-protocol.md) | Shared 13-step fix sequence (TDD, commit, push, reply, resolve) |
449
+ | [`reference/fix-protocol.md`](reference/fix-protocol.md) | pr-converge deltas on the shared fix protocol |
450
+
451
+ ## Package file index
452
+
453
+ | Path | Purpose |
454
+ |---|---|
455
+ | `SKILL.md` | Hub: pre-flight, state, progress checklist, gotchas |
456
+ | `CLAUDE.md` | Package map for agents working in this skill |
457
+ | `pr_converge_skill_constants/` | Importable constants for skill scripts |
458
+ | `reference/per-tick.md` | Full per-tick procedure (phases, cwd routing, pacing) |
459
+ | `reference/convergence-gates.md` | Six gates before mark-ready |
460
+ | `reference/fix-protocol.md` | pr-converge fix-protocol deltas |
461
+ | `reference/ground-rules.md` | Non-negotiable loop constraints |
462
+ | `reference/state-schema.md` | `pr-converge-state.json` fields |
463
+ | `reference/stop-conditions.md` | Hard stops without convergence |
464
+ | `reference/multi-pr-orchestration.md` | Multi-PR session orchestration |
465
+ | `reference/examples.md` | Worked tick sequences |
466
+ | `reference/obstacles/` | Per-obstacle fix runbooks |
467
+ | `scripts/` | Convergence helpers, Copilot fetch, fix-reply poster, tests |
468
+ | `workflows/schedule-wakeup-loop.md` | ScheduleWakeup pacing (`pacer=schedule_wakeup`) |
469
+ | [`../_shared/pr-loop/portable-driver.md`](../_shared/pr-loop/portable-driver.md) | Portable continuous pacer (`pacer=portable`) |